1 前言

屏幕深度和法线纹理简介中对深度和法线纹理的来源、使用及推导过程进行了讲解,激光雷达特效中讲述了一种重构屏幕像素点世界坐标的方法,本文将介绍使用深度纹理重构屏幕像素点在相机坐标系下的坐标计算方法,并使用重构后的坐标模拟雾效,再结合噪声纹理实现流动的雾效。

雾效即离观察者越远的点越趋近于雾的颜色,并且雾的浓度越大。本文将使用屏幕后处理技术,计算每个顶点与相机的距离,并根据距离计算雾的浓度,依据浓度给该像素点混合原始颜色与雾效颜色。

本文完整资源见→Unity3D流动雾效。

2 由深度纹理重构相机坐标系下坐标

1)重构像素点在相机坐标系下坐标

对于屏幕上的任意一点,它对应的相机坐标系中的点记为 P,对应的近裁剪平面上的点记为 Q,相机位置记为 O(坐标为 (0, 0, 0)),假设 P 点的深度为 depth(由 LinearEyeDepth 函数获取),相机到近平面的距离为 near,如下图所示。

根据上图,可以列出以下方程组关系。其中,公式 2 由三角形相似原理得到,公式 3 由 O、P、Q 三点共线得到。

化简得:

Q 点在近平面上,可以通过近裁剪平面的四个角插值得到,O 和 near 为定值,因此 (OQ / near) 也可以通过插值得到。假设近裁剪平面的四个角分别为 A、B、C、D,我们将 (OA  / near)、(OB  / near)、(OC  / near)、(OD  / near) 输入顶点着色器中,光珊化会自动为我们计算插值后的 (OQ  / near)。

如下,我们可以在插值寄存器中定义变量 interpolatedRay,用于存储向量 (OQ / near)。

struct v2f {float4 pos : SV_POSITION; // 裁剪空间顶点坐标half2 uv : TEXCOORD0; // 纹理uv坐标, float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向近平面上点的向量除以near后的坐标)
};

2)近裁剪平面四角射线向量计算

记近裁剪平面上左下角、右下角、右上角、左上角、中心、右中心、上中心顶点分别为 A、B、C、D、Q、E、F,相机位置为 O 点,如下:

根据几何关系,可以计算向量 OA、OB、OC、OD 如下:

假设像机竖直方向的视野角度为 fov(通过 camera.fieldOfView 获取),屏幕宽高比为 aspect(通过 camera.aspect 获取),相机距离近裁剪平面的距离为 near(通过 camera.nearClipPlane 获取),相机向右、向上、向前方向的单位方向向量分别为 right(坐标为 (1, 0, 0))、up(坐标为 (0, 1, 0)、forward(坐标为 (0, 0, 1),则向量 OQ、QE、QF 的计算如下:

假设摄像机竖直方向的视野角度为 fov(通过 camera.fieldOfView 获取),屏幕宽高比为 aspect(通过 camera.aspect 获取),相机距离近裁剪平面的距离为 near(通过 camera.nearClipPlane 获取),相机向右、向上、向前方向的单位方向向量分别为 right、up、forward(通过 camera.transform 组件获取),则向量 OQ、QE、QF 的计算如下:

2 雾效因子计算

雾效因子的计算一般采样线性衰减、反比衰减、指数衰减等方法。假设顶点与相机间的距离为 dist,雾效因子为 factor,则 factor 的计算如下。

1)线性衰减雾效因子

其中,dist_min 和 dist_max 分别为受雾效影响的最小距离和最大距离。

2)反比衰减雾效因子

其中,r 为衰减速率参数。

3)指数衰减雾效因子

其中,r 为衰减速率参数。

3 雾效实现

FogEffect.cs

using UnityEngine;[RequireComponent(typeof(Camera))] // 需要相机组件
public class FogEffect : MonoBehaviour {public Material material = null; // 材质private Camera cam; // 相机private void Awake() {cam = GetComponent<Camera>();material.hideFlags = HideFlags.DontSave;}private void OnEnable() {cam.depthTextureMode |= DepthTextureMode.Depth;}private void Update() {float scroll = Input.GetAxis("Mouse ScrollWheel");if (Mathf.Abs(scroll) > 0){ // 缩放场景cam.transform.position += cam.transform.forward * scroll * 15;}}private void OnRenderImage(RenderTexture src, RenderTexture dest) {if (material != null) {Matrix4x4 frustumCorners = GetFrustumCornersRay();material.SetMatrix("_FrustumCornersRay", frustumCorners);Graphics.Blit(src, dest, material);} else {Graphics.Blit(src, dest);}}private Matrix4x4 GetFrustumCornersRay() { // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)Matrix4x4 frustumCorners = Matrix4x4.identity;float fov = cam.fieldOfView;float near = cam.nearClipPlane;float aspect = cam.aspect;float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);Vector3 toRight = Vector3.right * halfHeight * aspect; // 指向右方的向量Vector3 toTop = Vector3.up * halfHeight; // 指向上方的向量Vector3 toForward = Vector3.forward * near; // 指向前方的向量Vector3 bottomLeft = (toForward - toTop - toRight) / near; // 指向左下角的射线Vector3 bottomRight = (toForward + toRight - toTop) / near; // 指向右下角的射线Vector3 topRight = (toForward + toRight + toTop) / near; // 指向右上角的射线Vector3 topLeft = (toForward + toTop - toRight) / near; // 指向左上角的射线frustumCorners.SetRow(0, bottomLeft);frustumCorners.SetRow(1, bottomRight);frustumCorners.SetRow(2, topRight);frustumCorners.SetRow(3, topLeft);return frustumCorners;}
}

说明:FogEffect 脚本组件挂在相机上。

FogEffect.shader

Shader "MyShader/FogEffect" { // 雷达波特效Properties{_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理_FogColor("Fog Color", Color) = (1, 0, 0, 1) // 雾的颜色_MinDist("Min Dist", Range(0, 20)) = 1 // 雾的最近距离(线性衰减函数才生效)_MaxDist("Max Dist", Range(30, 1000)) = 1000 // 雾的最远距离(线性衰减函数才生效)_R("Density", Range(0, 1)) = 0.01 // 衰减速率参数(反比衰减和指数衰减函数才生效)}SubShader{Pass {// 深度测试始终通过, 关闭深度写入ZTest Always ZWrite OffCGPROGRAM#include "UnityCG.cginc"#pragma vertex vert#pragma fragment fragsampler2D _MainTex; // 主纹理sampler2D _CameraDepthTexture; // 深度纹理float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)fixed4 _FogColor; // 雾的颜色float _MinDist; // 雾的最近距离(线性衰减函数才生效)float _MaxDist; // 雾的最远距离(线性衰减函数才生效)float _R; // 衰减速率参数(反比衰减和指数衰减函数才生效)struct v2f {float4 pos : SV_POSITION; // 裁剪空间顶点坐标half2 uv : TEXCOORD0; // 纹理uv坐标, float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向近平面上点的向量除以near后的坐标)};float4 getInterpolatedRay(half2 uv) { // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)int index = 0;if (uv.x < 0.5 && uv.y < 0.5) {index = 0;} else if (uv.x > 0.5 && uv.y < 0.5) {index = 1;} else if (uv.x > 0.5 && uv.y > 0.5) {index = 2;} else {index = 3;}return _FrustumCornersRay[index];}float getFactory(float len) { // 获取雾效因子float factor = saturate((_MaxDist - len) / (_MaxDist - _MinDist)); // 线性雾效衰减因子//float factor = 1 / (_R * len + 1); // 反比雾效衰减因子//float factor = exp(-_R * len); // 指数雾效衰减因子return factor;}v2f vert(appdata_img v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)o.uv = v.texcoord;o.interpolatedRay = getInterpolatedRay(v.texcoord); // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)return o;}fixed4 frag(v2f i) : SV_Target{ // v2f_img为内置结构体, 里面只包含pos和uvfloat depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).rfloat linearDepth = LinearEyeDepth(depth); // 线性的深度//if (linearDepth > _ProjectionParams.z - 2) {//  return tex2D(_MainTex, i.uv); // 天空不参与雾效//}float3 viewPos = linearDepth * i.interpolatedRay.xyz; // 顶点在相机坐标系下的坐标(不是观察坐标系, 观察坐标系与相机坐标系z轴方向相反)float len = length(viewPos);float factor = getFactory(len); // 获取雾效因子fixed4 tex = tex2D(_MainTex, i.uv);fixed4 color = lerp(_FogColor, tex, factor);return color;}ENDCG}}FallBack off
}

说明:在 Assets 目录下面新建 Resources 目录,接着在 Resources 目录下面创建材质,重命名为 FogMat,将 FogEffect.shader 与 FogMat 材质绑定,再将 FogMat 拖拽到相机对象的 FogEffect 脚本组件里。

运行效果:

4 流动雾效实现

流动雾效原理:在第 3 节的基础上,对雾效因子 factor 进行周期性偏移(偏移量记为 offset),从而实现雾的流动效果,为使雾呈现团状,偏移量可以从团状的噪声纹理中采样得到。如下是一些常用的噪声纹理。

这些噪声纹理都有以下共同点:

  • 在较小的邻域范围内,灰度是渐变的,使得模拟的雾效效果更加和谐;
  • 将它们向四周平铺展开,边界处刚好衔接,使得模拟的雾效效果更加自然;

DynamicFogEffect.shader

Shader "MyShader/DynamicFogEffect" { // 雷达波特效Properties{_MainTex("Base (RGB)", 2D) = "white" {} // 主纹理_NoiseTex("Noise Tex", 2D) = "white" {} // 噪声纹理_FogColor("Fog Color", Color) = (1, 0, 0, 1) // 雾的颜色_MinDist("Min Dist", Range(0, 20)) = 1 // 雾的最近距离(线性衰减函数才生效)_MaxDist("Max Dist", Range(30, 1000)) = 1000 // 雾的最远距离(线性衰减函数才生效)_R("Density", Range(0, 1)) = 0.01 // 衰减速率参数(反比衰减和指数衰减函数才生效)_SpeedX("Speed X", Range(0, 1)) = 0.45 // 雾气在水平方向飘动的速度_SpeedY("Speed Y", Range(0, 1)) = 0.3 // 雾气在竖直方向飘动的速度_NoiseScale("Noise Scale", Range(0, 1)) = 0.3 // 噪声放大系数}SubShader{Pass {// 深度测试始终通过, 关闭深度写入ZTest Always ZWrite OffCGPROGRAM#include "UnityCG.cginc"#pragma vertex vert#pragma fragment fragsampler2D _MainTex; // 主纹理sampler2D _NoiseTex; // 噪声纹理sampler2D _CameraDepthTexture; // 深度纹理float4x4 _FrustumCornersRay; // 视锥体四角射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)fixed4 _FogColor; // 雾的颜色float _MinDist; // 雾的最近距离(线性衰减函数才生效)float _MaxDist; // 雾的最远距离(线性衰减函数才生效)float _R; // 衰减速率参数(反比衰减和指数衰减函数才生效)float _SpeedX; // 雾气在水平方向飘动的速度float _SpeedY; // 雾气在竖直方向飘动的速度float _NoiseScale; // 噪声放大系数struct v2f {float4 pos : SV_POSITION; // 裁剪空间顶点坐标half2 uv : TEXCOORD0; // 纹理uv坐标, float4 interpolatedRay : TEXCOORD1; // 插值射线向量(由相机指向近平面上点的向量除以near后的坐标)};float4 getInterpolatedRay(half2 uv) { // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)int index = 0;if (uv.x < 0.5 && uv.y < 0.5) {index = 0;} else if (uv.x > 0.5 && uv.y < 0.5) {index = 1;} else if (uv.x > 0.5 && uv.y > 0.5) {index = 2;} else {index = 3;}return _FrustumCornersRay[index];}float getFactory(float len) { // 获取雾效因子float factor = saturate((_MaxDist - len) / (_MaxDist - _MinDist)); // 线性雾效衰减因子//float factor = 1 / (_R * len + 1); // 反比雾效衰减因子//float factor = exp(-_R * len); // 指数雾效衰减因子return factor;}v2f vert(appdata_img v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)o.uv = v.texcoord;o.interpolatedRay = getInterpolatedRay(v.texcoord); // 获取插值射线向量(由相机指向近平面上四个角点的向量除以near后的坐标)return o;}fixed4 frag(v2f i) : SV_Target{ // v2f_img为内置结构体, 里面只包含pos和uvfloat depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).rfloat linearDepth = LinearEyeDepth(depth); // 线性的深度//if (linearDepth > _ProjectionParams.z - 2) {//  return tex2D(_MainTex, i.uv); // 天空不参与雾效//}float3 viewPos = linearDepth * i.interpolatedRay.xyz; // 顶点在相机坐标系下的坐标(不是观察坐标系, 观察坐标系与相机坐标系z轴方向相反)float len = length(viewPos);float factor = getFactory(len); // 获取雾效因子float2 dist = float2(_SpeedX, _SpeedY) * _Time.y; // 雾气移动的距离float offset = (tex2D(_NoiseTex, i.uv + dist).r - 0.5) * _NoiseScale; // 雾效因子偏移量factor = saturate(factor * (1 + offset));fixed4 tex = tex2D(_MainTex, i.uv);fixed4 color = lerp(_FogColor, tex, factor);return color;}ENDCG}}FallBack off
}

说明:在 Assets 目录下面新建 Resources 目录,接着在 Resources 目录下面创建材质,重命名为 DynamicFogMat,将 DynamicFogEffect.shader 与 DynamicFogMat 材质绑定,并将噪声纹理拖拽到 DynamicFogMat 的 Noise Tex 中,最后将 DynamicFogMat 拖拽到相机对象的 FogEffect 脚本组件里。

运行效果:

【Unity3D】流动雾效相关推荐

  1. 【浅墨Unity3D Shader编程】之四 热带雨林篇: 剔除、深度测试、Alpha测试以及基本雾效合辑

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41923661 作者:毛星云(浅墨) ...

  2. 【Unity3D Shader编程】之四 热带雨林篇: 剔除、深度测试、Alpha测试以及基本雾效合辑

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41923661 作者:毛星云(浅墨) ...

  3. 【Unity3D Shader编程】之四 热带雨林篇 剔除 深度测试 Alpha测试以及基本雾效合辑

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow 本系列文章由@浅墨 ...

  4. Unity3D Shader编程】之四 热带雨林篇: 剔除、深度测试、Alpha测试以及基本雾效合辑

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41923661 作者:毛星云(浅墨) ...

  5. unity3D小小白之雾效

    Fog 为雾效,是一种渲染.可以设置雾 的颜色和雾的密度,雾效开启后远处的物体会被罩上雾. 雾效的添加方法: Edit--Render Settings 在Inspector视图里勾选Fog便可以开启 ...

  6. Unity3D之天空盒子和雾效

    1.导入素材或者去Asset Store下载素材 这边我是通过Asset Store下载了 二.选择菜单栏中的Window->Lighting->Settings,打开Lighting窗口 ...

  7. unity3D游戏开发九之雾效、水效和音效

    开启Fog(雾效)将会在场景中渲染出雾的效果,在Unity中,可以对雾的颜色.密度等属性进行调整.开启雾效通常用于优化性能,开启雾效后远处的物体被雾遮挡,此时便可选择不渲染距离摄像机较远的物体.这种性 ...

  8. unity3D游戏开发之雾效、水效和音效

    开启Fog(雾效)将会在场景中渲染出雾的效果,在Unity中,可以对雾的颜色.密度等属性进行调整.开启雾效通常用于优化性能,开启雾效后远处的物体被雾遮挡,此时便可选择不渲染距离摄像机较远的物体.这种性 ...

  9. UnityShader27:屏幕雾效

    前置:UnityShader26:运动模糊 一.Linear01Depth & LinearEyeDepth 在前一章中,提到过这两个方法: LinearEyeDepth(d):将获得视角空间 ...

最新文章

  1. websockets_如何将WebSockets与AWS API Gateway和Lambda一起使用来构建实时应用程序
  2. Docker(十):Docker实战 Docker 安装 Nginx
  3. Qt版本中国象棋开发(二)
  4. arcgis几何修复有作用吗_ArcGis拓扑的那些事儿(拓扑应用过程二)
  5. 文华软件登录显示请选择服务器,文华财经提示先登录云服务器
  6. mysql key value 引擎_mysql集成的key-value引擎-个人翻译
  7. python安装不了是什么问题_安装不上python的模块怎么办?别怕,我这有妙招!
  8. CIO大咖专访 | 从实战中提炼的企业数字化转型要点
  9. 《深入理解 Java 虚拟机》把这个知识点讲错了?
  10. python遗传算法_基于Python的遗传算法特征约简(附代码)
  11. 宋宝华: 一图理解终端、会话、 进程组、进程关系
  12. HNUCM-1435 最大最小值(分治法)
  13. 联想IBM笔记本驱动
  14. 咸鱼带你理解信号带宽与信道带宽
  15. 实验1构建多连杆机器人模型
  16. mindspore详解
  17. 跟着团子学SAP PS—项目结算规则的自动生成 CJB2/CJB1 (ETO模式下正确结算规则设定案例)
  18. SCTF | 三足鼎立焦点对抗,天枢战队有惊无险斩获冠军头衔
  19. linux如何卸载lightdm,告诉你Ubuntu安装LightDM的方法及命令
  20. Spring计划会议

热门文章

  1. Android Studio Emulator Process finished with exit code -1073741515 (0xC0000135)
  2. 日常交际技巧经验总结99句(大全)
  3. linux ps命令缺点,Linux pkill和killall命令的缺陷
  4. 一位耳鸣患者的自述---宁心清耳汤
  5. 服务器 用一个独立的硬盘存储文件夹,一、块存储、文件存储、对象存储,三者的本质差别是什么?...
  6. numpy和matlab的多维数组展平:ravel, flatten, reshape, (:)
  7. 应届生数据分析求职记
  8. Travel(dij)
  9. MySQL之CHAR函数的妙用
  10. 深入浅出kubernetes之client-go的SharedInformer