体积光介绍

首先,我们要确认一下什么是体积光。体积光通俗来说是我们能看见的”光路“,并不是所有灯光都会形成体积光效果,它是光照到大气中粒子散射后得到的效果(丁达尔效应)。我们有时候还会看到一束束光散开的效果,这是光在传播过程中遇到了障碍物(比如穿过云层、树木的光束)导致的。

根据物理原理,我们知道体积光是粒子散射的结果,如果我们用体素的思想来考虑体积光,我们所看到的某一点处的体积光颜色是眼睛到当前点的射线上,光路中所有粒子散射光的叠加。

体积光经常模拟Sun Shaft(太阳散射)的效果。

常用实现思路

常用的体积光实现思路包括:

  • BillBoard贴片

    BillBoard贴片很容易理解,用PHOTOSHOP生成一个随机的明暗条文,加上遮罩,让它看起来有光条的感觉。
  • 径向模糊

    径向模糊是一种后处理的方法,所谓后期处理就是在游戏画面渲染完毕之后,另外加一次渲染,类似于PHOTOSHOP,但处理的对象是每一帧游戏画面,因为速度要求多使用GPU计算。
  • 光线追踪

    近期伴随着渲染技术的进步,业界已经开始使用基于光线追踪、阴影贴图等更为精细的渲染技术来实现体积光的效果。

详细介绍可以参考下面这篇文章,上面介绍的内容来自这篇文章:

https://zhuanlan.zhihu.com/p/21425792

本文的重点是介绍通过径向模糊来实现体积光的效果。当然径向模糊的缺点十分明显,如果光源不在画面内,显然径向模糊是没办法执行的。因此径向模糊实现的体积光主要用来表现天空中日月星光散射的效果。

径向模糊实现体积光

径向模糊实现体积光的主要步骤大致如下:

  1. 正常渲染整个画面。
  2. 然后再一次渲染整个画面:使用指定颜色渲染发光的对象,使用黑色渲染其他对象(遮挡物)。

    3.对第二次渲染的画面进行径向模糊。
  3. 把模糊的画面和正常渲染的画面通过相机混合(Additively blend)得到最终的结果。

示例说明

本文示例中,渲染四个圆环物体和一个球形的发光物体。 四个圆环从上到下排列,发光物在在圆环中间周期性上下运动。

正常渲染整个画面

正常渲染整个画面不属于本文的重点内容,属于webgl的基本内容,此处不过多赘述。不过需要注意的一点是,发光物体使用纯色渲染,后面的效果才会好。

渲染发光物体和遮挡物

此处渲染的结果,我们称之为Occlusion buffer。为了获取Occlusion buffer,一般使用指定的颜色纯色绘制发光的球体,而使用黑色绘制其他的对象;我觉得更好的方式是,在渲染遮挡物的时候,通过colorMask指定不渲染颜色,只记录深度,因此起到遮挡的效果而不产生任何遮挡物的像素。代码如下所示:

 frameBuffer2.bind();
gl.colorMask(false, false, false, false);
for (var i = 0; i < 4; i++) {
mat4.identity(mMatrix);
ambientLightColor = hsva(i * 40, 1, 1, 1);
mat4.translate(mMatrix, mMatrix, [0.0, 10.0 * i - 20, 0.0]);
mat4.invert(invMatrix, mMatrix);
mat4.mul(mvpMatrix, tmpMatrix, mMatrix);
drawNormal(); // 绘制圆环
} gl.colorMask(true, true, true, true);
for (var i = 0; i < 1; i++) {
mat4.identity(mMatrix);
ambientLightColor = hsva(i * 40, 1, 1, 1);
mat4.translate(mMatrix, mMatrix, [0.0, rad * 5 - 15, 0.0]);
mat4.scale(mMatrix, mMatrix, [1.1, 1.1,1.1]);
mat4.invert(invMatrix, mMatrix);
mat4.mul(mvpMatrix, tmpMatrix, mMatrix);
drawShpere(1);
}
frameBuffer2.unbind();

代码首先绑定一个framebuffer,因为Occlusion buffer是要绘制到贴图对象上的,有关framebuffer的内容此处不做详细说明,不明白的读者可以自行查找资料,也可以参考:渲染到纹理

之后开始循环绘制遮挡物,也就是圆环,此处循环了4次,表示绘制四个圆环。

需要注意的是,在绘制遮挡物之前,通过colorMask指定不绘制颜色到颜色缓冲区,也就是实际上不真正绘制圆环对象:

gl.colorMask(false, false, false, false);

既然不真正绘制圆环对象,为何要调用绘制代码呢,这是因为绘制的过程除了绘制颜色信息到颜色缓冲区,还会记录深度信息到深度缓冲区,而深度缓冲区可以记录最终的遮挡效果。 如果对于基本原理不懂的读者,可以自行查询相关知识,此处不赘述。也可以参考专栏内容:

https://xiaozhuanlan.com/webgl

然后开始绘制发光球体,需要注意的是,在绘制之前需要恢复颜色缓冲区的写入,所以先调用下面的代码进行恢复:

gl.colorMask(true, true, true, true);

然后绘制发光球体。

一个小技巧是,此处绘制发光球体的时候,适当的放大了球体的缩放比例:

mat4.scale(mMatrix, mMatrix, [1.1, 1.1,1.1]);

这是为了后期获取更明显的发光效果。

最终的绘制效果就是Occlusion buffer。如下图所示:

可以看出值绘制了球体的部分,但是圆环对球体的遮挡仍然存在。

对Occlusion buffer进行径向模糊

上一节的内容,我们绘制了一个Occlusion buffer,此处对Occlusion buffer进行径向模糊,有关径向模糊的内容,可以关注我上一篇文章。代码如下所示:

function drawCopy(vv) {
gl.useProgram(program2);
gl.uniform1i(program2.texture, 1);
gl.uniform2fv(program2.uCenterOffset, [vv[0],vv[1]]);
gl.uniform1f(program2.strength, (document.getElementById('range').value | 0) / 2); gl.activeTexture(gl.TEXTURE0+1); // 激活gl.TEXTURE0
gl.bindTexture(gl.TEXTURE_2D, frameBuffer2.colorTexture); // 绑定贴图对象 gl.enableVertexAttribArray(program2.aPosition);
gl.enableVertexAttribArray(program2.aTexCoord); gl.bindBuffer(gl.ARRAY_BUFFER, qdVerticesBuffer); //绑定缓冲区
// 把缓冲区分配给attribute变量
gl.vertexAttribPointer(program2.aPosition, 3, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, qdStBuffer); //绑定缓冲区
gl.vertexAttribPointer(program2.aTexCoord, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, qdIndexBuffer);
// gl.drawElements(gl.TRIANGLES,indices.length,gl.UNSIGNED_BYTE,0);
gl.drawElements(gl.TRIANGLES, qdIndices.length, gl.UNSIGNED_SHORT, 0);
}

需要注意的是:

  • 径向模糊的中心点不是固定的canvas的中心点,而应该是发光球体位置在屏幕上面的投影坐标位置:
    gl.uniform2fv(program2.uCenterOffset, [vv[0],vv[1]]);

vv的计算如下:

 let vv = vec4.create();
vv[3] = 1;
vec4.transformMat4(vv, vv, mvpMatrix);
vv[0] = vv[0] / vv[3];
vv[1] = vv[1] / vv[3];
vv[2] = vv[2] / vv[3];
vv[3] = 1;

径向模糊的内容和正常绘制内容进行叠加

要进行叠加,有人使用如下的思路:

  1. 把正常的场景绘制到一个framebuffer上面
  2. 把模糊后的效果绘制到另外一个framebuffer上面。
  3. 把上面两次绘制的贴图对象传递给一个叠加的绘制程序,绘制正常的结果。 一般来说,叠加程序会构造一个像素叠加方法,如下所示:
	"void main() {",

				"vec4 texel = texture2D( tDiffuse, vUv );",
"vec4 add = texture2D( tAdd, vUv );",
"gl_FragColor = texel + add * fCoeff;", "}"

该方法的优点是,可以更加灵活的控制叠加算法,比如可以调整fCoeff参数调整体积光的强度;缺点也比较明显,多使用了两次framebuffer,性能消耗更大。

本示例不使用以上方法,而是使用如下思路:

  1. 正常绘制场景
  2. 开启webgl的addtive blend 功能
  3. 绘制模糊场景

代码如下所示:

  gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
// gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
...
drawCopy(vv);
drawCopy(vv);

其中gl.blendFunc(gl.SRC_ALPHA, gl.ONE);指定了相加的混合方式。

注意上面drawCopy方法调用了两次,是为了加强体积光的效果。drawCopy调用次数和前面说到的fCoeff参数的作用类似。虽然增加了调用次数,但是由于drawCopy只是简单的绘制了贴图的内容,其性能损耗并不会太大。

有关性能优化

如果需要优化性能,可以考虑减少framebuffer的尺寸。

另外还可以通过降低模糊迭代次数来提高性能。

效果图

上面就是“webgl径向模糊实现体积光”的主要内容,下面上一张图看看渲染的效果:

本文也发表在我的webgl专栏,完整代码可以在专栏中获取:

https://xiaozhuanlan.com/topic/3148296057

案例视频 可以关注视频号 "ITman彪叔"观看,也欢迎关注公众号。

webgl径向模糊实现体积光的更多相关文章

  1. Unity Shader-GodRay,体积光(BillBoard,Volume Shadow,Raidal Blur,Ray-Marching)

    好久没有更新博客了,经历了不少事情,好在最近回归了一点正轨,决定继续Unity Shader的学习之路.作为回归的第一篇,来玩一个比较酷炫的效果(当然废话也比较多),一般称之为GodRay(圣光),也 ...

  2. 【Unity Shaders】ShadowGun系列之二——雾和体积光

    写在前面 体积光,这个名称是God Rays的中文翻译,感觉不是很形象.God Rays其实是Crepuscular rays在图形学中的说法,而Crepuscular rays的意思是云隙光.曙光. ...

  3. webgl实现径向模糊

    径向模糊简介 径向模糊,是一种从中心向外呈幅射状,逐渐模糊的效果. 因此径向模糊经常会产生一些中心的发散效果,在PS中同样也有径向模糊的滤镜效果. 径向模糊通常也称为变焦模糊.径向模糊(Radial ...

  4. PBRT笔记(14)——光线传播2:体积渲染

    传输公式 传输方程是控制光线在吸收.发射和散射辐射的介质中的行为的基本方程.它解释了第11章中描述的所有体积散射过程--吸收.发射和内.外散射.并给出了一个描述环境中辐射分布的方程.光传输方程实际上是 ...

  5. webGL动画

    在做这个项目之前,我也和很多人的想法一样觉得:H5做动画性能不行,只能完成简单动画,可是事实并非如此.所以借此篇分享振奋下想在H5下做酷炫游戏的人心. 体验游戏请长按二维码识别: 好吧,知道你懒.不想 ...

  6. [转]显卡帝揭秘3D游戏画质特效

    显卡帝揭秘3D游戏画质特效 近几年来,大量采用最新技术制作的大型3D游戏让大部分玩家都享受到了前所未有的游戏画质体验,同时在显卡硬件方面的技术革新也日新月异.对于经常玩游戏的玩家来说,可能对游戏画质提 ...

  7. Unity实现刺客信条灯光的思路探究

    灯光需求 类似刺客信条的开场CG动画,场景中打着酷炫的灯光,玩家在场景中行走可以感受到灯光很真实. 参考视频:http://www.iqiyi.com/w_19rqytbmvt.html 运行环境 安 ...

  8. {转自MC}NVIDIA DirectX 11演示DEMO详解

    http://tieba.baidu.com/p/1960826986 图形技术无论如何发展,最终都要落到实际的应用中才有效果.在个人电脑上,图形技术最大的用户除了显示UI和操作界面外,就是呈现美轮美 ...

  9. 移动平台Unity3D 应用性能优化

    WeTest 导读 做了大概半年多VR应用了,VR由于双眼double渲染的原因,对性能的优化要求比较高,在项目的进展过程中,总结了一些关于移动平台上Unity3D的性能优化经验,供分享. 一.移动平 ...

  10. PBRT笔记(10)——体积散射

    体散射处理过程 3个影响参与介质在环境中的辐射度分布的主要因素: 吸收:减少光能,并将其转化为别的能量,例如热量. 发光:由光子发射光能至环境中. 散射:由于粒子碰撞,使得一个方向的辐射度散射至其他方 ...

随机推荐

  1. IceRPC之调用管道Invocation pipeline与传出请求Outgoing request->快乐的RPC

    作者引言 .Net 8.0 下的新RPC 很高兴啊,我们来到了IceRPC之调用管道 Invocation pipeline与传出请求 Outgoing request->快乐的RPC, 基础引 ...

  2. 《最新出炉》系列入门篇-Python+Playwright自动化测试-49-Route类拦截修改请求-下篇

    1.简介 在日常工作和学习中,自动化测试的时候:在加载页面时,可能页面出现很多不是很重要或者不是我们所关注的,这个时候我们就可以选择不加载这些内容,以提高页面加载速度,节省资源.例如:可能页面上图片比 ...

  3. 在Mac上运行Rainbond,10分钟快速安装

    前言 以往安装部署 Rainbond 的方式都无法绕过 Kubernetes 集群的搭建,无论是作为开发环境还是用于生产交付,部署的过程都非常依赖于服务器或云主机.这在体验 Rainbond 云原生应 ...

  4. 使用 Filebeat+Easysearch+Console 打造日志管理平台

    近年来,日志管理平台越来越流行.使用日志管理平台可以实时地.统一地.方便地管理和查看日志,挖掘日志数据价值,驱动运维.运营,提升服务管理效率. 方案架构 Beats 是轻量级采集器,包括 Filebe ...

  5. 如何排查常规软件问题 - 面向 Linux 初级用户的教程

    笔者从 14 年做开源软件以来,接触了众多 Linux 新手用户,这里我为这类用户总结了一些常见的问题排查方法,希望能帮助到大家.如果你已经工作多年,对于下面提到的思路和方法应该非常熟悉,如果对某一条 ...

  6. Mysql常见使用问题的解决方法

    问题一:Mysql插入中文数据时,报错"incorrect string value"字符转换不正确 解决方法: 第一种方式: 1.更改Mysql安装目录下的文件my.ini(一般 ...

  7. 副本集replicaSet

    mongodb高可用架构 https://www.mongodb.com/docs/manual/tutorial/deploy-replica-set/ 复制是跨多个服务器同步数据的过程. 复制提供 ...

  8. Vue学习:8.v标签综合-强化版

    通过前几节的认识和学习,我们掌握了常用v标签的用法,这一节再来巩固提高一下吧. 实例:成绩面板 实现功能: 主体由两大部分组成:表格+表单.这个表格可以显示多科成绩,并具有表头.删除以及底部统计功能. ...

  9. 项目管理--PMBOK 读书笔记(9)【项目资源管理】

    1.团队成员的角色与职责: 1)层级结构(OBS):与 WBS 交叉确认部门的全部项目指责,项目组织结构图: 2)矩阵结构(RAM):工作包(活动)与项目团队的关系,主要用于明确角色与期望(职责) 3 ...

  10. Java代码忽略https证书:解决No subject alternative names present问题 HttpURLConnection https请求

    Java代码忽略https证书:解决No subject alternative names present问题 import org.slf4j.Logger; import org.slf4j.L ...