UnityShader 基于物理的体积光(丁达尔光线)

Posted 李利知

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UnityShader 基于物理的体积光(丁达尔光线)相关的知识,希望对你有一定的参考价值。

体积光在现实世界中也叫丁达尔光

体积光在网上也有很多个版本,但是大多数是 以 光照贴图作为 参考最想进行比较。 这样的尽管可以很轻松方便的实现出 体积光,但是他的硬伤确实 精细度太低了,完全无法复原出 体积光该有的 朦胧的感觉。(如下)https://blog.csdn.net/weixin_32938207/article/details/113035729

实现效果如下

黄昏

清晨

光线模拟


其基本原理很简单 ,网上的原理是根据光照深度map于当前的世界坐标点做对比,如果高于 光照深度map,就进行采样计算光照。呢么同理,我们是需要把光照深度map替换成自定义的太阳Depth图。这么一说可能跨度有点大,那么下面我们一步一步仔细给大家讲解实现过程。

丁达尔光线物理原理

丁达尔光是什么

当一束光线透过胶体,从入射光的垂直方向可以观察到胶体里出现的一条光亮的“通路”,其原理是光被悬浮的胶体粒子所散射。所以胶体粒子散射入射光产生了肉眼可见的光路,被称作丁达尔效应。

丁达尔效应的发现

约翰·丁达尔是英国著名物理学家。他于1869 年发现了丁达尔效应,也是首次解释了为什么天空是蓝色的科学家。
当光线射向分散体系时,只有一部分光能够通过体系,剩余部分则被反射、散射或吸收。体系内物质的化学组成决定了光的吸收量,而体系的分散程度决定了光的散射和反射量。当分散相粒子直径大于入射光的波长时,主要发生光的反射和折射;当入射光照射到直径小于光波长的分散粒子时,则主要发生散射,这时观察到的是光波环绕微粒而向四周放射的光,称为散射光或乳光。丁达尔效应 就是光的散射现象或称乳光现象。
这里 我们可以发现,我们要实现体积光找,我们必须沿着视角方向的光路,进行逐段落采样,获取该光点的光线信息(光的吸收量,散射和反射量等)

自定义太阳
我们获取深度图的第一个方式 就是通过摄像机获取他的深度贴图,这是我们要注意性能的效果,但是获取深度贴图是一个比较耗时的操作,我们不能直接通过depthTextureMode获取深度图,毕竟代价很大,所以我们需要自己申请两个rendertexture,用来存储从GPU传过来的深度贴图和颜色贴图

   void Start()
    
        colorRT = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Default);
        depthRT = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.Depth);
    
    private void OnPreRender()
    
        cam.SetTargetBuffers(colorRT.colorBuffer, depthRT.depthBuffer);
    

自定义太阳视椎体平面提取

若通过解析方法计算射线与探照灯的投影锥体表面交点,需要首先得到该锥形体的平面方程。Gribb 等人提出了通过投影矩阵快速得到投影平锥体6个平面方程的方法[5],这里简单介绍一下其思路。

设从世界空间到摄像机观察空间的矩阵为 V,投影矩阵 P,我们令 M=PV 为摄像机的 ViewProjection 矩阵。对于空间中任意一点 p = (x, y, z, 1),通过与 ViewProjection 矩阵相乘得到其在裁剪空间下的坐标,并经过齐次除法后得到 NDC 坐标。
如果我们令 ViewProjection 矩阵中4的4行分别为 m_1, m_2, m_3, m_4 ,即

那么 p 在裁剪空间下的坐标为

p 在 NDC 空间下的坐标可以通过下式计算

不妨以左裁剪平面为例,其平面方程在 NDC 空间下为 x_NDC = -1 ,即

即是一个平面的标准方程 Ax + By + Cz + D = 0 的向量形式 (A, B, D, D) dot (x, y, z, 1) = 0 ,而平面的法向量即是 n=(A, B, C) ,也可以直接从该式得到,但该式并没有考虑法向量的正负方向。

采样频率

最为一个 很重要的性能消耗指标,采样频率直接影响到了 游戏的帧率。我们在沿视角方向的光路 进行采样的时候,会矩阵转换,深度图提取,光照衰减,以及强度等运算,所以一个很简单的想法就是 减少光路的采样频率,但是随之而来的就是很严重的失真效果

呢我们是否可以通过模糊的方式来减少 因为采样过低二造成的梯度显示效果的呢?
Blue Noise
Blue Noise 是一种高频的随机噪声。但 Blue Noise 无法实时生成。开发者 Chistoph Petter 的博客中对 Blue Noise 做了更深入的介绍,在其中也能找到他生成的各种格式和尺寸的 Blue Noise Texture (CC0 授权)http://momentsingraphics.de/BlueNoise.html
此外也有通过去除 White Noise 中的低频成分得到高频的 Blue Noise 的近似实现,此方法也能用于生成各种颜色的噪声(https://blog.demofox.org/2017/1


但是很不幸的消息是,做过对应的早点随机采样之后,梯度的效果会消失,但是随之而来的确实黑白不同的颗粒感,如上图所以。

所以我们这时候需要进行后处理大气模糊的效果可以参考https://blog.csdn.net/weixin_51327051/article/details/123031360。最终效果如下图

总结
代碼庫
https://github.com/ChenZeMing-xiaoming/Dingder
性能方面的消耗还是有的 ,毕竟需要向真实世界的渲染效果方向努力,所以作为给硬件擦屁股的优化指标,也要把我折磨的快疯掉了(不断的在性能优化 和 渲染效果取最优解)。

UnityShader镜面反射计算与反射光向量推导

面试考察频率:??

注:本文章主要讲解计算方法及推导,原理暂不讲解

什么是镜面反射?

? 镜面反射(Specular)又叫高光反射,主要可以来模拟非常光滑的平面受到光线照射所产生的反射效果,使得物体看起来更光滑有光泽。

如何实现镜面反射?

? 镜面反射主要有两种实现方式:
? Phong模型:
技术图片
? 该模型中关键步骤就是计算反射光线。需要提前知道的信息有:
? Ks(物体材质的高光反射颜色即材质rgb分量),lightColor(光的颜色即光的rgb分量),shininess(为反光度系数,越大亮点越小)。
? 需要计算:R反射光向量V观察点向量)这两个为计算重点。
? 反射光直接使用shader中的reflect函数就可以计算,在这里我们手动进行推导一下:
技术图片
? ? 在计算之前,我们只知道入射光向量(AO)和法向量(OP‘),我们需要求的OB。推导如下:
技术图片
? 现在我们知道了想要求出OB,还必须要求出来OP,但我们现在只知道OP’,OP怎么求呢?推导如下:
技术图片
? 现在我们求出了OB=AO+2OP。OP=-(AO·N)N。最后联立两式可得:OP=AO-(AO·N)*N。这里的OP就是反射光向量R。切记在计算中对法向量单位化是很重要的一步。
? 计算观察点向量就简单多了,只需要知道相机的位置就可以,Shader中内置变量_WorldSpaceCameraPos 便捷的提供了相机位置。_WorldSpaceCameraPos-目标点就求出了观察点向量V。最后所有的值都求了出来,就可以计算了。

? Blinn-Phong模型:
技术图片
该模型是对Phong的改进,为了解决反射光计算的耗时问题。
? Ks(物体材质的高光反射颜色即材质rgb分量),lightColor(光的颜色即光的rgb分量),shininess(为反光度系数,越大亮点越小)。
? 需要计算:H入射光向量与观察点向量的中间向量)。计算很简单:normalize(入射光向量+观察点向量),入射光向量已知,观察点向量还和上面的计算方法一样。

两模型的对比

技术图片
可见Blinn-Phong模型的计算要简单快速很多。

以上是关于UnityShader 基于物理的体积光(丁达尔光线)的主要内容,如果未能解决你的问题,请参考以下文章

为啥3dmax做体积光要用阴影贴图?vr阴影为啥不行?请大神说下原理,谢谢您!

从强度计算光体积半径

光学基于matlab GS算法高斯光转换成高阶高斯光+一阶空心高斯光+贝塞尔高斯光含Matlab源码 2166期

Unity ShadersShadowGun系列之二——雾和体积光

UnityShader之光照

光学基于matlab光折射反射(不同界面)含Matlab源码 2372期