WebGL雾化

news2024/11/26 12:14:40

目录

前言 

如何实现雾化

线性雾化公式

雾化因子关系图

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

示例程序(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))  return
  var 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 buffer
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  // Draw
  gl.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 fog
      fogDist[1]  += 1;
      break;
    case 40: // Down arrow key -> Decrease the maximum distance of fog
      if (fogDist[1] > fogDist[0]) fogDist[1] -= 1;
      break;
    default: return;
  }
  gl.uniform2fv(u_FogDist, fogDist);   // Pass the distance of fog
  gl.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------v3

  var vertices = new Float32Array([   // Vertex coordinates
     1, 1, 1,  -1, 1, 1,  -1,-1, 1,   1,-1, 1,    // v0-v1-v2-v3 front
     1, 1, 1,   1,-1, 1,   1,-1,-1,   1, 1,-1,    // v0-v3-v4-v5 right
     1, 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 down
     1,-1,-1,  -1,-1,-1,  -1, 1,-1,   1, 1,-1     // v4-v7-v6-v5 back
  ]);
  var colors = new Float32Array([     // Colors
    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
    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
    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
    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
    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
    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
  ]);
  var indices = new Uint8Array([       // Indices of the vertices
     0, 1, 2,   0, 2, 3,    // front
     4, 5, 6,   4, 6, 7,    // right
     8, 9,10,   8,10,11,    // up
    12,13,14,  12,14,15,    // left
    16,17,18,  16,18,19,    // down
    20,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.coloradmin.cn/o/1048211.html

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

相关文章

KongA 任意用户登录漏洞分析

KongA 简介 KongA 介绍 KongA 是 Kong 的一个 GUI 工具。GitHub 地址是 https://github.com/pantsel/konga 。 KongA 概述 KongA 带来的一个最大的便利就是可以很好地通过UI观察到现在 Kong 的所有的配置,并且可以对于管理 Kong 节点 漏洞成因 未设置TOKEN_SECRE…

智能报修系统哪家好?有什么扫码一键报修的软件?

这是一套to b系统,专门为企事业单位提供日常的故障报修服务,单位员工遇到任何故障可扫码提交到平台上,再由平台根据技术维修员的擅长方向派单至技术维修员手中,全程可以跟踪报修的进度,每月可以导出维修记录&#xff0…

QT编译报错stdlib.h:No such file or directory

如图,需要将第19行,INCLUDEPATH /usr/include 注释掉 原因分析: 在Ubuntu的CSTDLIB中,使用的是#include_next下“stdlib.h” ,自行增加/usr/include 把include_next的顺序打乱,造成编译错误。但是在cento…

Qt 画自定义饼图统计的例子

先给出结果图,这个例子是将各种事件分类然后统计的其比例,然后画饼图显示出来 这个是我仿照官方给的例子,让后自己理解后,修改的,要生成饼图,需要QT的 charts 支持,安装QT 没有选择这个的&#…

RDMA Shared Receive Queue(四)

参考知乎文章《RDMA之Shared Receive Queue》:https://zhuanlan.zhihu.com/p/279904125 SRQ SRQ全称为Shared Receive Queue,即共享接受队列。在QP中,SQ用于下发SEND/WRITE/READ等操作,而RQ只用于下发RECV操作,对于本…

Bytebase x Hacktoberfest 2023 黑客啤酒节开源挑战邀请

🎃十月就要到了,年度开源庆典 Hacktoberfest 即将启动!今年,Bytebase 再次加入 Hacktoberfest,欢迎大家一起参加🍺 Hacktoberfest 是什么 Hacktoberfest Hacker 黑客 Oktoberfest 啤酒节,每…

远程访问:让您随时与电脑保持连接

远程访问可以带来什么? ​如今,我们的大部分工作都是在电脑的帮助下完成的。但是,我们并不总是坐在工作电脑前,随身携带电脑也很不方便,尤其是当工作设备是台式电脑而不是笔记本电脑时。那么,当我们不在…

【EI会议征稿】2023年第二届信号处理、计算机网络与通信国际学术会议(SPCNC2023)

2023年第二届信号处理、计算机网络与通信国际学术会议(SPCNC2023) The 2nd International Conference on Signal Processing, Computer Networks and Communications 2023年第二届信号处理、计算机网络与通信国际学术会议(SPCNC2023&#x…

纷享销客蝉联2023「Cloud100 China 」榜单,综合实力再获认可

近日,2023Cloud 100 China榜单正式发布,榜单由靖亚资本与崔牛会联合推出,纷享销客凭借着过硬的综合实力与卓越的技术创新能力,再次荣登榜单。 【Cloud 100 China】榜单旨在关注中国最具潜力的Cloud公司,上榜的 100 家企…

无需公网IP,实现公网SSH远程登录MacOS【内网穿透】

目录 前言 1. macOS打开远程登录 2. 局域网内测试ssh远程 3. 公网ssh远程连接macOS 3.1 macOS安装配置cpolar 3.2 获取ssh隧道公网地址 3.3 测试公网ssh远程连接macOS 4. 配置公网固定TCP地址 4.1 保留一个固定TCP端口地址 4.2 配置固定TCP端口地址 5. 使用固定TCP端…

AutoDL初体验

AutoDL初体验 真的很给力! 相同的配置,在华为云上价格令人望而却步,AutoDL真的良心价! 搞了个4090体验一下,一小时只要两块五(主要是,之前没有GPU,好多想玩的LLM开源项目都体验不了&…

数字人直播系统开发哪家好?

随着互联网技术的不断发展,直播行业已经成为了一个炙手可热的领域。数字人直播系统作为直播行业的一种新型应用,通过虚拟形象与真人克隆的结合,为用户提供了一种全新的互动体验。那么,在众多开发公司中,哪家公司的数字…

jquery和jquery-ui拖动元素(vue2)

彩色小方块可以任意拖动&#xff0c;红色箭头指向的区域可以拖动 CDN在index.html文件中引入 <link rel"stylesheet" href"//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"><script src"https://code.jquery.com/jquery-3.6.0.min…

水利感知信息系统方案浅析

传统水利感知信息主要包括雨水工情信息&#xff0c;通常是利用信息监测技术、通信技术、网络技术、计算机技术&#xff0c;建立由雨量站、水位站、流量站等组成的雨水工情遥测系统&#xff0c;实现对流域、河流、湖泊、水库、水电站和灌区的固定或移动站点的降水量、水位、流量…

差值结构表达的吸引能

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由5张二值化的图片组成&#xff0c;让A 中有5个点&#xff0c;B中有1个点&#xff0c;并且重合&#xff0c;统计迭代次数并排序。 其中有6组数据 差值结构 迭代次数 L1 L2 L1 E1 L2…

C函数学习总结

一.内存拷贝函数 void* memcpy(voidr* s1,const void* s2,size_t n); 该函数返回拷贝后的指针。比如说&#xff0c;它可以将一个字符数组缓冲区的内容拷贝到另一个缓冲区中, size_t n可以使用sizeof进行得到。 memmove(void *s1,const void* s2,size_t n); 如果目标区域和源区…

LeetCode 2251. 花期内花的数目:排序 + 二分

【LetMeFly】2251.花期内花的数目&#xff1a;排序 二分 力扣题目链接&#xff1a;https://leetcode.cn/problems/number-of-flowers-in-full-bloom/ 给你一个下标从 0 开始的二维整数数组 flowers &#xff0c;其中 flowers[i] [starti, endi] 表示第 i 朵花的 花期 从 st…

使用docker完成minio服务部署扩容备份迁移生产实践文档

一、minio服务扩容方案 当服务器存储空间不足的时候&#xff0c;需要进行扩容&#xff0c;扩容过程中需要短暂停机时间&#xff0c;预计在一小时内能够完成和恢复 统一注意事项 强烈建议为部署中的所有节点选择基本相似的硬件配置。确保硬件&#xff08;CPU、内存、主板、存…

【Spring Boot】操作Redis数据结构

🌿欢迎来到@衍生星球的CSDN博文🌿 🍁本文主要学习【Spring Boot】操作Redis数据结构 🍁 🌱我是衍生星球,一个从事集成开发的打工人🌱 ⭐️喜欢的朋友可以关注一下🫰🫰🫰,下次更新不迷路⭐️💠作为一名热衷于分享知识的程序员,我乐于在CSDN上与广大开发者…

OOTD耳机 | 国庆出游头戴式耳机推荐|轻装上阵出片神器

国庆假期马上就要来啦&#xff5e; 今天就来分享一款数码神器——头戴式耳机&#xff0c;穿搭拍照必备&#xff0c;特别适合假期出游哦&#xff01;轻装上阵出片神器——轻律Umelody U1头戴式蓝牙耳机&#xff01; 整个设计简约低调&#xff0c;棕色的为主调的包身&#xff0c;…