下边准备了线立方体和面立方体的实现方式。他们的主要不同点也是在于数据构造和绘制方式上。绘制立方体个人感觉你要学会自己自己去构造立方体所需要的数据。明白为什么么要构造出这种数据格式。关于物体的旋转矩阵的构造的原理后面会细说,先学会用就行。 在这片文章中用到了新的GLSL知识。

  • mat4:4维矩阵
  • gl.drawArrays(gl.LINE_LOOP,0,4): 以闭合线的形式绘制一个面
  • gl.drawArrays(gl.LINES,8,8):绘制线条。

# 1、面立方体

  • 顶点着色器代码
<script id="vertexShader" type="x-shader/x-vertex">
    //浮点数设置为中等精度
    precision mediump float;
    attribute vec4 apos;
    attribute vec4 acolor;
    varying vec4 vcolor;
    //矩阵变量
    uniform mat4 mx;
    //矩阵变量
    uniform mat4 my;
    void main() {
        //两个旋转矩阵、顶点齐次坐标连乘
        gl_Position = mx*my*apos;
        vcolor=acolor;
    }
</script>
  • 片元着色器代码
<script id="fragmentShader" type="x-shader/x-fragment">
    // 所有float类型数据的精度是lowp
    precision mediump float;
    varying vec4 vcolor;
    void main() {
        gl_FragColor =vcolor;
    }
</script>
  • javascript代码部分
    • WebGL上下文获取
    function init() {
        //通过getElementById()方法获取canvas画布
        const canvas = document.getElementById('WebGL');
        //通过方法getContext()获取WebGL上下文
        const gl = canvas.getContext('WebGL');
        //顶点着色器源码
        const vertexShaderSource = document.getElementById('vertexShader').innerText;
        //片元着色器源码
        const fragShaderSource = document.getElementById('fragmentShader').innerText;
        //初始化着色器
        const program = initShader(gl, vertexShaderSource, fragShaderSource);
        initBuffer(gl, program);
    }
    
    • 声明初始化着色器函数
    function initShader(gl, vertexShaderSource, fragmentShaderSource) {
        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, program) {
        //获取顶点着色器的位置变量apos
        const aposLocation = gl.getAttribLocation(program, 'apos');
        const acolor = gl.getAttribLocation(program, 'acolor');
        //创建立方体的顶点坐标数据
        const data = new Float32Array([
            -0.5, -0.5, 0.5, 1, 0, 0, 1,
            0.5, -0.5, 0.5, 1, 0, 0, 1,
            0.5, 0.5, 0.5, 1, 0, 0, 1,
            -0.5, 0.5, 0.5, 1, 0, 0, 1,
    
            -0.5, 0.5, 0.5, 0, 1, 0, 1,
            -0.5, 0.5, -0.5, 0, 1, 0, 1,
            -0.5, -0.5, -0.5, 0, 1, 0, 1,
            -0.5, -0.5, 0.5, 0, 1, 0, 1,
    
            0.5, 0.5, 0.5, 0, 0, 1, 1,
            0.5, -0.5, 0.5, 0, 0, 1, 1,
            0.5, -0.5, -0.5, 0, 0, 1, 1,
            0.5, 0.5, -0.5, 0, 0, 1, 1,
    
            0.5, 0.5, -0.5, 1, 0, 1, 1,
            0.5, -0.5, -0.5, 1, 0, 1, 1,
            -0.5, -0.5, -0.5, 1, 0, 1, 1,
            -0.5, 0.5, -0.5, 1, 0, 1, 1,
    
            -0.5, 0.5, 0.5, 1, 1, 0, 1,
            0.5, 0.5, 0.5, 1, 1, 0, 1,
            0.5, 0.5, -0.5, 1, 1, 0, 1,
            -0.5, 0.5, -0.5, 1, 1, 0, 1,
    
            -0.5, -0.5, 0.5, 0, 1, 1, 1,
            -0.5, -0.5, -0.5, 0, 1, 1, 1,
            0.5, -0.5, -0.5, 0, 1, 1, 1,
            0.5, -0.5, 0.5, 0, 1, 1, 1,
        ]);
        //顶点索引数据构造
        const indexdata = new Uint16Array([
            0, 1, 2, 0, 2, 3,
            4, 5, 6, 4, 6, 7,
            8, 9, 10, 8, 10, 11,
            12, 13, 14, 12, 14, 15,
            16, 17, 18, 16, 18, 19,
            20, 21, 22, 20, 22, 23
        ])
        vertexBuffer(gl,data,aposLocation,3,'',4*7,0);
        vertexBuffer(gl,indexdata,acolor,4,'index',4*7,12);
        render(gl, program, indexdata.length)
    }
    //判断是否是顶点索引还是常规方式,并创建缓冲区
    function vertexBuffer(gl,data,position,n,type,rowCount=0,offset=0){
            //创建缓冲区对象
            const buffer = gl.createBuffer();
            if(type==='index'){
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
            }else{
            //绑定缓冲区对象
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            //顶点数组data数据传入缓冲区
            gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
            }
        ///缓冲区中的数据按照一定的规律传递给位置变量apos
        gl.vertexAttribPointer(position, n, gl.FLOAT, false, rowCount, offset);
            //允许数据传递
        gl.enableVertexAttribArray(position);
    }
    
    • WebGL渲染
      在渲染时,有以下两点需要注意。 1、在绘制立方体时,需要对立方体旋转一定角度,才能看出立方体的三维效果,不然只能看到一个面。 2、在渲染时,要开启深度测试,不然看到的立方体不是我们想看到的效果。
    function render(gl, program, count=36) {
         /**执行绘制之前,一定要开启深度测试,以免颜色混乱
              * gl.CULL_FACE:表示隐藏正面
              * gl.DEPTH_TEST:启用深度测试。根据坐标的远近自动隐藏被遮住的图形
              * **/
        gl.enable(gl.DEPTH_TEST);
        // gl.enable(gl.CULL_FACE);
        //旋转矩阵
        tranlate(gl, program);
        //设置清屏颜色为黑色。
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
        
        gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0);
    }
    //构造矩阵,使矩阵向x,y分别旋转30度
    let angle=30.0;
    function tranlate(gl, program) {
        const rad = angle * Math.PI / 180;
        const cos = Math.cos(rad);
        const sin = Math.sin(rad);
        const mx = gl.getUniformLocation(program, 'mx');
        const mxArr = new Float32Array([
            1, 0, 0, 0, 
            0, cos, -sin, 0,
            0, sin, cos, 0, 
            0, 0, 0, 1
        ])
        gl.uniformMatrix4fv(mx, false, mxArr);
        const my = gl.getUniformLocation(program, 'my');
        const myArr = new Float32Array([
            cos, 0, -sin, 0,
            0, 1, 0, 0,
            sin, 0, cos,
            0, 0, 0, 0, 1
        ])
        
        gl.uniformMatrix4fv(my, false, myArr);
    }
    

# 2、线立方体

  • 顶点着色器代码
<script id="vertexShader" type="x-shader/x-vertex">
  //attribute声明vec4类型变量apos
  attribute vec4 apos;
  uniform mat4 mx;
  uniform mat4 my;
  void main() {
    //两个旋转矩阵、顶点齐次坐标连乘
    gl_Position = mx*my*apos;
    gl_PointSize=10.0;
  }
</script>
  • 片元着色器代码
<script id="fragmentShader" type="x-shader/x-fragment">
  void main() {
    // 逐片元处理数据,所有片元(像素)设置为红色
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
  }
</script>
  • javascript代码部分 它跟面立方体的不同主要在于数据的构造和渲染方面。线立方体的数据构造相对来说简单一些,没有用到顶点索引,是正常的顶点数据构造。
    • 着色器的变量获取及赋值
    function initbuffer(gl, program){
            //获取顶点着色器的位置变量apos
            var aposLocation = gl.getAttribLocation(program, 'apos');
        //创建立方体的顶点坐标数据
        var data = new Float32Array([
            //z为0.5时,xOy平面上的四个点坐标
            0.5, 0.5, 0.5,
            -0.5, 0.5, 0.5,
            -0.5, -0.5, 0.5,
            0.5, -0.5, 0.5,
            //z为-0.5时,xOy平面上的四个点坐标
            0.5, 0.5, -0.5,
            -0.5, 0.5, -0.5,
            -0.5, -0.5, -0.5,
            0.5, -0.5, -0.5,
            //上面两组坐标分别对应起来组成一对
            0.5, 0.5, 0.5,
            0.5, 0.5, -0.5,
    
            -0.5, 0.5, 0.5,
            -0.5, 0.5, -0.5,
    
            -0.5, -0.5, 0.5,
            -0.5, -0.5, -0.5,
    
            0.5, -0.5, 0.5,
            0.5, -0.5, -0.5,
        ]);
        //创建缓冲区对象
        var buffer = gl.createBuffer();
        //绑定缓冲区对象
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        //顶点数组data数据传入缓冲区
        gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    
        //缓冲区中的数据按照一定的规律传递给位置变量apos
        gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0);
        //允许数据传递
        gl.enableVertexAttribArray(aposLocation);
    }
    
    • WebGL渲染
    function render(gl, program) {
        tranlate(gl,program);
        //设置清屏颜色为黑色。
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
        //LINE_LOOP模式绘制前四个点
        gl.drawArrays(gl.LINE_LOOP,0,4);
        //LINE_LOOP模式从第五个点开始绘制四个点
        gl.drawArrays(gl.LINE_LOOP,4,4);
        //LINES模式绘制后8个点
        gl.drawArrays(gl.LINES,8,8);
    }
    

# 3、立方体的旋转

通过不断改变x、y、z轴的坐标来实现立方体的旋转。只需要在上面translaterender修改一下就行。

  • 修改后的translate
let angle=30.0;
function tranlate(gl, program) {
    const rad = angle * Math.PI / 180;
    const cos = Math.cos(rad);
    const sin = Math.sin(rad);

    const mx = gl.getUniformLocation(program, 'mx');
    const mxArr = new Float32Array([
        1, 0, 0, 0, 0, cos, -sin, 0, 0, sin, cos, 0, 0, 0, 0, 1
    ])
    gl.uniformMatrix4fv(mx, false, mxArr);

    const my = gl.getUniformLocation(program, 'my');
    const myArr = new Float32Array([
        cos, 0, -sin, 0,  0, 1, 0, 0,  sin, 0, cos, 0,  0, 0, 0, 1
    ])
    
    gl.uniformMatrix4fv(my, false, myArr);
    //每次渲染时,沿x轴和y轴旋转1度
    angle+=1;
}
  • 修改后的render
function render(gl, program, count=36) {
    tranlate(gl, program);
    //设置清屏颜色为黑色。
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0);
    //调用requestAnimationFrame动画函数,使立方体动起来
    requestAnimationFrame(()=>{
        render(gl, program,count);
    })
}

参考
WebGL零基础入门教程(郭隆邦) (opens new window)
WebGL 入门与实践 (opens new window)
WebGL官方文档 (opens new window)

Last Updated: 9/24/2024, 6:06:00 PM