WebGL基本图形三角形的绘制

635 阅读4分钟

三角形的绘制比点的绘制多对多一个步骤。那就是三角形需要创建缓冲区。有以下几种绘制三角形的方法。 三种绘制方式的代码几乎是一样的。只是在进行渲染时drawArrays(type,start,count)中的type不一样。

以下例子主要用到以下GLSL知识。

gl.createBuffer:创建buffer。
gl.bindBuffer:绑定某个缓冲区对象为当前缓冲区。
gl.bufferData:往缓冲区中复制数据。
gl.enableVertexAttribArray:启用顶点属性。
gl.vertexAttribPointer:设置顶点属性从缓冲区中读取数据的方式。
  • 顶点着色器代码
<script id="vertexShader" type="x-shader/x-vertex">
  //attribute声明vec4类型变量apos
  attribute vec4 apos;
  void main() {
    //顶点坐标apos赋值给内置变量gl_Position
    //逐顶点处理数据
    gl_Position = apos;
  }
</script>
  • 片元着色器代码
<script id="fragmentShader" type="x-shader/x-fragment">
  void main() {
    // 逐片元处理数据,所有片元(像素)设置为红色
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
  }
</script>
  • javascript代码,主要由以下4部分代码构成
    • 获取WebGL上下文
    //通过getElementById()方法获取canvas画布
    const canvas = document.getElementById('webgl');
    //通过方法getContext()获取WebGL上下文
    const gl = canvas.getContext('webgl');
    //初始化着色器
    const program = initShader(gl);
    //初始化缓存区
    const count = initBuffer(gl);
    render(gl, program, count);
    
    • 声明初始化着色器函数
    //声明初始化着色器函数
    function initShader(gl) {
        //顶点着色器源码
        const vertexShaderSource = document.getElementById('vertexShader').innerText;
        //片元着色器源码
        const fragmentShaderSource = document.getElementById('fragmentShader').innerText;
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(vertexShader, vertexShaderSource);
        gl.shaderSource(fragmentShader, fragmentShaderSource);
        gl.compileShader(vertexShader);
        gl.compileShader(fragmentShader);
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        gl.useProgram(program);
        return program;
    }
    
    • 初始化缓冲数据
    //初始化缓冲数据
    function initBuffer(gl) {
        //类型数组构造函数Float32Array创建顶点数组
        const data = new Float32Array([
        -0.5, 0.5,
        -0.5, -0.5,
        0.5, 0.5,
        ]);
    
        //创建缓冲区对象
        var buffer = gl.createBuffer();
        //绑定缓冲区对象,激活buffer
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        //顶点数组data数据传入缓冲区
        gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
        return data.length;
    }
    
    • 渲染
    function render(gl, program, count) {
        //获取顶点着色器的位置变量apos,即aposLocation指向apos变量。
        const aposLocation = gl.getAttribLocation(program, 'apos');
    
        //缓冲区中的数据按照一定的规律传递给位置变量apos
        gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0);
        //允许数据传递
        gl.enableVertexAttribArray(aposLocation);
        //设置清屏颜色为黑色。
        gl.clearColor(0, 0, 0, 1.0);
        //清屏
        gl.clear(gl.COLOR_BUFFER_BIT);
        //开始绘制图形
        gl.drawArrays(gl.TRIANGLES, 0, count);
    }
    

1、常规方式

gl.drawArrays(gl.TRIANGLES, 0, 3);

2、三角形带

绘制三角形的数量 = 顶点数 - 2

 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);

3、三角形扇

绘制三角形的数量 = 顶点数 - 2

 gl.drawArrays(gl.TRIANGLE_FAN, 0, 3);

4、动态绘制三角形

着色器中的代码跟动态绘制点的代码几乎是一样的。只是多了一个颜色插值的处理。

  • 顶点着色器代码
<script type="shader-source" id="vertexShader">
    //浮点数设置为中等精度
    precision mediump float;
    //接收 JavaScript 传递过来的点的坐标(X, Y)
    attribute vec2 a_Position;
    attribute vec4 a_Color;
    varying vec4 v_Color;
    void main(){
        // 最终的顶点坐标。
        gl_Position = vec4(a_Position, 0.0, 1.0);
        //进行颜色插值计算
        v_Color = a_Color;
    }
</script>
  • 片元着色器代码
<script type="shader-source" id="fragmentShader">
    //浮点数设置为中等精度
    precision mediump float;
    // 用来接收顶点着色器插值后的颜色。
    varying vec4 v_Color;
    void main(){
        // 点的最终颜色。
        gl_FragColor =  v_Color;
    }
</script>
  • javascript代码
    • WebGL上下文的获取
    //通过getElementById()方法获取canvas画布
    const canvas = document.getElementById('webgl');
    //设置canvas尺寸为满屏
    //注意设置canvas的尺寸必须在获取WebGL上下文之前调用
    resizeCanvas(canvas);
    //通过方法getContext()获取WebGL上下文
    const gl = canvas.getContext('webgl');
    
    • canvas的尺寸动态设置
    //设置canvas的大小
    function resizeCanvas(canvas, width, height) {
        if (canvas.width !== width) {
            canvas.width = width ? width : window.innerWidth;
        }
        if (canvas.height !== height) {
            canvas.height = height ? height : window.innerHeight;
        }
    }
    
    • 着色器中的变量赋值处理
    function assignValue(gl,program){
        let a_Position = gl.getAttribLocation(program, 'a_Position');
        let a_Color = gl.getAttribLocation(program, 'a_Color');
        gl.enableVertexAttribArray(a_Position);
        gl.enableVertexAttribArray(a_Color);
        let buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        //将缓冲区中的数据按照一定规律传递给a_Position,表示从缓存区中获取两个浮点型数据(浮点型数据,4个字节),缓存区一行为4*6个字节,偏移量为0
        gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 4*6, 0);
        gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false,4*6, 8);
    }
    
    • 点击事件监听
    const positions = [];
    function initClick() {  
        canvas.addEventListener("click", e => {
            const color = randomColor();
            const position = coordTransform(e.pageX, e.pageY)
            positions.push(...position, ...Object.values(color));
            // 顶点信息为 18 的整数倍即3个顶点时执行绘制操作,因为三角形由三个顶点组成,每个顶点由六个元素组成。
            if (positions.length % 18 == 0) {
                // gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
                render(gl);
            }
        })
    }
    //颜色值的随机生成
    const random = Math.random;
    function randomColor() {
        return {
            r: random(),
            g: random(),
            b: random(),
            a: random()
        };
    }
    
    • 声明初始化着色器函数
    //声明初始化着色器函数
    function initShader(gl) {
        //顶点着色器源码
        const vertexShaderSource = document.getElementById('vertexShader').innerText;
        //片元着色器源码
        const fragmentShaderSource = document.getElementById('fragmentShader').innerText;
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(vertexShader, vertexShaderSource);
        gl.shaderSource(fragmentShader, fragmentShaderSource);
        gl.compileShader(vertexShader);
        gl.compileShader(fragmentShader);
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        gl.useProgram(program);
        return program;
    }
    
    • 屏幕坐标换算成裁剪坐标系(顶点着色器)的坐标
    //屏幕坐标换算成裁剪坐标系的坐标(在着色器中的坐标)
    const { width, height } = canvas;
    function coordTransform(x, y) {
        // 将 canvas 的坐标值 转换为 [-1.0, 1.0]的范围。
        const posisionX = 2 * x / width - 1;
        // canvas的 Y 轴坐标方向和 裁剪坐标系的相反。
        const positionY = -(2 * y / height - 1);
        return [posisionX, positionY];
    }
    
    • WebGL渲染
    function render(gl) {
        //设置清屏颜色为黑色。
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
        if (positions.length > 0) {
            gl.drawArrays(gl.TRIANGLES, 0, positions.length / 6);
        }
    }
    //创建着色器程序
    const program = initShader(gl);
    constassignValue(gl,program)
    initClick();
    render(gl);
    

参考

WebGL零基础入门教程(郭隆邦);
WebGL 入门与实践
WebGL官方文档