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 Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag sampler2D _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) { // 顶点着色器中只处理4个顶点, 每个顶点对应了1条射线(O点指向**面的四角)
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和uv
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).r
float 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 Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag sampler2D _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) { // 顶点着色器中只处理4个顶点, 每个顶点对应了1条射线(O点指向**面的四角)
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和uv
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); // 非线性的深度, tex2D(_CameraDepthTexture, i.uv).r
float 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】流动雾效

【Unity3D】流动雾效的更多相关文章

  1. [原]Unity3D深入浅出 - 雾效(Fog)

    在Unity中开启雾效的方式:依次选中菜单栏中的 Edit - Render Settings 项,勾选Fog 选框即可开启雾效.雾效的参数如下: Fog Color:雾的颜色. Fog Mode:雾 ...

  2. 【Unity Shaders】Unity里的雾效模拟

    写在前面 熟悉Unity的都知道,Unity可以进行基本的雾效模拟.所谓雾效,就是在远离我们视角的方向上,物体看起来像被蒙上了某种颜色(通常是灰色).这种技术的实现实际上非常简单,就是根据物体距离摄像 ...

  3. Unity中雾效的开启

    原文:https://blog.csdn.net/Rhett_Yuan/article/details/54425236 1.对于雾效的开启在新版的Unity中通过界面菜单Windows->Li ...

  4. Re:《Unity Shader入门精要》13.3全局雾效--如何从深度纹理重构世界坐标

    如何从深度纹理重构世界坐标 游戏特效,后处理是必不可少的,而后处理经常需要我们得到当前画面的像素对应世界空间的所有信息. 思路 通过深度纹理取得NDC坐标,然后再通过NDC坐标还原成世界空间坐标 // ...

  5. 【淡墨Unity3D Shader计划】四 热带雨林的文章: 排除、深度测试、Alpha测试和基本雾编译

    本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://hpw123.net/a/C__/kongzhitaichengxu/2014/1222/163.html 作者:毛星云 ...

  6. Unity3D使用过程中常见的20个问题

    1:天空盒有接缝怎么解决?答:在贴图导入设置里设置Wrap Mode为"Clamp". 2:DDS格式怎么不显示?答:Unity不支持DDS格式,Unity会将除DDS外的其他格式 ...

  7. [Unity3D]图形渲染优化、渲染管线优化、图形性能优化

    原地址:http://blog.sina.com.cn/s/blog_5b6cb9500101dmh0.html 转载请留下本文原始链接,谢谢.本文会不定期更新维护,最近更新于2013.11.09   ...

  8. 【转】unity3d 各种优化综合

      检测方式: 一,unity3d 渲染统计窗口 Game视窗的Stats去查看渲染统计的信息: 1.FPS fps其实就是 frames per second,也就是每一秒游戏执行的帧数,这个数值越 ...

  9. 【淡墨Unity3D Shader计划】五 圣诞用品: Unity在Shader三种形式的控制&amp;混合操作编译

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

  10. Unity3D Adam Demo的学习与研究

      1.简述 这篇文章是对Adam各种相关资料了解后进行一些精简的内容.如果你想仔细研究某个技术请跳转至unity相关页面. Adam官方页面: https://unity3d.com/cn/page ...

随机推荐

  1. [转帖]tidb backup

    https://docs.pingcap.com/zh/tidb/v4.0/sql-statement-restore BACKUP 语句使用的引擎与 BR 相同,但备份过程是由 TiDB 本身驱动, ...

  2. [转帖]看看 Jmeter 是如何玩转 redis 数据库的

    柠檬小欧 2021-08-31 20:06420 Jmeter 作为当前非常受欢迎的接口测试和性能测试的工具,在企业中得到非常广泛的使用,而 Redis 作为缓存数据库,也在企业中得到普遍使用,那如何 ...

  3. [转帖]PostgreSQL 压测工具pgbench

    1.命令 pgbench --help pgbench is a benchmarking tool for PostgreSQL. Usage:   pgbench [OPTION]... [DBN ...

  4. 【转帖】一文解析ethtool 命令的使用

    命令简介 ethtool命令用于查询和控制网络设备驱动程序和硬件设置,尤其是有线以太网设备,devname网卡的名称.网卡就像是交换机的一个端口,正常使用我们只是配置网卡IP地址等信息,网卡的速率.双 ...

  5. 【转帖】io_uring vs epoll ,谁在网络编程领域更胜一筹?

    io_uring vs epoll ,谁在网络编程领域更胜一筹? 2021-12-16 1473举报 简介: 从定量分析的角度,通过量化 io_uring 和 epoll 两种编程框架下的相关操作的耗 ...

  6. [转帖]超线程 Smt 究竟可以快多少?

    https://www.51cto.com/article/686171.html 刚才我们关闭SMT是把CPU10-CPU19全关了,只留下每对里面的1个CPU,也就是留下了CPU0-CPU9. 默 ...

  7. 【转贴】2019.3 学习向SP打造指南 篇一:微软神器Surface产品线全系列详细介绍

    学习向SP打造指南 篇一:微软神器Surface产品线全系列详细介绍 2019-03-01 22:30:00 161点赞 699收藏 141评论 https://post.smzdm.com/p/a5 ...

  8. express实现批量删除和分页

    后端代码批量删除 // 批量删除 router.get('/manyDel', function (req, res) { let { ids } = req.query if (ids&&a ...

  9. 【6】python生成数据曲线平滑处理——(Savitzky-Golay 滤波器、convolve滑动平均滤波)方法介绍,推荐玩强化学习的小伙伴收藏

    相关文章: Python xlwt数据保存到 Excel中以及xlrd读取excel文件画图  先上效果图: 由于高频某些点的波动导致高频曲线非常难看,为了降低噪声干扰,需要对曲线做平滑处理,让曲线过 ...

  10. 6.0 Python 使用函数装饰器

    装饰器可以使函数执行前和执行后分别执行其他的附加功能,这种在代码运行期间动态增加功能的方式,称之为"装饰器"(Decorator),装饰器的功能非常强大,装饰器一般接受一个函数对象 ...