zoukankan      html  css  js  c++  java
  • WebGL学习(2)

    原文地址:WebGL学习(2) - 3D场景

    经过前面WebGL学习(1) - 三角形的学习,我们已经掌握了webGL的基础知识,也已经能够画出最基本的图形,比如点,线,三角形,矩形等。有了2D绘图的基础,现在终于可以进入精彩的3D世界了,来看一下这一节要实现的3D的效果吧。
    实际效果:webGL3D场景

    webGL渲染流程

    重温一下webGL的渲染流程,这一节在第3、4、5、6步骤需要学习新的内容。其中写入数据交叉存放缓冲区,设置隐藏面消除,清空深度缓冲都是比较简单的部分。重点和难点是在3D变换的环节,在理解了矩阵的原理基础上,这次使用了《WebGL编程指南》提供的矩阵操作库。

    1. 获取webGL绘图上下文
    2. 初始化着色器
    3. 创建、绑定缓冲区对象
    4. 3D变换
    5. 向顶点着色器和片元着色器写入数据(数据交叉存放缓冲区)
    6. 设置canvas背景色,设置隐藏面消除
    7. 清空canvas|清空深度缓冲
    8. 绘制

    着色器

    着色器代码修改为下面,我们现在需要为每个顶点都使用不同的颜色,所以使用到了varying限定符的变量,这个变量目的就是连接顶点和片元着色器,把顶点信息和颜色信息结合起来。看到顶点着色器和片元着色器都有的v_color变量了吗?其实就是通过全局变量传递。
    顶点着色器

    <script type="x-shader/x-vertex" id="vs">
    attribute vec4 a_Position; //顶点
    uniform mat4 u_MvpMatrix;//模型视点投影矩阵
    attribute vec4 a_Color;
    varying vec4 v_color;// 连接片元着色器
    void main() { 
      	gl_Position = u_MvpMatrix * a_Position;
      	v_color=a_Color;//传递给片元着色器变量
    } 
    </script>
    

    片元着色器

    <script type="x-shader/x-fragment" id="fs">
    precision mediump float; // 精度限定
    varying vec4 v_color; //从顶点着色器接收
    void main() {
      	gl_FragColor = v_color;
    }
    </script>
    

    3D坐标系

    第1、2、3步骤前面文章已经介绍,现在我们直接进入3D的环节。3D比2D主要就是多了深度信息,用坐标系来描述就是,除xy轴外,还多了z轴。webGL的坐标系跟我们web的坐标系是不一样的,首先它原点不是在左上角而是位于中间,xyz方向也不同。

    视点和视线

    接着引入一个概念,视点,也就是定义观察者的位置,观察者能看多远,观察者的方向,直接看图吧

    上方向就是观测者从哪个方向看,(0,1,0)是正常的Y轴正方向,(1,0,0)就相当于物体向左旋90度,等于我们把头打横看物体。通过定义视点矩阵,我们看到的图形的形状会产生变化的,就和我们实际环境不同的角度位置观察同一物体是一样一样的。我们调用矩阵库中的方法,会产生出一个4X4的矩阵,具体中间的产生过程,可以看源代码。

    // (视点,观察目标点,上方向)
    setLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)
    

    投影可视空间

    只有指定了可视空间,webGL才会去绘制图形,有两种类型:
    一是盒状可视空间,由正射投影产生,它产生的图形,前后物体没有大小区别,都是一样高宽。

    调用矩阵库的setOrtho方法,产生矩阵
    // 正视投影 (left,right,bottom,top,near,far), 组成一个正方体的可视空间 
    setOrtho(left, right, bottom, top, near, far);
    

    二是四棱锥可视空间,由透视投影产生,透视投影产生的3D场景更加真实自然,它产生的图形具有近大远小的透视效果,当然性能消耗相对正射投影高一些。

    调用setPerspective方法,同理生成矩阵
    // 投影矩阵(fov可视空间底面和顶面夹角<大于0>,近裁截面宽高比,近裁截面位置<大于0>,远裁截面位置<大于0> )
    setPerspective(fovy, aspect, near, far)
    

    数据交叉存放缓冲区

    我们既可以给不同的信息分别创建单独缓冲区,也可以给不同的信息创建同一块合用的缓冲区,前者适合数据量小的情况,我们现在实现第二种情况:给不同的信息创建一块缓冲区,并交叉存放。首先用一个数组同时存放顶点信息和顶点对应的颜色信息,接着创建缓冲区后调用gl.vertexAttribPointer(),该方法有定义每个分量的个数,每一行的个数以及偏移数,当然相邻顶点数和偏移量要乘以单位字节,具体看代码的注释。

    /**
     * 混合缓冲区(包括顶点,颜色),每一行前3个是顶点信息,后3个是颜色信息
     */
    var verticeColors=new Float32Array([
      0.0,  1.0,  -2.0,  0.3,  1.0,  0.4, 
      -0.5, -1.0,  -2.0,  0.3,  1.0,  0.4,
      0.5, -1.0,  -2.0,  1.0,  0.4,  0.4, 
    
      0.0,  1.0,  -1.0,  1.0,  1.0,  0.4, 
      -0.5, -1.0,  -1.0,  1.0,  1.0,  0.4,
      0.5, -1.0,  -1.0,  1.0,  0.4,  0.4, 
    
      0.0,  1.0,   0.0,  0.4,  0.4,  1.0, 
      -0.5, -1.0,   0.0,  0.4,  0.4,  1.0,
      0.5, -1.0,   0.0,  1.0,  0.4,  0.4, 
    ]);
    // 创建缓冲区
    if(!createBuffer(verticeColors)){
      console.log('Failed to create the buffer object');
      return;
    }
    
    // 每个元素的字节
    var FSIZE = verticeColors.BYTES_PER_ELEMENT;
    // 获取顶点位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    // (地址,每个顶点分量的个数<1-4>,数据类型<整形,符点等>,是否归一化,指定相邻两个顶点间字节数<默认0>,指定缓冲区对象偏移字节数量<默认0>)
    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0);
    // Enable the assignment to a_Position variable
    gl.enableVertexAttribArray(a_Position);
    
    // 获取a_Color变量的存储地址并赋值
    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 2*FSIZE);
    gl.enableVertexAttribArray(a_Color);
    

    3D相关的其他设置

    开启隐藏面消除可以减少渲染量,提高性能,同时还可以避免顺序不一致时,后面的图形盖住前面的图形。而多边形偏移,可以避免深度很接近的两个图形产生冲突。当然每次重新渲染的时候,在清屏的同时清除深度缓冲,具体实现请看代码。

    // 开启隐藏面消除
    gl.enable(gl.DEPTH_TEST);
    //清屏|清深度缓冲
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    // 启用多边形偏移,避免深度冲突
    gl.enable(gl.POLYGON_OFFSET_FILL);
    //设置偏移量
    gl.polygonOffset(1.0, 1.0);
    

    执行动画

    来看一下我们执行动画的部分,首先设置好用于位移旋转的模型矩阵,然后依次产生视点矩阵,投影矩阵,接着把它们相乘产生出mvp矩阵,然后传入变量,最后绘图。在绘制完第一组图形的时候,将前面的mvp矩阵再左移2个单位,再绘制一遍,于是就产生出了第二组图形。具体的逻辑情况代码注释。

    var angle=0;
    // 执行动画
    (function animate(){
    		// 旋转位移 等于绕原点Y旋转
    		modelMatrix.setRotate((angle++)%360,0,1,0);
    		modelMatrix.translate(1, 0, 1);
    		// (视点,观察目标点,上方向)
    		viewMatrix.setLookAt(-0.25, -0.25, 5, 0, 0, -100, 0, 1, 0);
    		// 投影矩阵(fov可视空间底面和顶面夹角<大于0>,近裁截面宽高比,近裁截面位置<大于0>,远裁截面位置<大于0> )
    		projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
    		// 矩阵相乘
    		mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
    		// 赋值
    		gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    
    
    		//清屏|清深度缓冲
    		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    		// 启用多边形偏移,避免深度冲突
    		gl.enable(gl.POLYGON_OFFSET_FILL);
    
    		// (基本图形,第几个顶点,执行几次),修改基本图形项可以生成点,线,三角形,矩形,扇形等
    		gl.drawArrays(gl.TRIANGLES, 0, 9);
    
    		//位移后,再将前面3个三角形重新绘制
    		modelMatrix.translate(-2, 0, 0);
    		mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
    		gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    
    		//设置偏移量
    		gl.polygonOffset(1.0, 1.0);
    		gl.drawArrays(gl.TRIANGLES, 0, 9); 
    
    		requestAnimationFrame(animate);
    }());
    

    总结

    学习完3D场景后,我们又再一次领略到了线性代数中矩阵在图形学中的重要作用。3D的矩阵转换才是需要空间思维和深入理解的部分,其他地方说实话就是学习如何调用api。
    最后,献上主体的全部代码

    var	canvas=document.getElementById('canvas'),
        gl=get3DContext(canvas,true);
    
    function main() {
        if (!gl) {
          console.log('Failed to get the rendering context for WebGL');
          return;
        }
    
        if (!createShaders(gl, 'fs', 'vs')) {
          console.log('Failed to intialize shaders.');
          return;
        }
    
        /**
           * 混合缓冲区(包括顶点,颜色)
           */
        var verticeColors=new Float32Array([
          0.0,  1.0,  -2.0,  0.3,  1.0,  0.4,
          -0.5, -1.0,  -2.0,  0.3,  1.0,  0.4,
          0.5, -1.0,  -2.0,  1.0,  0.4,  0.4, 
    
          0.0,  1.0,  -1.0,  1.0,  1.0,  0.4,
          -0.5, -1.0,  -1.0,  1.0,  1.0,  0.4,
          0.5, -1.0,  -1.0,  1.0,  0.4,  0.4, 
    
          0.0,  1.0,   0.0,  0.4,  0.4,  1.0,
          -0.5, -1.0,   0.0,  0.4,  0.4,  1.0,
          0.5, -1.0,   0.0,  1.0,  0.4,  0.4, 
        ]);
        // 创建缓冲区
        if(!createBuffer(verticeColors)){
          console.log('Failed to create the buffer object');
          return;
        }
    
        // 每个元素的字节
        var FSIZE = verticeColors.BYTES_PER_ELEMENT;
        // 获取顶点位置
        var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
        // (地址,每个顶点分量的个数<1-4>,数据类型<整形,符点等>,是否归一化,指定相邻两个顶点间字节数<默认0>,指定缓冲区对象偏移字节数量<默认0>)
        gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0);
        // Enable the assignment to a_Position variable
        gl.enableVertexAttribArray(a_Position);
    
        // 获取a_Color变量的存储地址并赋值
        var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
        gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 2*FSIZE);
        gl.enableVertexAttribArray(a_Color);
    
        var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
        if(!u_MvpMatrix) { 
          console.log('Failed to get the storage location of u_MvpMatrix');
          return;
        }
        // 设置背景颜色
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        // 开启隐藏面消除
        gl.enable(gl.DEPTH_TEST);
    
        var modelMatrix = new Matrix4(); // 模型矩阵
        var viewMatrix = new Matrix4();  // 视点矩阵
        var projMatrix = new Matrix4();  // 投影矩阵
        var mvpMatrix = new Matrix4();   // 用于相乘用
        var angle=0;
        // 执行动画
        (function animate(){
          // 旋转位移 等于绕原点Y旋转
          modelMatrix.setRotate((angle++)%360,0,1,0);
          modelMatrix.translate(1, 0, 1);
          // (视点,观察目标点,上方向)
          viewMatrix.setLookAt(-0.25, -0.25, 5, 0, 0, -100, 0, 1, 0);
          // 投影矩阵(fov可视空间底面和顶面夹角<大于0>,近裁截面宽高比,近裁截面位置<大于0>,远裁截面位置<大于0> )
          projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
          // 矩阵相乘
          mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
          // 赋值
          gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    
          //清屏|清深度缓冲
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
          // 启用多边形偏移,避免深度冲突
          gl.enable(gl.POLYGON_OFFSET_FILL);
    
          // (基本图形,第几个顶点,执行几次),修改基本图形项可以生成点,线,三角形,矩形,扇形等
          gl.drawArrays(gl.TRIANGLES, 0, 9);
    
    
          //位移后,再将前面3个三角形重新绘制
          modelMatrix.translate(-2, 0, 0);
          mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
          gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    
          //设置偏移量
          gl.polygonOffset(1.0, 1.0);
          gl.drawArrays(gl.TRIANGLES, 0, 9);
    
          requestAnimationFrame(animate);
        }());
    }
    
    main();
    
  • 相关阅读:
    UVa OJ 148 Anagram checker (回文构词检测)
    UVa OJ 134 LoglanA Logical Language (Loglan逻辑语言)
    平面内两条线段的位置关系(相交)判定与交点求解
    UVa OJ 130 Roman Roulette (罗马轮盘赌)
    UVa OJ 135 No Rectangles (没有矩形)
    混合函数继承方式构造函数
    html5基础(第一天)
    js中substr,substring,indexOf,lastIndexOf,split等的用法
    css的textindent属性实现段落第一行缩进
    普通的css普通的描边字
  • 原文地址:https://www.cnblogs.com/edwardloveyou/p/7833639.html
Copyright © 2011-2022 走看看