UnityShader27:屏幕雾效
前置:UnityShader26:运动模糊
一、Linear01Depth & LinearEyeDepth
在前一章中,提到过这两个方法:
- LinearEyeDepth(d):将获得视角空间下的线性深度值,范围为[Near, Far]
- Linear01Depth(d):由 LinearEyeDepth(d) / Far 得到,范围为[Near/Far, 1]
如果尝试用公式递推的话:
根据《OpenGL基础29:深度测试》的内容可以得到:,其中
为远平面,
为近平面
因此可以反推出 ,其中
,但考虑到 Unity 使用的视角空间中,摄像机正向对应的 z 值均为负值,因此前者还要将
取反,得到
,这个公式正是 LinearEyeDepth(d)!
做个简单除法就可得出 ,这个公式是 Linear01Depth(d)
但是,这些东西都很简单,在《UnityShader入门精要》这本书中也有一样的步骤,而现在已经是 2021 年了,随着平台的发展,不少的地方已经采用了深度反转(Reversed direction)的计策(这个在上一章里也提到过),NDC 空间中的深度值 z 范围不再是 [-1, 1] 而是 [1, 0],这主要取决于 P 矩阵的特殊处理,因此上面的公式结果在这些平台下当然就不再正确
在 UntiyShader 中,可以使用 UNITY_REVERSED_Z 宏来判断是否有进行深度反转
尽管如此,计算方法还是如出一辙,如果进行了深度反转,类比推理得到的公式为
以及
这些都可以通过 Unity 内置变量以及 UnityCG.cginc 内函数实现确认:
// Values used to linearize the Z buffer (http://www.humus.name/temp/Linearize%20depth.txt)
// x = 1-far/near
// y = far/near
// z = x/far
// w = y/far
// or in case of a reversed depth buffer (UNITY_REVERSED_Z is 1)
// x = -1+far/near
// y = 1
// z = x/far
// w = 1/far
float4 _ZBufferParams;
// Z buffer to linear 0..1 depth
inline float Linear01Depth( float z )
{return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}
// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}
二、根据深度缓冲重建世界空间坐标
这个在前一章中也实现过了,方法是传入摄像机的逆 VP 矩阵到着色器,然后在片段着色器中进行空间变换
这样的方法没有问题,只不过在片段着色器中计算矩阵乘法可能有点吃性能,现在提供另一个重建世界空间坐标的方法
我们能够很轻松的拿到摄像机的世界空间坐标 ,以及通过 LinearEyeDepth() 方法得到的线性深度值
,这个
为当前片段所在世界坐标与摄像机 z 方向上的距离
将问题简单化,先假设这个片段所对应世界坐标上的点正好落入图中 这个向量所在直线上,那么如何得到摄像机到这个点的向量 Dist 呢?如果我们能拿到这个向量,那么当然就可以拿到这个点的坐标(废话)
定义 为摄像机到近裁平面的向量,
为摄像机到近裁平面左上角顶点的向量,那么根据相似三角形可以得到:
,根据对称性,另外3个顶点的公式与此完全一致
完美,考虑到屏幕后处理本身就是渲染到一个刚好填充整个屏幕的四边形面片,而这个四边形面片正是图片中的 、
、
和
,因此,我们可以直接将计算的结果
、
、
及
传入顶点着色器,这样到片段着色器后,就可以直接通过
计算得到片段所对应世界坐标了,其中
就是前面4个顶点向量插值后得到的
现在问题就变成:如何求出向量 ?
这个更简单,根据图中可得:
然后对于 和
又有
,其中
为摄像机竖直方向的视角范围
,其中
为摄像机平面横纵比
对应的代码如下:
using UnityEngine;
using System.Collections;public class Fog: PostEffectsBase
{public Shader shader;private Material _material;public Material material{get{_material = CheckShaderAndCreateMaterial(shader, _material);return _material;}}private Camera _myCamera;public Camera myCamera{get{if (_myCamera == null)_myCamera = GetComponent<Camera>();return _myCamera;}}[Range(0.0f, 3.0f)]//雾的浓度public float fogDensity = 1.2f;//雾的颜色public Color fogColor = Color.white;//世界坐标y轴大于fogEnd的部分不会有雾,小于fogStart的部分雾的浓度最高,在[fogStart, fogEnd]范围内雾的浓度和高度呈反比public float fogStart = -1.5f;public float fogEnd = 3.0f;void OnEnable(){//通知摄像机需要深度纹理,此后可以在着色器中使用//https://docs.unity3d.com/2021.1/Documentation/ScriptReference/DepthTextureMode.htmlmyCamera.depthTextureMode |= DepthTextureMode.Depth;}void OnRenderImage(RenderTexture src, RenderTexture dest){if (material != null){Matrix4x4 frustumCorners = Matrix4x4.identity;//获得摄像机的竖直方向视角范围,单位为角度float fov = myCamera.fieldOfView;//获得摄像机与近裁平面的距离float near = myCamera.nearClipPlane;//获得摄像机平面纵横比float aspect = myCamera.aspect;//Mathf.Deg2Rad:同等于2PI/360,可以将角度转换为弧度,Mathf.Deg2Rad相反float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);Vector3 toRight = myCamera.transform.right * halfHeight * aspect;Vector3 toTop = myCamera.transform.up * halfHeight;Vector3 topLeft = myCamera.transform.forward * near + toTop - toRight;//VectorX.magnitude:获得向量长度float scale = topLeft.magnitude / near;//向量归一化topLeft.Normalize();topLeft *= scale;Vector3 topRight = myCamera.transform.forward * near + toRight + toTop;topRight.Normalize();topRight *= scale;Vector3 bottomLeft = myCamera.transform.forward * near - toTop - toRight;bottomLeft.Normalize();bottomLeft *= scale;Vector3 bottomRight = myCamera.transform.forward * near + toRight - toTop;bottomRight.Normalize();bottomRight *= scale;frustumCorners.SetRow(0, bottomLeft);frustumCorners.SetRow(1, bottomRight);frustumCorners.SetRow(2, topRight);frustumCorners.SetRow(3, topLeft);material.SetMatrix("_FrustumCornersRay", frustumCorners);material.SetFloat("_FogDensity", fogDensity);material.SetColor("_FogColor", fogColor);material.SetFloat("_FogStart", fogStart);material.SetFloat("_FogEnd", fogEnd);Graphics.Blit(src, dest, material);}else{Graphics.Blit(src, dest);}}
}
- Camera.fieldOfView:获得摄像机的竖直方向视角范围,单位为角度
- Camera.nearClipPlane:获得摄像机与近裁平面的距离
- Camera.aspect:获得摄像机平面纵横比
- Mathf.Deg2Rad:同等于2PI/360,可以将角度转换为弧度
- VectorX.magnitude:获得对应向量长度
- VectorX.Normalize():向量归一化
需要注意的是:这些都是在透视投影下的计算,如果摄像机为正交投影,就需要用到不同的公式。当然篇幅有限,这里就不详细给出步骤了
三、屏幕雾效
下面计算雾效的方法非常简单,步骤如下:
- 对于当前片段,按照上一节的方法得到世界空间坐标
- 以世界空间坐标的 y 轴为参数,根据类插值公式得出当前点的雾效系数 F
- 根据雾效系数 F 来混和片段颜色和雾颜色
关键在于雾效系数 F 的计算,一般有三种方法(其中 为距离参数):
- 线性(Linear):
,其中
和
分别为上界和下界
- 指数(Exponential):
,其中
为浓度参数
- 指数平方(Exponential Squared):
,其中
为浓度参数
这里使用第一种:
Shader "Jaihk662/Fog"
{Properties{_MainTex("Base (RGB)", 2D) = "white" {}_FogDensity("Fog Density", Float) = 1.0_FogColor("Fog Color", Color) = (1, 1, 1, 1)_FogStart("Fog Start", Float) = 0.0_FogEnd("Fog End", Float) = 1.0}CGINCLUDE#include "UnityCG.cginc"sampler2D _MainTex;half _FogDensity;fixed4 _FogColor;float _FogStart;float _FogEnd;//如果设置了 myCamera.depthTextureMode |= DepthTextureMode.Depth,那么这里就可以通过 _CameraDepthTexture 拿到深度图sampler2D _CameraDepthTexture;half4 _MainTex_TexelSize;float4x4 _FrustumCornersRay;struct vert2frag{float4 pos: SV_POSITION;half4 uv: TEXCOORD0;float4 ray : TEXCOORD2;};vert2frag vert(appdata_img v){vert2frag o;o.pos = UnityObjectToClipPos(v.vertex);o.uv.xy = v.texcoord; o.uv.zw = v.texcoord;//对深度纹理进行平台差异化处理#if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0.0)o.uv.w = 1.0 - o.uv.w;#endifint index = 0;if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5)index = 0;else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5)index = 1;else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5)index = 2;elseindex = 3;//当在 DirectX 平台上使用渲染到纹理技术时,Unity 会自动为我们翻转屏幕图像纹理,所以大部分情况下我们都不需要关系在意纹理翻转问题,但是有特殊情况:一个例子就是开启抗锯齿后再对得到的渲染纹理进行后处理时,这些图像在竖直方向的朝向就可能是不同的//在这些情况下,_MainTex_TexelSize.y 就会为负值,我们需要对索引也进行翻转#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)index = 3 - index;#endifo.ray = _FrustumCornersRay[index];return o;}fixed4 frag(vert2frag i): SV_Target{float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.zw));float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.ray.xyz;float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); fogDensity = saturate(fogDensity * _FogDensity);fixed4 finalColor = tex2D(_MainTex, i.uv);finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);return finalColor;}ENDCGSubshader{ZTest Always Cull Off ZWrite OffPass{CGPROGRAM#pragma vertex vert#pragma fragment fragENDCG}}FallBack Off
}
不过,这样实现的雾效有三个缺陷:
- 如果你的某个视角方向上没有任何物体(看到了背景的天空盒),这时对应片段所在世界空间坐标将会是摄像机的远裁平面,可想而知这会对本案例中雾的计算带来错误的插值结果,因此可以考虑设置背景颜色为雾的颜色,又或者调小摄像机的远裁平面
- 雾的浓度太过“均匀”了,它在某些时候应该是看起来飘渺的
- 真正的雾效应为场景雾效,因此想要表现出现实世界的雾的效果,就不能用简单的后处理了(可以想象从天上往下看一个被雾笼罩城市的感觉)
这些问题可以留到后面再解决吧
四、非均匀雾效
对于上面缺陷②的优化,只需要加一个噪声纹理就可以了:
着色器部分修改如下:
fixed4 frag(vert2frag i): SV_Target
{float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.zw));float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.ray.xyz;float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount;float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); fogDensity = saturate(fogDensity * _FogDensity * (1 + noise));fixed4 finalColor = tex2D(_MainTex, i.uv);finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);return finalColor;
}
参考资料:
- https://zhuanlan.zhihu.com/p/119145598
- https://zhuanlan.zhihu.com/p/92315967
UnityShader27:屏幕雾效相关推荐
- unity 继承会调用start吗_【浅入浅出】Unity 雾效
大家好,我是Shawn.如何实现"雾效"?"在Unity中,是有自带的雾效的,在Lighting窗口下,在other Settings就可以找到fog选项了,启用fog就 ...
- 闲云野鹤:吃鸡(二)之场景制作—雾效的制作
试了下unity自带的雾效,感觉最大的问题就是没有包括天空盒,远处处于天空背景的物体显得不真实,如下图. 因此决定还是自己动手写一个脚本,最终效果如下图:(雾的垂直厚度和水平浓度可调) 思路:很 ...
- Unity Shader - URP Fog - URP 管线下的雾效
文章目录 参考 LitForwardPass.hlsl 临摹使用 Test/URPFog 只要 Fog_Linear 变体的 效果 问题 修复 References 管线:URP URP:7.7.1 ...
- 【浅墨Unity3D Shader编程】之四 热带雨林篇: 剔除、深度测试、Alpha测试以及基本雾效合辑
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41923661 作者:毛星云(浅墨) ...
- 【Unity3D Shader编程】之四 热带雨林篇: 剔除、深度测试、Alpha测试以及基本雾效合辑
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41923661 作者:毛星云(浅墨) ...
- 【Unity3D Shader编程】之四 热带雨林篇 剔除 深度测试 Alpha测试以及基本雾效合辑
分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow 本系列文章由@浅墨 ...
- Unity3D Shader编程】之四 热带雨林篇: 剔除、深度测试、Alpha测试以及基本雾效合辑
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41923661 作者:毛星云(浅墨) ...
- UnityShader入门精要——全局雾效
基于屏幕后处理的全局雾效的关键是,根据深度纹理来重建每个像素在世界空间下的位置.我们在模拟运动模糊时已经实现了这个要求,即构建出当前像素的NDC坐标,再通过当前摄像机的视角*投影矩阵的逆矩阵来得到世界 ...
- 【Unity3D】流动雾效
1 前言 屏幕深度和法线纹理简介中对深度和法线纹理的来源.使用及推导过程进行了讲解,激光雷达特效中讲述了一种重构屏幕像素点世界坐标的方法,本文将介绍使用深度纹理重构屏幕像素点在相机坐标系下的坐标计算方 ...
最新文章
- QListWidget 小练习
- 命名管道 win7未响应_大数据分析Python建立分析数据管道
- 异常检测-LocalOutlierFactor的理解与应用
- CentOS7 iso封装语句
- Tachyon更名为 Alluxio,并发布1.0版本
- 【单元测试框架unittest】
- Python 爬虫技巧
- PHP在微博优化中的“大显身手”
- Linux安装docker及docker基本操作
- 设计模式之——原型模式
- 四旋翼无人机的动力学模型
- 使用对话框模板创建一个InputBox()在C + +
- mysql ndb存储引擎_ndb 存储引擎
- cad断点快捷键_CAD打断命令怎么使用,快捷键是什么
- excel链接到另一个工作表的指定位置
- cncert网络安全周报35期 境内被植入后门的政府网站112个 环比上涨24.4%
- 漂亮的Adapter模式-体会RecyclerView的设计实现
- 关于flask入门教程-ajax+echarts实现关系图
- 计算机电源5v有多大电流,计算机电源多大?
- Spring源码学习:BeanPostProcessor注册和执行时机
热门文章
- python对于设计师有什么用-好的IT产品设计师要做到哪些事
- python是什么 自学-自学Python买什么书?
- python必备基础代码-python基础知识和练习代码
- python工资高还是java-Python工资高还是Java工资高?Python和Java学哪个?
- python入门指南-Python完全小白入门指南
- 计算机典型的操作系统有,计算机操作系统典型示例.doc
- html网页字段序号的样式,[网页设计]局部自定义li序号CSS样式的方法
- 空间说说秒赞java_人生靠反省,Java靠泛型
- php url参数用–,php获取URL各部分参数
- Element UI el-table 表格多选的使用