【笔记】《WebGL编程指南》学习-第7章进入三维世界(4-前后关系)

在真实世界中,如果你讲两个盒子一前一后放在桌子上,如下图,下面的盒子会挡住部分后面的盒子。

这里写图片描述

看一下示例程序 PerspectiveView 的效果
这里写图片描述
绿色三角形的一部分被黄色和蓝色三角形挡住了。看上去似乎是 WebGL 专为三维图形学设计,能够自动分析处三维对象的远近,并正确处理遮挡关系。

遗憾的是,事实没有想想的那么美好。默认情况下,WebGL 为了加速绘图操作,是按照顶点在缓冲区中的顺序来处理它们的。前面所有的示例程序我们都是故意先定义远的物体,后定义近的物体,从而产生正确的效果。

比如在 PerspectiveView_mvp.js中,我们按照如下顺序定义了三角形的顶点和颜色数据:

 var verticesColors = new Float32Array([
        //右侧的3个三角形
        0.0, 1.0, -4.0, 0.4, 1.0, 0.4,              //绿色三角形在最后面
        -0.5, -1.0, -4.0, 0.4, 1.0, 0.4,
        0.5, -1.0, -4.0, 1.0, 0.4, 0.4,

        0.0, 1.0, -2.0, 1.0, 0.4, 0.4,              //黄色三角形在中间
        -0.5, -1.0, -2.0, 1.0, 1.0, 0.4,
        0.5, -1.0, -2.0, 1.0, 1.0, 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
    ]);

WebGL 按照顶点在缓冲区中的顺序来进行绘制。后绘制的图形将覆盖已经绘制好的图形,这样就恰好产生了近处的三角形挡住远处的三角形的效果。

为了验证这一点,我们将缓冲区中三角形顶点数据的顺序调整一下,把近处的蓝色三角形定义在前面,然后是中间的黄色三角形,最后是远处的绿色三角形,如下所示:

var verticesColors = new Float32Array([
        //右侧的3个三角形
        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,

        0.0, 1.0, -2.0, 1.0, 0.4, 0.4,              //黄色三角形在中间
        -0.5, -1.0, -2.0, 1.0, 1.0, 0.4,
        0.5, -1.0, -2.0, 1.0, 1.0, 0.4,

        0.0, 1.0, -4.0, 0.4, 1.0, 0.4,              //绿色三角形在最后面
        -0.5, -1.0, -4.0, 0.4, 1.0, 0.4,
        0.5, -1.0, -4.0, 1.0, 0.4, 0.4
    ]);

运行程序,你就会发现本该出现在最远处的绿色三角形,不自然地挡住了近处的黄色和蓝色三角形。

这里写图片描述

WebGL 在默认情况下会按照缓冲区中的顺序绘制图形,而且后绘制的图形覆盖先绘制的图形,因为这样做很高效。如果场景中的对象不发生运动,观察者的状态也是唯一的,那么这种做法没有问题。但是如果,比如你希望不断移动视点,从不同的角度看物体,那么你不可能事先决定对象出现的顺序。

隐藏面消除

为了解决这个问题,WebGL 提供了隐藏面消除功能。这个功能会帮助我们消除那些被遮挡的表面,你可以放心地绘制场景而不必顾及各物体在缓冲区中的顺序,因为那些远处的物体会自动被近处的物体挡住,不会被绘制出来。这个功能已经内嵌在 WebGL 中了,你只需要简单地开启这个功能就可以了。

开启隐藏面消除功能,需要遵循以下两步:

  1. 开启隐藏面消除功能。
  2. 在绘制之前,清楚深度缓冲区。

第1步所用的 gl.enable()函数实际上可以开启 WebGL 中的多种功能,其规范如下:

这里写图片描述

第2步,使用 gl.clear()方法清楚深度缓冲区。深度缓冲区是一个中间对象,其作用就是帮助WebGL 进行隐藏面消除。WebGL 在颜色缓冲区中绘制几何图形,绘制完成后将缓冲区显示到 canvas 上。如果要将隐藏面消除,那就必须知道每个几个图形的深度信息,而深度缓冲区就是用来存储深度信息的。由于深度方向通常是Z轴方向,所以有时候我们也称为Z缓冲区。

这里写图片描述

在绘制任意一帧之前,都必须清楚深度缓冲区,以消除绘制上一帧时在其中留下的痕迹。如果不这样做,就会出现错误的记过。我们调用 gl.clear()函数,并传入参数 gl.DEPTH_BUFFER_BIT 清楚深度缓冲区。

当然,还需要清楚颜色缓冲区。用按位或符号 | 链接这两个参数,并作为参数传入函数中。

类似的,同时清除任意两个缓冲区时,都可以使用按位或符号。

与 gl.enble()函数对应的还有 gl.disable()函数,其规范如下所示,前者启用某个功能,后者则禁用之。

这里写图片描述

示例程序

示例程序名为 DepthBuffer.js,它在 PerspectiveView_mvpMatrix.js 的基础上,加入了隐藏面消除的相关代码。注意,缓冲区中顶点的顺没有改变,程序一次按照近处,中间,远处的顺序绘制三角形。程序运行的结果和 PerspectiveView_mvpMatrix 完全一样。

DepthBuffer.js

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'attribute vec4 a_Color;'+
    'uniform mat4 u_MvpMatrix;'+
    'varying vec4 v_Color;'+
    'void main(){'+
    'gl_Position = u_MvpMatrix * a_Position;'+
    'v_Color = a_Color;'+
    '}';

//片元着色器程序
var FSHADER_SOURCE=
    '#ifdef GL_ES\n' +
    'precision mediump float;\n' +
    '#endif\n' +
    'varying vec4 v_Color;' +
    'void main() {'+
    'gl_FragColor = v_Color;'+
    '}';

function main() {
    //获取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //获取WebGL绘图上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化着色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }

    //指定清空<canvas>颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    //开启隐藏面消除
    gl.enable(gl.DEPTH_TEST);

    //获取 u_ViewMatrix 、u_ModelMatrix和 u_ProjMatrix 变量的存储位置
    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    if(u_MvpMatrix < 0){
        console.log("Failed to get the storage location of u_MvpMatrix");
        return;
    }

    var modelMatrix = new Matrix4(); //模型矩阵
    var viewMatrix = new Matrix4(); //视图矩阵
    var projMatrix = new Matrix4(); //投影矩阵
    var mvpMatrix = new Matrix4();

    modelMatrix.setTranslate(0.75, 0, 0);
    viewMatrix.setLookAt(0, 0, 5, 0, 0, -100, 0, 1, 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.drawArrays(gl.TRIANGLES, 0, n);

    //为另一侧的三角形重新计算模型矩阵
    modelMatrix.setTranslate(-0.75, 0, 0);
    mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);

    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

    gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
    var verticesColors = new Float32Array([
        //右侧的3个三角形
        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,

        0.0, 1.0, -2.0, 1.0, 0.4, 0.4,              //黄色三角形在中间
        -0.5, -1.0, -2.0, 1.0, 1.0, 0.4,
        0.5, -1.0, -2.0, 1.0, 1.0, 0.4,

        0.0, 1.0, -4.0, 0.4, 1.0, 0.4,              //绿色三角形在最后面
        -0.5, -1.0, -4.0, 0.4, 1.0, 0.4,
        0.5, -1.0, -4.0, 1.0, 0.4, 0.4
    ]);
    var n=9; //点的个数

    //创建缓冲区对象
    var vertexColorBuffer = gl.createBuffer();
    if(!vertexColorBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //将缓冲区对象保存到目标上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);

    //向缓存对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

    var FSIZE = verticesColors.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE*6, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    if(a_Color < 0){
        console.log("Failed to get the storage location of a_Color");
        return -1;
    }

    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE*6, FSIZE*3);
    gl.enableVertexAttribArray(a_Color);

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return n;
}

运行 DepthBuffer,可见程序成功地消除了因此那个面,位于近处的三角形挡住了远处的三角形。该程序证明了不管视点位于何处,隐藏面都能够被消除。在任何三维场景中,你都应该开启隐藏面消除,并在适当的时刻清楚深度缓冲区。

应当注意的是,隐藏面消除的前提是正确设置可视空间,否则就可能产生错误的结果。不管是盒装的正射投影空间,这时金字塔状的透视投影空间,你必须使用一个。

深度冲突

隐藏面消除是 WebGL 的一项复杂而又强大的特性,在绝大多数情况下,它都能很好地完成任务。然而,当几何图形或物体的两个表面极为接近时,就会出现新的问题,使得表面看上去斑斑驳驳的,如下图所示。这种现象被称为深度冲突。现在,我们来画两个Z值完全一样的三角形。

这里写图片描述

之所以会产生深度冲突,是因为两个表面过于接近,深度缓冲区有限的精度已经不能区分哪个在前,哪个在后了。严格地说,如果创建三维模型阶段就对顶点的深度值加以注意,是能够避免深度冲突的。但是,当场景中有多个运动者的物体时,实现这一点几乎是不可能的。

WebGL 提供一种称为多边形偏移的的机制来解决这个问题。该机制将自动在Z值加上一个偏移量,偏移量的值由物体表面相对与观察者视线的角度来确定。启用该机制只需两个代码。

  1. 启用多边形偏移
  2. 在绘制之前指定用来计算偏移量的参数,

    第1步调用了 gl.enable()启用多边形偏移,注意启用隐藏面消除用到的也是该函数,只不过两者传入了不同的参数。第2步中的函数 gl.polygonOffset()的规范如下。

这里写图片描述

Zfighting.js

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'attribute vec4 a_Color;'+
    'uniform mat4 u_ViewProjMatrix;'+
    'varying vec4 v_Color;'+
    'void main(){'+
    'gl_Position = u_ViewProjMatrix * a_Position;'+
    'v_Color = a_Color;'+
    '}';

//片元着色器程序
var FSHADER_SOURCE=
    '#ifdef GL_ES\n' +
    'precision mediump float;\n' +
    '#endif\n' +
    'varying vec4 v_Color;' +
    'void main() {'+
    'gl_FragColor = v_Color;'+
    '}';

function main() {
    //获取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //获取WebGL绘图上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化着色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }

    //指定清空<canvas>颜色
    gl.clearColor(0,0,0,1);
    //开启隐藏面消除
    gl.enable(gl.DEPTH_TEST);

    //获取 u_ViewMatrix 、u_ModelMatrix和 u_ProjMatrix 变量的存储位置
    var u_ViewProjMatrix = gl.getUniformLocation(gl.program, 'u_ViewProjMatrix');
    if(u_ViewProjMatrix < 0){
        console.log("Failed to get the storage location of u_ViewProjMatrix");
        return;
    }

    var viewProjMatrix = new Matrix4();

    viewProjMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
    viewProjMatrix.lookAt(3.06, 2.5, 10.0, 0, 0, -2, 0, 1, 0);

    gl.uniformMatrix4fv(u_ViewProjMatrix, false, viewProjMatrix.elements);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    //gl.enable(gl.POLYGON_OFFSET_FILL);

    gl.drawArrays(gl.TRIANGLES, 0, n/2);
    gl.polygonOffset(1.0, 1.0);
    gl.drawArrays(gl.TRIANGLES, n/2, n/2);
}

function initVertexBuffers(gl) {
    var verticesColors = new Float32Array([
        //右侧的3个三角形
        0.0, 2.5, -5.0, 0.4,  1.0,  0.4,                 //绿色三角形
        -2.5, -2.5, -5.0, 0.4,  1.0,  0.4,
        2.5, -2.5, -5.0, 1.0,  0.4,  0.4,

        0.0, 3.0, -5.0, 1.0,  0.4,  0.4,            //黄色三角形
        -3.0, -3.0, -5.0, 1.0,  0.4,  0.4,
        3.0, -3.0, -5.0, 1.0,  1.0,  0.4
    ]);
    var n=6; //点的个数

    //创建缓冲区对象
    var vertexColorBuffer = gl.createBuffer();
    if(!vertexColorBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //将缓冲区对象保存到目标上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);

    //向缓存对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

    var FSIZE = verticesColors.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE*6, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    if(a_Color < 0){
        console.log("Failed to get the storage location of a_Color");
        return -1;
    }

    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE*6, FSIZE*3);
    gl.enableVertexAttribArray(a_Color);

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return n;
}

可见,所有顶点的Z 坐标值都一样,为 -0.5,但是却没有出现深度冲突现象。

在代码的其余部分,我们开启了多边形偏移机制,然后绘制了一个绿色的三角形和一个黄色的三角形。两个三角形的数据存储在同一个缓冲区中,所以需要格外注意 gl.drawArryas()的第2个和第3个参数。第2个参数表示开始绘制的顶点的编号,而第3个参数表示该次操作绘制的顶点个数。所以,我们先画了一个绿色三角形,然后通过 gl.polygonOffset()设置了多边形偏移参数,使之后的绘制收到多边形偏移机制影响,再画了一个黄色三角形。运行程序,你将看到两个三角形没有发生深度冲突。

这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值