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“世界坐标系和局部坐标系”),所以雾化因子是在世界坐标系下计算的。 

var VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'attribute vec4 a_Color;\n' +'uniform mat4 u_MvpMatrix;\n' +'uniform mat4 u_ModelMatrix;\n' +'uniform vec4 u_Eye;\n' +     // 视点位置(世界坐标)'varying vec4 v_Color;\n' + 'varying float v_Dist;\n' +'void main() {\n' +'  gl_Position = u_MvpMatrix * a_Position;\n' +'  v_Color = a_Color;\n' +// 计算从视点到每个顶点的距离'  v_Dist = distance(u_ModelMatrix * a_Position, u_Eye);\n' +'}\n';
var FSHADER_SOURCE ='#ifdef GL_ES\n' +'precision mediump float;\n' +'#endif\n' +'uniform vec3 u_FogColor;\n' + // 雾的颜色'uniform vec2 u_FogDist;\n' +  // 雾的距离(起点、终点)'varying vec4 v_Color;\n' +'varying float v_Dist;\n' +'void main() {\n' +/* 计算雾化因子(雾化因子 = (终点 - 当前点与视点见的距离) / (终点 - 起点))clamp函数:将第一个参数的值限制在第2个和第3个参数区间内,如果值在区间内,函数就直接返回第一个值,如果值小于区间的最小值或大于区间的最大值,函数就直接返回第二个参数或第三个参数*/'  float fogFactor = clamp((u_FogDist.y - v_Dist) / (u_FogDist.y - u_FogDist.x), 0.0, 1.0);\n' +/* 计算片元颜色 mix函数:uFogColor*(1-雾因子)+v_Color*雾因子 */ '  vec3 color = mix(u_FogColor, vec3(v_Color), fogFactor);\n' +'  gl_FragColor = vec4(color, v_Color.a);\n' +'}\n';function main() {var canvas = document.getElementById('webgl');var gl = getWebGLContext(canvas);if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE))  returnvar n = initVertexBuffers(gl);var fogColor = new Float32Array([0.137, 0.231, 0.423]);  // 雾色var fogDist = new Float32Array([55, 80]); // 雾的距离[雾开始的地方,雾完全覆盖物体的地方]var eye = new Float32Array([25, 65, 35, 1.0]); // 视点位置var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');var u_Eye = gl.getUniformLocation(gl.program, 'u_Eye');var u_FogColor = gl.getUniformLocation(gl.program, 'u_FogColor');var u_FogDist = gl.getUniformLocation(gl.program, 'u_FogDist'); // 获取用于存储雾起始点的uniform变量// 将雾的颜色、视点和雾距离传递给统一变量gl.uniform3fv(u_FogColor, fogColor); gl.uniform2fv(u_FogDist, fogDist); gl.uniform4fv(u_Eye, eye);  gl.clearColor(fogColor[0], fogColor[1], fogColor[2], 1.0); // 用雾的颜色清除gl.enable(gl.DEPTH_TEST);var modelMatrix = new Matrix4();modelMatrix.setScale(10, 10, 10);gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);var mvpMatrix = new Matrix4();mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 1000);mvpMatrix.lookAt(eye[0], eye[1], eye[2], 0, 2, 0, 0, 1, 0);mvpMatrix.multiply(modelMatrix);gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);document.onkeydown = function(ev){ keydown(ev, gl, n, u_FogDist, fogDist); };// Clear color and depth buffergl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);// Drawgl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}function keydown(ev, gl, n, u_FogDist, fogDist) {switch (ev.keyCode) {case 38: // Up arrow key -> Increase the maximum distance of fogfogDist[1]  += 1;break;case 40: // Down arrow key -> Decrease the maximum distance of fogif (fogDist[1] > fogDist[0]) fogDist[1] -= 1;break;default: return;}gl.uniform2fv(u_FogDist, fogDist);   // Pass the distance of foggl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}function initVertexBuffers(gl) {//    v6----- v5//   /|      /|//  v1------v0|//  | |     | |//  | |v7---|-|v4//  |/      |///  v2------v3var vertices = new Float32Array([   // Vertex coordinates1, 1, 1,  -1, 1, 1,  -1,-1, 1,   1,-1, 1,    // v0-v1-v2-v3 front1, 1, 1,   1,-1, 1,   1,-1,-1,   1, 1,-1,    // v0-v3-v4-v5 right1, 1, 1,   1, 1,-1,  -1, 1,-1,  -1, 1, 1,    // v0-v5-v6-v1 up-1, 1, 1,  -1, 1,-1,  -1,-1,-1,  -1,-1, 1,    // v1-v6-v7-v2 left-1,-1,-1,   1,-1,-1,   1,-1, 1,  -1,-1, 1,    // v7-v4-v3-v2 down1,-1,-1,  -1,-1,-1,  -1, 1,-1,   1, 1,-1     // v4-v7-v6-v5 back]);var colors = new Float32Array([     // Colors0.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 front0.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 right1.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 up1.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 left1.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 down0.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]);var indices = new Uint8Array([       // Indices of the vertices0, 1, 2,   0, 2, 3,    // front4, 5, 6,   4, 6, 7,    // right8, 9,10,   8,10,11,    // up12,13,14,  12,14,15,    // left16,17,18,  16,18,19,    // down20,21,22,  20,22,23     // back]);var indexBuffer = gl.createBuffer();if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) return -1;if (!initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color')) return -1;gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);return indices.length;
}function initArrayBuffer (gl, data, num, type, attribute) {var buffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);var a_attribute = gl.getAttribLocation(gl.program, attribute);gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);gl.enableVertexAttribArray(a_attribute);gl.bindBuffer(gl.ARRAY_BUFFER, null);return true;
}

代码详解

顶点着色器计算了顶点与视点间的距离:首先将顶点坐标转换到世界坐标系下,然后调用内置函数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种那样,雾化效果基本不变。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/90868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

搭建自己的搜索引擎之四

一、前言 搭建自己的搜索引擎之三 介绍了通过HTTP RESTful 对ES进行增删改查,这一般手工运维ES时使用,程序代码中最好还是使用Java API去操作ES会更容易维护,但ES API竟然贼多,本篇介绍一下 四种 API及其简单使用。 注&#xff…

第1篇 目标检测概述 —(2)目标检测算法介绍

前言:Hello大家好,我是小哥谈。目标检测算法是一种计算机视觉算法,用于在图像或视频中识别和定位特定的目标物体。常见的目标检测算法包括传统的基于特征的方法(如Haar特征和HOG特征)以及基于深度学习的方法&#xff0…

Docker(三)、Dockerfile探究

Dockerfile探究 一、镜像层概念1、通过执行命令显化docker的机制 二、Dockerfile基础命令1、FROM 基于基准镜像【即构建镜像的时候,依托原有镜像做拓展】2、LABEL & MAINTAINER -说明信息3、WORKDIR 设置工作目录4、ADD & COPY 复制文件5、ENV 设置环境常量…

crypto:Alice与Bob

题目 根据题目描述,将98554799767分解成两个素数 得到两个素数101999和966233。根据题目提示把它们拼接起来进行md5的32位小写哈希

提升您的Mac文件拖拽体验——Dropzone 4 for mac

大家都知道,在Mac上进行文件拖拽是一件非常方便的事情。然而,随着我们在工作和生活中越来越多地使用电脑,我们对于这个简单操作的需求也越来越高。为了让您的文件拖拽体验更加高效和便捷,今天我们向大家介绍一款强大的工具——Dro…

[plugin:vite:css] [sass] Undefined mixin.

前言: vite vue3 TypeScript环境 scss报错: [plugin:vite:css] [sass] Undefined mixin. 解决方案: 在vite.config.ts文件添加配置 css: {preprocessorOptions: {// 导入scss预编译程序scss: {additionalData: use "/resources/_ha…

8章:scrapy框架

文章目录 scrapy框架如何学习框架?什么是scarpy?scrapy的使用步骤1.先转到想创建工程的目录下:cd ...2.创建一个工程3.创建之后要转到工程目录下4.在spiders子目录中创建一个爬虫文件5.执行工程setting文件中的参数 scrapy数据解析scrapy持久…

计算机视觉与深度学习-循环神经网络与注意力机制-RNN(Recurrent Neural Network)、LSTM-【北邮鲁鹏】

目录 举例应用槽填充(Slot Filling)解决思路方案使用前馈神经网络输入1-of-N encoding(One-hot)(独热编码) 输出 问题 循环神经网络(Recurrent Neural Network,RNN)定义如何工作学习目标深度Elm…

uniapp ssr发行后一直Hydration completed but contains mismatches Cannot find module

最开始我用前端网页托管的地址访问一直是 Hydration completed but contains mismatches 解决方案 要从云函数的地址访问项目。 先绑定域名,否则用uniapp自带地址访问一直是下载文件 设置路径 最后效果 uniapp ssr 云函数访问 MODULE_NOT_FOUND:Cannot fin…

2023 年 Bitget Wallet 测评

对Bitget Wallet钱包的看法 Bitget Wallet在安全性、产品实力和使用体验方面可与Metamask媲美,甚至有所超越,唯一稍显不足的是知名度稍逊一筹。在众多钱包中,Bitget Wallet是拥有最全面的钱包之一,尤其适合那些希望一步到位&…

最小生成树 | 市政道路拓宽预算的优化 (Minimum Spanning Tree)

任务描述: 市政投资拓宽市区道路,本着执政为民,节省纳税人钱的目的,论证是否有必要对每一条路都施工拓宽? 这是一个连问带答的好问题。项目制学习可以上下半场,上半场头脑风暴节省投资的所有可行的思路&a…

web:[极客大挑战 2019]Upload

题目 页面显示为一个上传&#xff0c;猜测上传一句话木马文件 先查看源代码看一下有没有有用的信息&#xff0c;说明要先上传图片&#xff0c;先尝试上传含有一句话木马的图片 构造payload <?php eval($_POST[123]);?> 上传后页面显示为&#xff0c;不能包含<&…

“益路同行”栏目专访第11期——柳州市雨花敬老服务中心陈勇梅

中国善网在本届&#xff08;第十届&#xff09;慈展会上特别推出了《益路同行》采访栏目&#xff0c;《益路同行》栏目旨在寻觅公益之路上同行者的故事&#xff0c;挖掘公益更深层次的内涵&#xff0c;探索新时代公益发展道路。希望公益企业、人物、故事被更多人看到&#xff0…

2.物联网射频识别,RFID通信原理,RFID读写器与标签无线交互方式、数据反馈方式,RFID调制与解调、编码方式,不同RFID标签与读写器

一。RFID无线识别的原理 1.RFID系统无线通信基本原理 如下图所示&#xff0c;左边是读写器&#xff08;刷卡器&#xff09;&#xff0c;右边是标签&#xff08;卡&#xff09;&#xff0c;中间通过无线通信方式。 标签&#xff1a;&#xff08;卡&#xff09; 读写器&#xff…

实战项目:VB实现小鸟快跑小游戏

文章目录&#xff1a; 一&#xff1a;效果演示 二&#xff1a;实现思路 三&#xff1a;代码实现 form1 效果图 代码 form2 效果图 代码 一&#xff1a;效果演示 效果图◕‿◕✌✌✌ 代码下载 二&#xff1a;实现思路 窗口1 就是实现窗口的跳转和关闭窗口2 1.先添加背…

CSS详细基础(三)复合选择器

前两章介绍了CSS中的基础属性&#xff0c;以及一些基础的选择器&#xff0c;本贴开始介绍复合选择器的内容~ ​ 在 CSS 中&#xff0c;可以根据选择器的类型把选择器分为基础选择器和复合选择器&#xff0c;复合选择器是建立在基础选择器之上&#xff0c;对基本选择器进行组合形…

ElementUI之动态树+数据表格+分页

目录 前言 一.ElementUI之动态树 1.前端模板演示 2.数据绑定 2.1 通过链接获取后台数据 2.2 对链接进行绑定 2.3添加动态路由 2.4 配置路由 3.效果演示 二.数据表格动态分页 1.前端模板 2.通过JS交互获取后端数据 3 效果演示 前言 Element UI 是一个基于 Vue.js 的开…

IDEA Debug技巧大全,看完就能提升工作效率

作者简介 目录 1.行断点 2.方法断点 3.异常断点 4.字段断点 5.条件表达式 1.行断点 行断点就是平时我们在代码行旁边单击鼠标打上的断点&#xff0c;这个没有什么好说的。关键点在于很多人不知道的&#xff0c;行断点其实是可以右击选择是对改行的全部调用都生效&#xf…

缓存一致性(cache coherency)解决方案:MESI 协议状态转换详解

MESI 协议 一&#xff0c;MESI状态释义二&#xff0c;MESI状态转换1 Invalid after Reset2, Invalid > Exclusive3, Exclusive > Modified4 Modified > Shared, Invalid > Shared5 Shared > Invalid, Shared > Modified 三&#xff0c;状态转换场景总结Inval…

最新影视视频微信小程序源码-带支付和采集功能/微信小程序影视源码PHP(更新)

源码简介&#xff1a; 这个影视视频微信小程序源码&#xff0c;新更新的&#xff0c;它还带支付和采集功能&#xff0c;作为微信小程序影视源码&#xff0c;它可以为用户 提供丰富的影视资源&#xff0c;包括电影、电视剧、综艺节目等。 这个小程序影视源码&#xff0c;还带有…