WebGL 雾化

news2025/1/9 1:20:38

目录

前言 

如何实现雾化

线性雾化公式

雾化因子关系图

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

示例程序(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/1037854.html

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

相关文章

二、搭建Java环境

搭建Java环境 搭建Java环境1.1.下载JDK1.2.在Win10下配置JDK环境 —————————————————————————————————————————————————— ———————————————————————————————————————————————…

李航老师《统计学习方法》第1章阅读笔记

1.1 统计学习 统计学习的特点 统计学习:计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析 现在人们提及机器学习时,往往指统计机器学习,所以可以认为本书介绍的是机器学习方法 统计学习的对象 统计学习研究的对象是数据(data)…

IDEA优化import导报-删除无用的包

选择File--Settings--Editor-General-Auto Import,勾选上下面框起来的即可,这样没有用到的包就会自己动被优化掉了~

了解SQL注入的类型、原理、检测与预防方法

作者:Insist-- 个人主页:insist--个人主页 梦想从未散场,传奇永不落幕,博主会持续更新优质网络知识、Python知识、Linux知识以及各种小技巧,愿你我共同在CSDN进步 目录 一、了解SQL注入 1. SQL注入是什么&#xff1f…

【kohya】训练自己的LoRA模型

文章目录 序言准备环境准备图片处理图片下载kohya_ss代码修改pyvenv.cfg启动界面访问地址生成字幕准备训练的文件夹配置训练参数开始训练遇到的问题: 序言 在把玩stable diffusion的webUI和comfyUI后,思考着自己也微调一个个性化风格的checkpoint、LyCO…

2023蓝帽杯南部赛区半决赛取证复现

首先嗷,仅代表个人评价一下就是说赛委会在出题的时候不严谨,我一度怀疑我的语文阅读能力有问题,但是呢,这次的取证题目虽然不是很难,但是有些地方我也是依旧没有找到,说了这么多,接下来&#xf…

使用BeanCopier复制对象属性值,遇到NullPointerException?

文章目录 一、场景二、问题三、分析四、解决五、总结 一、场景 项目中,为了数据安全,由于身份证属于敏感信息,需要加密后返回给页面解密显示,但DTO中出现了一种骚操作,身份证的get方法,把身份证转成大写的…

【数据结构】C++实现哈希表

闭散列哈希表 哈希表的结构 在闭散列的哈希表中,哈希表每个位置除了存储所给数据之外,还应该存储该位置当前的状态,哈希表中每个位置的可能状态如下: EMPTY(无数据的空位置)。EXIST(已存储数…

Zabbix介绍与安装

目录 一、概述 二、zabbix的主要功能 三、zabbix监控原理 四、Zabbix 监控模式 五、zabbix的架构 server-client server-proxy-client master-node-client 六、zabbix的安装 安装zabbix服务端 安装zabbix客户端 测试zabbix 1、在 Web 页面中添加 agent 主机点击左…

SystemC入门学习-第3章 数据类型

本章将详细的描述SystemC的数据类型,并介绍这些类型的数据可以进行哪些操作。比如值保持器(value holder)就是一种特殊的类型。在所有的类型中,最重要的是bool和sc_uint两种类型 3.1 值保持器 值保持器有三种: 变量…

如何管理销售团队?

本文将为大家讲解:如何管理销售团队? 销售团队的管理是企业成功的关键因素之一。一个高效、协同的销售团队可以推动企业的增长,增强市场竞争力。然而,销售团队的管理并不是一件容易的事情,它涉及多个方面的协调和优化…

Verilog开源项目——百兆以太网交换机(二)AES加解密模块设计

Verilog开源项目——百兆以太网交换机(二)AES加解密模块设计 🔈声明:未经作者允许,禁止转载 😃博主主页:王_嘻嘻的CSDN主页 🔑全新原创以太网交换机项目,Blog内容将聚焦整…

基于微信小程序的投票系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户微信小程序的主要功能有:管理员的主要功能有:具体实现截图论文参考为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,C…

Vue.js路由及Node.js的入门使用---超详细

一,Vue路由 1.1 路由是什么 路由是用来管理应用程序中不同页面之间导航的概念。Vue Router是Vue.js官方提供的路由管理器,它允许我们通过定义路由规则和视图组件来配置路由 1.2 路由给我们带来的好处有哪些? 单页应用(Single Pag…

Springboot整合jdbc和Mybatis

目录 整合jdbc 1. 新建项目 2. 编写yaml配置文件连接数据库 3. 测试类 使用原生的jdbcTemplate进行访问测试 使用Druid连接池 1. 添加类型 2. 初始化连接池 3. 编写config类 配置Druid数据源监视 整合Mybatis 1. 导入依赖 2. 编写mapper接口 3. 编写实体类 4. 编…

GLTF编辑器的另一个作用

1、GLB模型介绍 GLB(GLTF Binary)是一种用于表示三维模型和场景的文件格式。GLTF是"GL Transmission Format"的缩写,是一种开放的、跨平台的标准,旨在在各种3D图形应用程序和引擎之间进行交换和共享。 GLB文件是GLTF文件…

MySQL数据库详解 二:数据库的高级语句(高级查询语句)

文章目录 1. 克隆表 ---- 将数据表的数据记录生成到新的表中1.1 方式一:先创建新表,再导入数据1.2 方式二:创建的时候同时导入 2. 清空表 ---- 删除表内的所有数据2.1 delete删除2.2 truncate删除(重新记录)2.3 创建临…

别着急,解决不了的问题,就请交给时间吧

转眼间我走出社会已过去四年之久,但很多事依旧历历在目,就好像昨天发生的一样。 我小时候,因为一场医学事故患有先天性白内障,真的是连黑板的看不清,当时自己也不太懂事,上课对我来说就是画画以及一切能够消…

保姆级 Keras 实现 Faster R-CNN 十三 (训练)

保姆级 Keras 实现 Faster R-CNN 十三 训练 一. 将 Faster R-CNN 包装成一个类二. 修改模型结构1. 修改 input_reader 函数2. 增加 RoiLabelLayer 层 三. 损失函数1. 自定义损失函数2. 自定义精度评价函数 四. 模型编译五. 模型训练六. 预训练模型七. 保存模型与参数八. 代码下…

更新、修改

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法: update 表名 列名该列新值, 列名该列新值, ... where 记录匹配条件; 说明:update 更新、修改 set 设置 …