54 WebGL实现阴影效果

2018-03-04 03:49:36 人阅读

案例查看有问题?
全屏试试!

案例解析

 实现阴影的基本思想是:太阳看不见阴影。如果在光源处放置以为观察者,其视线方向与光线一致,那么观察者也看不到阴影。他看到的每一处都在光的照射下,而那些背后的,他没有看到的物体则处在阴影中。这里,我们需要用到光源与物体之间的距离(实际上也就是物体在光源坐标系下的深度z值)来决定物体是否可见。如图所示,同一条光线上有两个点P1和P2,由于P2的z值大于P1,所以P2在阴影中。

我们需要使用两对着色器以实现阴影:[1]一对着色器用来计算光源到物体的距离,[2]另一对着色器根据[1]中计算出的距离绘制场景。使用一张纹理图像把[1]的结果传入[2]中,这张纹理图像就被称为阴影贴图(shadow map),而通过阴影贴图实现阴影的方法就被称为阴影映射(shadow mapping)。阴影映射的过程包括以下两步:

(1)将视点移动到光源的位置处,并运行[1]中的着色器。这时,那些“将要被绘出”的片元都是被光照射到的,即落在这个像素上的各个片元中最前面的。我们并不实际地绘制出片元的颜色,而是将片元的z值写入到阴影贴图中。

(2)将视点移回原来的位置,运行[2]中的着色器绘制场景。此时,我们计算出每个片元在光源坐标系(即[1]中的视点坐标系)下的坐标,并与阴影贴图中记录的z值比较,如果前者大于后者,就说明当前片元处在阴影之中,用较深暗的颜色绘制。

前两个带shadow前缀的着色器负责生产阴影贴图。我们需要将绘制目标切换到帧缓冲区对象,把视点在光源处的模型视图投影矩阵传给u_MvpMatrix变量,并运行着色器。着色器会将每个片元的z值写入帧缓冲区关联的阴影贴图中。顶点着色器的任务很简单,将顶点坐标乘以模型视图投影矩阵,而片元着色器略复杂一些,它将片元的z值写入了纹理贴图中。为此,我们使用了片元着色器的内置变量gl_FragCoord。

gl_FragCoord的内置变量是vec4类型的,用来表示片元的坐标。gl_FragCoord.x和gl_FragCoord.y是片元在屏幕上的坐标,而gl_FragCoord.z是深度值。它们是通过(gl_Position.xyz/gl_Position.w)/2.0+0.5 计算出来的,都被归一化到[0.0,1.0]区间。如果gl_FragCoord.z是0.0,则表示该片元在近裁剪面上,如果是1.0, 则表示片元在远才见面上。我们将该值写入到阴影贴图的R分量重。当然,你也可以使用其他分量。

 

[javascript] view plain copy
 
  1. "   gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\n" +  

这样,着色器就将视点位于光源时每个片元的z值存储在阴影贴图中。阴影贴图将被作为纹理对象传给另一对着色器中的u_shadowMap变量。

 

正常绘制的顶点着色器和片元着色器实现了第2步。将绘制目标切换回颜色缓冲区,把视点移回原位,开始真正地绘制场景。此时,我们需要比较片元在光源坐标系下的z值和阴影贴图中对应的值来决定当前片元是否处在阴影之中。u_MvpMatrix变量是视点在原处的模型视图投影矩阵,而u_MvpMatrixFromLight变量是第1步中视点位于光源处时的模型视图投影矩阵。顶点着色器计算每个顶点在光源坐标系(即第1步中的视图坐标系)中的坐标v_PositionFromLight(等价于第1不重的gl_Position),并传入片元着色器。

片元着色器的任务是根据片元在光源坐标系中的坐标v_PositionFromLight 计算出可以与阴影贴图相比较的z值。前面说过,阴影贴图中的z值是通过(gl_Position.z/gl_Position.w)/2.0+0.5 计算出来的,为使这里的结果能够与之比较,我们也需要通过(v_PositionFromLight.z / v_PositionFromLight.w) / 2.0 + 0.5 来进行归一化。然后,为了将z值与阴影贴图中的相应纹素值比较,需要通过 v_PositionFromLight 的 x 和 y 坐标从阴影贴图中获取纹素。但我们知道,WebGL中的x和y坐标都是在[-1.0, 1.0]区间中的, 而纹理坐标s和t是在[0.0, 1.0]的区间中的。所以我们还需要将x和y坐标转化为s和t坐标:

 

[cpp] view plain copy
 
  1. s = (v_PositionFromLight.x / v_PositionFromLight.w) / 2.0 + 0.5  
  2. t = (v_PositionFromLight.y / v_PositionFromLight.w) / 2.0 + 0.5  

其归一化的方式与z值的归一化的方式一致。所以我们在一行代码中完成xyz的归一化(74行),计算出shadowCoord变量,其x和y分量为当前片元在阴影贴图中对应的纹素的纹理坐标,而z分量表示当前片元在光源坐标系中的归一化z值,可与阴影贴图中的纹素值比较。

 

 

[cpp] view plain copy
 
  1. "void main(){\n" +  
  2. "   vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n" +  
  3. "   vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\n" +  
  4. "   float depth = rgbaDepth.r;\n" +  
  5. "   float visibility = (shadowCoord.z > depth + 0.005) ? 0.5 : 1.0;\n" +  
  6. "   gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\n" +  
  7. "}\n";  

然后,我们通过shadowCoord.xy从阴影贴图中抽取出纹素(75,76行),你应该还记得,这并非单纯的抽取纹理的纹素,而涉及内插过程。由于之前z值被写在了R分量中(48行),所以这里也只需提取R分量,并保存在depth变量中。接着,我们通过比较shadowCoord.z和depth来决定片元是否是在阴影中,如果前者比较大,说明当前片元在阴影中,就为visibility变量赋值为0.7,否则就赋为1.0.改变量参与了计算片元的最终颜色的过程,如果其为0.7,那么片元就会深暗一些,以表示在阴影中。

 

个人心得:

上面是原搬的书中的解释,但是对实现的原理的解释过于原理,而且里面掺杂着相关变量,变得更加难懂。博主本来上一节的在帧缓冲区绘制纹理就看得不怎么懂,没想到在这一节还竟然用的这种方法,所以,个人感觉在这里解释一波比较好。

(1)其实WebGL绘制模型就和作画一样,在2D界面上展现3D的效果。与其说是3D模型,不如说有3D效果的2D图。动画,顾名思义,就是动起来的画,切换的速度快了,就感觉不出来是2D平面的效果。

(2)如果能理解(1)所说的东西了,下一步就好理解了。所以,这一节的两个着色器绘制了两张图片而已,fbo是啥,就是能够不直接在画布上绘制,而是直接凭空绘制完成,还放到纹理缓存区。它绘制出来了啥,就是绘制出来了所有能够被光照射到的地方。

(3)绘制第一对着色器保存了绘制的每个点与光源的距离,这个是干嘛用的,如果再绘制别的地方的时候,绘制的点比这个距离大的话,就肯定处于阴影当中了。(教程中是把这个z值赋值给了RGBA中的R变量,但是如果纹理设置的分辨率太低的话,图形的边缘也会出现显示出是贴图的效果。这是因为片元着色器获取纹理时的一个内插的效果,所以我改成了A变量,效果比之前好了很多。)

(4)第二对着色器绘制时干了啥,首先就是计算出了绘制的当前点在第一对着色器中时这个点的xyz的值,就是计算出来这个点距离光源的距离z,然后和放到纹理中的这个z值进行对比。如果距离比纹理中的能证明处于亮面的距离远的话,就能判断出来是处在阴影当中,就让RGBA乘以0.7,比不处于亮面的颜色显得黑,就做出来了阴影。

 

马赫带:


 

在77行中,我们对比的时候给depth添加了0.005的偏移量。如果将这个偏移量删除掉的话,再运行程序,就会发现会出现和上图一样的马赫带(Mach band)。

偏移量0.005的作用是消除马赫带。出现马赫带的原因虽然有点复杂,但三维图形学中经常会出现类似的问题,这个问题值得弄明白。我们知道,纹理图像RGBA分量中,每个分量都是8位,那么存储在阴影贴图中的z值精度也只有8位,而与阴影贴图进行比较的值shadowCoord.z是float类型的,有16位。比如说z值是0.1234567,8位的浮点数的精度是1/256,也就是0.00390625。根据:0.1234567/(1/256) = 31.6049152 在8位精度下,0.1234567实际上是31个1/256,即0.12109375 。同理,在16位精度下,0.1234567实际上是8090个1/65536,即0.12344360 。前者比后者小。这意味着,即使是完全相同的坐标,在阴影贴图中的z值可能会比shadowCoord.z中的值小,这就造成了矩形平面的某些区域被误认为是阴影了。我们再进行比较时,为阴影贴图添加了一个偏移量0.005,就可以避免产生马赫带。注意,偏移量应当略大于精度,比如这里的0.005就略大于1/256 。

 

提高精度:

虽然我们已经成功地实现了场景中的阴影效果,但这仅仅使用与光源距离物体很近的情况。如果我们将光源拿远一些,比如将其y坐标改为40:

 

[javascript] view plain copy
 
  1. 86行  
  2.     //灯光的位置  
  3.     var light_x = 0.0;  
  4.     var light_y = 40.0;  
  5.     var light_z = 2.0;  

再次运行,就会发现阴影就会消失掉。

 

阴影消失的原因是,随着光源与照射物体间的距离变远,gl_FragCoord的值也会增大,当光源足够远时,gl_FragCoord.z就大到无法存储在只有8位的R分量中了。简单的解决方法是,使用阴影贴图中的R、G、B、A这四个分量,用4个字节共32位来存储z值。实际上,已经有列行的方法来完成这项任务了,让我们来看看示例程序修改:
将设置阴影贴图的片元着色器修改为:

 

[javascript] view plain copy
 
  1. var shadowFragmentShaderSource = "" +  
  2.     "#ifdef GL_ES\n" +  
  3.     "precision mediump float;\n" +  
  4.     "#endif\n" +  
  5.     "void main(){\n" +  
  6.     "   const vec4 bitShift = vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0);\n" +  
  7.     "   const vec4 bitMask = vec4(1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0);\n" +  
  8.     "   vec4 rgbaDepth = fract(gl_FragCoord.z * bitShift);\n" +  
  9.     "   rgbaDepth -= rgbaDepth.gbaa * bitMask;\n" +  
  10.     "   gl_FragColor = rgbaDepth;\n" + //将灯源视点下的每个顶点的深度值存入绘制的颜色内  
  11.     "}\n";  

 

 

将正常绘制的片元着色器修改为:

 

[javascript] view plain copy
 
  1. var fragmentShaderSource = "" +  
  2.     "#ifdef GL_ES\n" +  
  3.     "precision mediump float;\n" +  
  4.     "#endif\n" +  
  5.     "uniform sampler2D u_ShadowMap;\n" + //纹理的存储变量  
  6.     "varying vec4 v_PositionFromLight;\n" + //从顶点着色器传过来的基于光源的顶点坐标  
  7.     "varying vec4 v_Color;\n" + //顶点的颜色  
  8.         //从rgba这4个分量中重新计算出z值的函数  
  9.     "float unpackDepth(const in vec4 rgbaDepth){\n" +  
  10.     "   const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0*256.0), 1.0/(256.0*256.0*256.0));\n" +  
  11.     "   float depth = dot(rgbaDepth, bitShift);\n" +  
  12.     "   return depth;\n" +  
  13.     "}\n" +  
  14.     "void main(){\n" +  
  15.     "   vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n" +  
  16.     "   vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\n" +  
  17.     "   float depth = unpackDepth(rgbaDepth);\n" + //重新计算出z值  
  18.     "   float visibility = (shadowCoord.z > depth + 0.0015) ? 0.5 : 1.0;\n" +  
  19.     "   gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\n" +  
  20.     "}\n";  


片元着色器shadowFragmentShaderSource将gl_FragCoord.z拆为了4个字节R、G、B、A 。因为1个字节的精度是1/256,所以我们将大于1/256的部分存储在R分量中,将1/256到1/(256*256)的部分存储在G分量中,将1/(256*256)到1/(256*256*256)存储在B分量中,并将小于1/(256*256*256)的部分存储在A分量中。我们使用内置函数fract()来计算上述分量的值,改函数舍弃参数的整数部分,返回小数部分。此外,由于rgbaDepth是vec4类型的,精度高于8位,还需要将多余的部分砍掉。最后,将rbgaDepth赋值给gl_FragColor,这样就将z值保存在阴影贴图的4个分量中,获得了更高的精度。

 

片元着色器fragmentShaderSource调用unpackDepth()函数获取z值。该函数是自定义函数,该函数根据如下公式从RGBA分量中还原出高精度的原始z值。此外,由于该公式与点积公式的形式一样,所以我们结果了dot()函数完成了计算。

这样,我们还原了原始的z值,并将它与shadowCoord.z相比较。我们仍然添加了一个偏移量0.0015来消除马赫带,因为此时z值得精度已经提高到了float,在medium精度下,精度为2-10=0.000976563,这样就又能够正确的绘制出阴影了

案例源代码


                        
 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Title</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
 
        #canvas {
            margin: 0;
            display: block;
        }
    </style>
</head>
<body onload="main()">
<canvas id="canvas" height="800" width="800"></canvas>
</body>
<script src="/lib/webgl-utils.js"></script>
<script src="/lib/webgl-debug.js"></script>
<script src="/lib/cuon-utils.js"></script>
<script src="/lib/cuon-matrix.js"></script>
<script>
    //设置WebGL全屏显示
    var canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
 
    //设置阴影贴图顶点着色器
    var shadowVertexShaderSource = "" +
        "attribute vec4 a_Position;\n" +
        "uniform mat4 u_MvpMatrix;\n" +
        "void main(){\n" +
        "   gl_Position = u_MvpMatrix * a_Position;\n" + //计算出在灯源视点下各个坐标的位置
        "}\n";
 
    //设置阴影贴图的片元着色器
    var shadowFragmentShaderSource = "" +
        "#ifdef GL_ES\n" +
        "precision mediump float;\n" +
        "#endif\n" +
        "void main(){\n" +
        "   gl_FragColor = vec4( 0.0, 0.0, 0.0,gl_FragCoord.z);\n" + //将灯源视点下的每个顶点的深度值存入绘制的颜色内
        "}\n";
 
    //正常绘制的顶点着色器
    var vertexShaderSource = "" +
        "attribute vec4 a_Position;\n" +
        "attribute vec4 a_Color;\n" +
        "uniform mat4 u_MvpMatrix;\n" + //顶点的模型投影矩阵
        "uniform mat4 u_MvpMatrixFromLight;\n" + //顶点基于光源的模型投影矩阵
        "varying vec4 v_PositionFromLight;\n" + //将基于光源的顶点位置传递给片元着色器
        "varying vec4 v_Color;\n" + //将颜色传递给片元着色器
        "void main(){\n" +
        "   gl_Position = u_MvpMatrix * a_Position;\n" + //计算并设置顶点的位置
        "   v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\n" + //计算基于光源的顶点位置
        "   v_Color = a_Color;\n" +
        "}\n";
 
    //正常绘制的片元着色器
    var fragmentShaderSource = "" +
        "#ifdef GL_ES\n" +
        "precision mediump float;\n" +
        "#endif\n" +
        "uniform sampler2D u_ShadowMap;\n" + //纹理的存储变量
        "varying vec4 v_PositionFromLight;\n" + //从顶点着色器传过来的基于光源的顶点坐标
        "varying vec4 v_Color;\n" + //顶点的颜色
        "void main(){\n" +
        "   vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n" +
        "   vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\n" +
        "   float depth = rgbaDepth.a;\n" +
        "   float visibility = (shadowCoord.z > depth + 0.005) ? 0.5 : 1.0;\n" +
        "   gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\n" +
        "}\n";
 
    //生成的纹理的分辨率,纹理必须是标准的尺寸 256*256 1024*1024  2048*2048
    var resolution = 256;
    var offset_width = resolution;
    var offset_height = resolution;
 
    //灯光的位置
    var light_x = 0.0;
    var light_y = 7.0;
    var light_z = 2.0;
 
    function main() {
        var canvas = document.getElementById("canvas");
 
        var gl = getWebGLContext(canvas);
 
        if(!gl){
            console.log("无法获取WebGL的上下文");
            return;
        }
 
        //初始化阴影着色器,并获得阴影程序对象,相关变量的存储位置
        var shadowProgram = createProgram(gl, shadowVertexShaderSource, shadowFragmentShaderSource);
        shadowProgram.a_Position = gl.getAttribLocation(shadowProgram, "a_Position");
        shadowProgram.u_MvpMatrix = gl.getUniformLocation(shadowProgram, "u_MvpMatrix");
        if(shadowProgram.a_Position < 0 || !shadowProgram.u_MvpMatrix ){
            console.log("无法获取到阴影着色器的相关变量");
            return;
        }
 
        //初始化正常绘制着色器,获取到程序对象并获取相关变量的存储位置
        var normalProgram = createProgram(gl, vertexShaderSource, fragmentShaderSource);
        normalProgram.a_Position = gl.getAttribLocation(normalProgram, "a_Position");
        normalProgram.a_Color = gl.getAttribLocation(normalProgram, "a_Color");
        normalProgram.u_MvpMatrix = gl.getUniformLocation(normalProgram, "u_MvpMatrix");
        normalProgram.u_MvpMatrixFromLight = gl.getUniformLocation(normalProgram, "u_MvpMatrixFromLight");
        normalProgram.u_ShadowMap = gl.getUniformLocation(normalProgram, "u_ShadowMap");
        if(normalProgram.a_Position < 0 || normalProgram.a_Color < 0 || !normalProgram.u_MvpMatrix || !normalProgram.u_MvpMatrixFromLight || !normalProgram.u_ShadowMap){
            console.log("无法获取到正常绘制着色器的相关变量");
            return;
        }
 
        //设置相关数据,并存入缓冲区内
        var triangle = initVertexBuffersForTriangle(gl);
        var plane = initVertexBuffersForPlane(gl);
        if(!triangle || !plane){
            console.log("无法设置相关顶点的信息");
            return;
        }
 
        //设置帧缓冲区对象
        var fbo = initFramebufferObject(gl);
        if(!fbo){
            console.log("无法设置帧缓冲区对象");
            return;
        }
 
        //开启0号纹理缓冲区并绑定帧缓冲区对象的纹理
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
 
        //设置背景设并开启隐藏面消除功能
        gl.clearColor(0.0,0.0,0.0,1.0);
        gl.enable(gl.DEPTH_TEST);
 
        //声明一个光源的变换矩阵
        var viewProjectMatrixFromLight = new Matrix4();
        viewProjectMatrixFromLight.setPerspective(70.0, offset_width/offset_height, 1.0, 100.0);
        viewProjectMatrixFromLight.lookAt(light_x, light_y, light_z,0.0,0.0,0.0,0.0,1.0,0.0);
 
        //为常规绘图准备视图投影矩阵
        var viewProjectMatrix = new Matrix4();
        viewProjectMatrix.setPerspective(45.0, canvas.width/canvas.height, 1.0, 100.0);
        viewProjectMatrix.lookAt(0.0,7.0,9.0,0.0,0.0,0.0,0.0,1.0,0.0);
 
        var currentAngle = 0.0; //声明当前旋转角度的变量
        var mvpMatrixFromLight_t = new Matrix4(); //光源(三角形)的模型投影矩阵
        var mvpMatrixFromLight_p = new Matrix4(); //光源(平面)的模型投影矩阵
 
        (function tick() {
            currentAngle = animate(currentAngle);
 
            //切换绘制场景为帧缓冲区
            gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
            gl.viewport(0.0,0.0,offset_height,offset_height);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
            gl.useProgram(shadowProgram); //使用阴影程序对象绘制阴影纹理
 
            //绘制三角形和平面(用于生成阴影贴图)
            drawTriangle(gl, shadowProgram, triangle, currentAngle, viewProjectMatrixFromLight);
            mvpMatrixFromLight_t.set(g_mvpMatrix); //稍后使用
            drawPlane(gl, shadowProgram, plane, viewProjectMatrixFromLight);
            mvpMatrixFromLight_p.set(g_mvpMatrix); //稍后使用
 
            //解除帧缓冲区的绑定,绘制正常颜色缓冲区
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            gl.viewport(0.0, 0.0, canvas.width, canvas.height);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
            //切换为正常的程序对象并绘制
            gl.useProgram(normalProgram);
            gl.uniform1i(normalProgram.u_ShadowMap, 0.0);
 
            //绘制三角形和平面(正常绘制的图形)
            gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.elements);
            drawTriangle(gl, normalProgram, triangle, currentAngle, viewProjectMatrix);
            gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.elements);
            drawPlane(gl, normalProgram, plane, viewProjectMatrix);
 
            requestAnimationFrame(tick);
        })();
    }
 
    //声明坐标转换矩阵
    var g_modelMatrix = new Matrix4();
    var g_mvpMatrix = new Matrix4();
 
    function drawTriangle(gl,program,triangle,angle,viewProjectMatrix) {
        //设置三角形图形的旋转角度,并绘制图形
        g_modelMatrix.setRotate(angle, 0.0, 1.0, 0.0);
        draw(gl, program, triangle, viewProjectMatrix);
    }
 
    function drawPlane(gl, program, plane, viewProjectMatrix) {
        //设置平面图形的旋转角度并绘制
        g_modelMatrix.setRotate(-45.0, 0.0, 1.0, 1.0);
        draw(gl, program, plane, viewProjectMatrix);
    }
 
    function draw(gl, program, obj, viewProjectMatrix) {
        initAttributeVariable(gl, program.a_Position, obj.vertexBuffer);
        //判断程序对象上面是否设置了a_Color值,如果有,就设置颜色缓冲区
        if(program.a_Color != undefined){
            initAttributeVariable(gl, program.a_Color, obj.colorBuffer);
        }
 
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.indexBuffer);
 
        //设置模板视图投影矩阵,并赋值给u_MvpMatrix
        g_mvpMatrix.set(viewProjectMatrix);
        g_mvpMatrix.multiply(g_modelMatrix);
        gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);
 
        gl.drawElements(gl.TRIANGLES, obj.numIndices, gl.UNSIGNED_BYTE, 0);
    }
 
    function initAttributeVariable(gl, a_attribute, buffer) {
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
        gl.enableVertexAttribArray(a_attribute);
    }
 
    var angle_step = 30;
    var last = +new Date();
    function animate(angle) {
        var now = +new Date();
        var elapsed = now - last;
        last = now;
        var newAngle = angle + (angle_step*elapsed)/1000.0;
        return newAngle%360;
    }
 
    function initFramebufferObject(gl) {
        var framebuffer, texture, depthBuffer;
 
        //定义错误函数
        function error() {
            if(framebuffer) gl.deleteFramebuffer(framebuffer);
            if(texture) gl.deleteFramebuffer(texture);
            if(depthBuffer) gl.deleteFramebuffer(depthBuffer);
            return null;
        }
 
        //创建帧缓冲区对象
        framebuffer = gl.createFramebuffer();
        if(!framebuffer){
            console.log("无法创建帧缓冲区对象");
            return error();
        }
 
        //创建纹理对象并设置其尺寸和参数
        texture = gl.createTexture();
        if(!texture){
            console.log("无法创建纹理对象");
            return error();
        }
 
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, offset_width, offset_height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        framebuffer.texture = texture;//将纹理对象存入framebuffer
 
        //创建渲染缓冲区对象并设置其尺寸和参数
        depthBuffer = gl.createRenderbuffer();
        if(!depthBuffer){
            console.log("无法创建渲染缓冲区对象");
            return error();
        }
 
        gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
        gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, offset_width, offset_height);
 
        //将纹理和渲染缓冲区对象关联到帧缓冲区对象上
        gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER,depthBuffer);
 
        //检查帧缓冲区对象是否被正确设置
        var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
        if(gl.FRAMEBUFFER_COMPLETE !== e){
            console.log("渲染缓冲区设置错误"+e.toString());
            return error();
        }
 
        //取消当前的focus对象
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.bindTexture(gl.TEXTURE_2D, null);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
 
        return framebuffer;
    }
 
    function initVertexBuffersForPlane(gl) {
        // 创建一个面
        //  v1------v0
        //  |        |
        //  |        |
        //  |        |
        //  v2------v3
 
        // 顶点的坐标
        var vertices = new Float32Array([
            3.0, -1.7, 2.5, -3.0, -1.7, 2.5, -3.0, -1.7, -2.5, 3.0, -1.7, -2.5    // v0-v1-v2-v3
        ]);
 
        // 颜色的坐标
        var colors = new Float32Array([
            1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
        ]);
 
        // 顶点的索引
        var indices = new Uint8Array([0, 1, 2,   0, 2, 3]);
 
        //将顶点的信息写入缓冲区对象
        var obj = {};
        obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
        obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
        obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
        if(!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) return null;
 
        obj.numIndices = indices.length;
 
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
 
        return obj;
    }
 
    function initVertexBuffersForTriangle(gl) {
        // Create a triangle
        //       v2
        //      / |
        //     /  |
        //    /   |
        //  v0----v1
 
        // 顶点的坐标
        var vertices = new Float32Array([-0.8, 3.5, 0.0, 0.8, 3.5, 0.0, 0.0, 3.5, 1.8]);
        // 颜色的坐标
        var colors = new Float32Array([1.0, 0.5, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 0.0]);
        // 顶点的索引
        var indices = new Uint8Array([0, 1, 2]);
 
        //创建一个对象保存数据
        var obj = {};
 
        //将顶点信息写入缓冲区对象
        obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
        obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
        obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
        if(!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) return null;
 
        obj.numIndices = indices.length;
 
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
 
        return obj;
    }
 
    function initArrayBufferForLaterUse(gl, data, num, type) {
        var buffer = gl.createBuffer();
        if(!buffer){
            console.log("无法创建缓冲区对象");
            return null;
        }
 
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
 
        buffer.num = num;
        buffer.type = type;
 
        return buffer;
    }
 
    function initElementArrayBufferForLaterUse(gl, data, type) {
        var buffer = gl.createBuffer();
        if(!buffer){
            console.log("无法创建着色器");
            return null;
        }
 
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
 
        buffer.type = type;
 
        return buffer;
    }
</script>
</html>
展开内容

联系我们

一个人的力量不如两个人的,两个人的力量不如一群人的。欢迎加入大家庭一起共同学习,共同进步。

查看更多