• WebGL 雾化


    目录

    前言 

    如何实现雾化

    线性雾化公式

    雾化因子关系图

    根据雾化因子计算片元颜色公式

    示例程序(Fog.js) 

    代码详解​编辑

    详解如何计算雾化因子(clamp())

    详解如何计算最终片元颜色(根据雾化因子计算片元颜色公式  mix())

    示例效果

    示例程序(使用w分量代替顶点与视点的距离 Fog_w.js)


    前言 

    三维图形学中,术语雾化(fog)用来描述远处的物体看上去较为模糊的现象。在现实中,任何介质中的物体都可能表现出雾化现象,比如水下的物体。本文的示例程序Fog将实现一个雾化的场景,场景中有一个立方体。程序的效果如下图所示,用户可以使用上下方向键调节雾的浓度。运行示例程序,试试上下方向键,看看雾的浓度改变的效果。

     

    如何实现雾化

    实现雾化的方式有很多种,这里使用最简单的一种: 线性雾化(linear fog)。在线性雾化中,某一点的雾化程度取决于它与视点之间的距离,距离越远雾化程度越高。线性雾化有起点和终点,起点表示开始雾化之处,终点表示完全雾化之处,两点之间某一点的雾化程度与该点与视点的距离呈线性关系。注意,比终点更远的点完全雾化了,即完全看不见了。某一点雾化的程度可以被定义为雾化因子(fog factor),并在线性雾化公式中被计算出来,如下式所示。

    线性雾化公式

    <雾化因子>=(<终点>-<当前点与视点间的距离>)/ (<终点>-<起点>)

    这里 <起点>≤<当前点与视点间的距离>≤<终点> 

     如果雾化因子为1.0,表示该点完全没有被雾化,可以很清晰地看到此处的物体。如果其为0.0,就表示该点完全雾化了,此处的物体完全看不见,如下图所示。在视线上,起点之前的点的雾化因子为1.0,终点之后的点的雾化因子为0.0。

    雾化因子关系图

    在片元着色器中根据雾化因子计算片元的颜色,如下等式。 

    根据雾化因子计算片元颜色公式

    <片元颜色>=<物体表面颜色>×<雾化因子>+<雾的颜色>×(1-<雾化因子>)

    来看一下示例程序

    示例程序(Fog.js) 

    如下显示了示例程序的代码。这里:(1)顶点着色器计算出当前顶点与视点的距离,并传入片元着色器;(2)片元着色器根据片元与视点的距离,计算雾化因子,最终计算出片元的颜色。注意,程序向着色器传入了视点在世界坐标系下的坐标(见附录G“世界坐标系和局部坐标系”),所以雾化因子是在世界坐标系下计算的。 

    1. var VSHADER_SOURCE =
    2. 'attribute vec4 a_Position;\n' +
    3. 'attribute vec4 a_Color;\n' +
    4. 'uniform mat4 u_MvpMatrix;\n' +
    5. 'uniform mat4 u_ModelMatrix;\n' +
    6. 'uniform vec4 u_Eye;\n' + // 视点位置(世界坐标)
    7. 'varying vec4 v_Color;\n' +
    8. 'varying float v_Dist;\n' +
    9. 'void main() {\n' +
    10. ' gl_Position = u_MvpMatrix * a_Position;\n' +
    11. ' v_Color = a_Color;\n' +
    12. // 计算从视点到每个顶点的距离
    13. ' v_Dist = distance(u_ModelMatrix * a_Position, u_Eye);\n' +
    14. '}\n';
    15. var FSHADER_SOURCE =
    16. '#ifdef GL_ES\n' +
    17. 'precision mediump float;\n' +
    18. '#endif\n' +
    19. 'uniform vec3 u_FogColor;\n' + // 雾的颜色
    20. 'uniform vec2 u_FogDist;\n' + // 雾的距离(起点、终点)
    21. 'varying vec4 v_Color;\n' +
    22. 'varying float v_Dist;\n' +
    23. 'void main() {\n' +
    24. /*
    25. 计算雾化因子(雾化因子 = (终点 - 当前点与视点见的距离) / (终点 - 起点))
    26. clamp函数:将第一个参数的值限制在第2个和第3个参数区间内,如果值在区间内,函数就直接返回第一个值,如果值小于区间的最小值或大于区间的最大值,函数就直接返回第二个参数或第三个参数
    27. */
    28. ' float fogFactor = clamp((u_FogDist.y - v_Dist) / (u_FogDist.y - u_FogDist.x), 0.0, 1.0);\n' +
    29. /* 计算片元颜色 mix函数:uFogColor*(1-雾因子)+v_Color*雾因子 */
    30. ' vec3 color = mix(u_FogColor, vec3(v_Color), fogFactor);\n' +
    31. ' gl_FragColor = vec4(color, v_Color.a);\n' +
    32. '}\n';
    33. function main() {
    34. var canvas = document.getElementById('webgl');
    35. var gl = getWebGLContext(canvas);
    36. if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
    37. var n = initVertexBuffers(gl);
    38. var fogColor = new Float32Array([0.137, 0.231, 0.423]); // 雾色
    39. var fogDist = new Float32Array([55, 80]); // 雾的距离[雾开始的地方,雾完全覆盖物体的地方]
    40. var eye = new Float32Array([25, 65, 35, 1.0]); // 视点位置
    41. var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    42. var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
    43. var u_Eye = gl.getUniformLocation(gl.program, 'u_Eye');
    44. var u_FogColor = gl.getUniformLocation(gl.program, 'u_FogColor');
    45. var u_FogDist = gl.getUniformLocation(gl.program, 'u_FogDist'); // 获取用于存储雾起始点的uniform变量
    46. // 将雾的颜色、视点和雾距离传递给统一变量
    47. gl.uniform3fv(u_FogColor, fogColor);
    48. gl.uniform2fv(u_FogDist, fogDist);
    49. gl.uniform4fv(u_Eye, eye);
    50. gl.clearColor(fogColor[0], fogColor[1], fogColor[2], 1.0); // 用雾的颜色清除
    51. gl.enable(gl.DEPTH_TEST);
    52. var modelMatrix = new Matrix4();
    53. modelMatrix.setScale(10, 10, 10);
    54. gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
    55. var mvpMatrix = new Matrix4();
    56. mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 1000);
    57. mvpMatrix.lookAt(eye[0], eye[1], eye[2], 0, 2, 0, 0, 1, 0);
    58. mvpMatrix.multiply(modelMatrix);
    59. gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    60. document.onkeydown = function(ev){ keydown(ev, gl, n, u_FogDist, fogDist); };
    61. // Clear color and depth buffer
    62. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    63. // Draw
    64. gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
    65. }
    66. function keydown(ev, gl, n, u_FogDist, fogDist) {
    67. switch (ev.keyCode) {
    68. case 38: // Up arrow key -> Increase the maximum distance of fog
    69. fogDist[1] += 1;
    70. break;
    71. case 40: // Down arrow key -> Decrease the maximum distance of fog
    72. if (fogDist[1] > fogDist[0]) fogDist[1] -= 1;
    73. break;
    74. default: return;
    75. }
    76. gl.uniform2fv(u_FogDist, fogDist); // Pass the distance of fog
    77. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    78. gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
    79. }
    80. function initVertexBuffers(gl) {
    81. // v6----- v5
    82. // /| /|
    83. // v1------v0|
    84. // | | | |
    85. // | |v7---|-|v4
    86. // |/ |/
    87. // v2------v3
    88. var vertices = new Float32Array([ // Vertex coordinates
    89. 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0-v1-v2-v3 front
    90. 1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0-v3-v4-v5 right
    91. 1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0-v5-v6-v1 up
    92. -1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1, 1, // v1-v6-v7-v2 left
    93. -1,-1,-1, 1,-1,-1, 1,-1, 1, -1,-1, 1, // v7-v4-v3-v2 down
    94. 1,-1,-1, -1,-1,-1, -1, 1,-1, 1, 1,-1 // v4-v7-v6-v5 back
    95. ]);
    96. var colors = new Float32Array([ // Colors
    97. 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, // v0-v1-v2-v3 front
    98. 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, // v0-v3-v4-v5 right
    99. 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, // v0-v5-v6-v1 up
    100. 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, // v1-v6-v7-v2 left
    101. 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // v7-v4-v3-v2 down
    102. 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0 // v4-v7-v6-v5 back
    103. ]);
    104. var indices = new Uint8Array([ // Indices of the vertices
    105. 0, 1, 2, 0, 2, 3, // front
    106. 4, 5, 6, 4, 6, 7, // right
    107. 8, 9,10, 8,10,11, // up
    108. 12,13,14, 12,14,15, // left
    109. 16,17,18, 16,18,19, // down
    110. 20,21,22, 20,22,23 // back
    111. ]);
    112. var indexBuffer = gl.createBuffer();
    113. if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) return -1;
    114. if (!initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color')) return -1;
    115. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    116. gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
    117. return indices.length;
    118. }
    119. function initArrayBuffer (gl, data, num, type, attribute) {
    120. var buffer = gl.createBuffer();
    121. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    122. gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    123. var a_attribute = gl.getAttribLocation(gl.program, attribute);
    124. gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
    125. gl.enableVertexAttribArray(a_attribute);
    126. gl.bindBuffer(gl.ARRAY_BUFFER, null);
    127. return true;
    128. }

    代码详解

    顶点着色器计算了顶点与视点间的距离:首先将顶点坐标转换到世界坐标系下,然后调用内置函数distance()并将视点坐标(也是在世界坐标系下)和顶点坐标作为参数传入,distance()函数算出二者间的距离,并赋值给v_Dist变量以传入片元着色器(第13行)。 

    片元着色器根据上式 线性雾化公式和式 雾化因子计算片元公式计算出雾化后的片元颜色。我们分别通过u_FogColor变量和u_FogDist变量来传入雾的颜色(第19行)和范围(第20行),其中u_FogDist.x和u_FogDist.y分别是起点和终点与视点间的距离。

    详解如何计算雾化因子(clamp()

    在根据式 线性雾化公式计算雾化因子时(第28行),我们用到了内置函数clamp(),这个函数的作用是将第1个参数的值限制在第2个和第3个参数的构成区间内。如果值在区间中,函数就直接返回这个值,如果值小于区间的最小值或大于区间的最大值,函数就返回区间的最小值或最大值。比如,本例将雾化因子限制在了0到1之间,因为视线上起点前的点和终点后的点直接根据式10.1计算出的雾化因子会是负数或大于1的数,需要将其修正成0和1。

    详解如何计算最终片元颜色(根据雾化因子计算片元颜色公式  mix())

    然后,片元着色器根据式 雾化因子计算片元公式,利用雾化因子和雾的颜色计算雾化后的片元颜色(第30行)。这里用到了内置函数mix(),该函数会计算x*(1-z)+y*z,其中x、y和z分别是第1、2和3个参数。 
    JavaScript中的main()函数将创建计算雾化效果需要的那些值,并通过相应的uniform变量传入着色器。

    你应当知道,除了线性雾化,还有多种其他雾化算法,如OpenGL中常用的指数雾化(见OpenGL Programming Guide)。使用其他的雾化算法也很简单,只需在着色器中修改雾化指数的计算方法即可。

    示例效果

    示例程序(使用w分量代替顶点与视点的距离 Fog_w.js)

    在顶点着色器中计算顶点与视点的距离,会造成较大的开销,也许会影响性能。我们可以使用另外一种方法来近似估算出这个距离,那就是使用顶点经过模型视图投影矩阵变换后的坐标的w分量。在在本例中,顶点变换后的坐标就是gl_Position。之前,我们并未显式使用过gl_Position的w分量,实际上,这个w分量的值就是顶点的视图坐标的z分量乘以-1。在视图坐标系中,视点在原点,视线沿着Z轴负方向,观察者看到的物体其视图坐标系值z分量都是负的,而gl_Position的w分量值正好是z分量值乘以-1,所以可以直接使用该值来近似顶点与视点的距离。 

    在顶点着色器中,将计算顶点与视点距离的部分替换成例10.7种那样,雾化效果基本不变。

  • 相关阅读:
    【语音识别】搭建本地的语音转文字系统:FunASR
    Vue非父子组件之间的通信
    ros协议的最佳解决方案
    如何从零开始解读什么叫产品经理
    spring-cloud-alibaba : nacos安装
    瓦片地图
    linux基础操作代码
    centos遇到的问题
    总结:原生servlet请求转发url与请求重定向url的使用区别
    安全运营 (历史漏洞修复)
  • 原文地址:https://blog.csdn.net/dabaooooq/article/details/133253435