先不看效果,我们先细心想想,生活中看到的雾是怎么个表现?
不过现在基本上都看不到雾了,很多年没有见过,印象中的雾应该在8~9年前,那时候坐着大巴,司机开得很慢很慢,因为雾太浓了,雾的颜色比奶白色暗一些,眼前只有2~5M可视,再远一点就全是雾了。
所以我们确定了一些有用的信息:
雾的颜色,眼前2~5M可视,再远一点就全是雾了。
如:我们常常看到一些综艺节目里,或是音乐节目里整个舞台地表大概:0.2M都是雾。
就是有高度
Scenario 1与2的两种雾起始都是一样的,为什么我们需要做两种区别能:分开实现,在不同场景使用性能更好。
当然你也可以使用Scenario 2中的雾来实现所有的场景,调整一下雾的高度为:很大的值(很高)即可。
我按照自己的理解就是:雾的固状体有密度区别。
密度低一些的雾就会飘得比较高,以至于高到我们整改眼前的视野都包括了,所以我们对这种雾处理为:纯深度雾。
密度高一些的雾就会飘得比较低,就像前面提到的舞台上的雾(一般是一些干冰雾),所以我们处理为:高度雾,只有部分高度可见。
上面只是我自己的理解,百度百科有比较全面的了解,有兴趣点击这里查看:雾
下面讲解以unity 2018.3.0f2 版本为主
我们在创建shader选择unlit shader就得到一个代码模板生成的shader
代码如下
Shader "Unlit/Fog"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
可以看到有各种 Fog
的字眼。
那么下面我们接着看看这些宏对应Built-in Shader里有是什么内容 (这里只看一些主要的部分,不会全面的去看)
// ------------------------------------------------------------------
// Fog helpers
//
// multi_compile_fog Will compile fog variants.
// UNITY_FOG_COORDS(texcoordindex) Declares the fog data interpolator.
// UNITY_TRANSFER_FOG(outputStruct,clipspacePos) Outputs fog data from the vertex shader.
// UNITY_APPLY_FOG(fogData,col) Applies fog to color "col". Automatically applies black fog when in forward-additive pass.
// Can also use UNITY_APPLY_FOG_COLOR to supply your own fog color.
multi_compile_fog
编译雾的变体shader的指令。UNITY_FOG_COORDS(texcoordindex)
定义雾需要的v2f
的插值数据。UNITY_TRANSFER_FOG(outputStruct,clipspacePos)
从vertex shader
顶点着色器输出数据UNITY_APPLY_FOG(fogData,col)
应用雾的颜色来着色。当使用forward-additive lightmode
的pass会自动应用黑色的雾颜色。UNITY_APPLY_FOG_COLOR
来当做是雾的颜色 struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
UNITY_FOG_COORDS
对应UnityCG.cginc 下的源码
// 1
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
#define UNITY_FOG_COORDS(idx) UNITY_FOG_COORDS_PACKED(idx, float1)、
// 2
#define UNITY_FOG_COORDS_PACKED(idx, vectype) vectype fogCoord : TEXCOORD##idx;
可以看到,就是定义一个float1 fogCoord : TEXCOORD[N]
的一个变量。
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
// 1
#define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord.x = unityFogFactor
// 2
#if defined(UNITY_REVERSED_Z)
#if UNITY_REVERSED_Z == 1
//D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]
//max is required to protect ourselves from near plane not being correct/meaningfull in case of oblique matrices.
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
#else
//GL with reversed z => z clip range is [near, -far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(-(coord), 0)
#endif
#elif UNITY_UV_STARTS_AT_TOP
//D3d without reversed z => z clip range is [0, far] -> nothing to do
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#else
//Opengl => z clip range is [-near, far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#endif
#define UNITY_CALC_FOG_FACTOR(coord) UNITY_CALC_FOG_FACTOR_RAW(UNITY_Z_0_FAR_FROM_CLIPSPACE(coord))
// 3
#if defined(FOG_LINEAR)
// factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)
// factor = exp(-density*z)
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2)
// factor = exp(-(density*z)^2)
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
#endif
// 4
CBUFFER_START(UnityFog)
fixed4 unity_FogColor;
// x = density / sqrt(ln(2)), useful for Exp2 mode
// y = density / ln(2), useful for Exp mode
// z = -1/(end-start), useful for Linear mode
// w = end/(end-start), useful for Linear mode
float4 unity_FogParams;
CBUFFER_END
// 5
// x = 1 or -1 (-1 if projection is flipped)
// y = near plane
// z = far plane
// w = 1/far plane
uniform vec4 _ProjectionParams;
可以看到一些有用的信息:
unity_FogParams
:用于CPU将一些除法结果计算好传入到GPU的 CBuffer
中使用的,减少GPU中的除法运算,提升性能。FOG_LINEAR
、FOG_EXP
、FOG_EXP2
分别对应了Unity中的Fog 算法的选择。从三个宏定义的分支可以知道三种算法对应的公式。从这里也可以看得出来,unity的 Fog 使用的是 ClipSpace下的z值来计算FogFactor。
ClipSpace.z
映射到0~far
0~far
的ClipSpace.z
来作为z代入对应公式计算unityFogFactor
UNITY_APPLY_FOG
应用这个unityFogFactor
来lerp(col, fragCol, untiyFogFactor)
从这里我们知道了三种公式,后面会继续说明
#ifdef UNITY_PASS_FORWARDADD
#define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
#else
#define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
#endif
这个可以看到,对应之前的 Helper
里说的,forward add的话,会使用黑色作为雾颜色
#define UNITY_FOG_LERP_COLOR(col,fogCol,fogFac) col.rgb = lerp((fogCol).rgb, (col).rgb, saturate(fogFac))
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
#if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
// mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp the color
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,(coord).x)
#else
// SM3.0 and PC/console: calculate fog factor and lerp fog color
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR((coord).x); UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
#endif
#define UNITY_EXTRACT_FOG(name) float _unity_fogCoord = name.fogCoord
#define UNITY_EXTRACT_FOG_FROM_TSPACE(name) float _unity_fogCoord = name.tSpace2.y
#define UNITY_EXTRACT_FOG_FROM_WORLD_POS(name) float _unity_fogCoord = name.worldPos.w
#define UNITY_EXTRACT_FOG_FROM_EYE_VEC(name) float _unity_fogCoord = name.eyeVec.w
#else
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
#define UNITY_EXTRACT_FOG(name)
#define UNITY_EXTRACT_FOG_FROM_TSPACE(name)
#define UNITY_EXTRACT_FOG_FROM_WORLD_POS(name)
#define UNITY_EXTRACT_FOG_FROM_EYE_VEC(name)
#endif
这里就是最终应用个三计算出 FogFactor
公式,并将 FogFactor
作为 col, fogColor
来插值。
下面我看
从Unity Built-in shader 的代码中,可以看到三个公式的代码:
#if defined(FOG_LINEAR)
// factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)
// factor = exp(-density*z)
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2)
// factor = exp(-(density*z)^2)
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
#endif
L i n e a r = d m a x − z d m a x − d m i n e Linear=\frac{d_{max}-z}{d_{max}-d_{mine}} Linear=dmax−dminedmax−z
length(_CameraWorldPos - FragWorldPos)
来替代或是view space下的z值都可以,因为这样再后续我们使用深度来重建世界坐标的方式会更方便Start
End
E x p o n e n t i a l = f = e − d ⋅ z Exponential=f=e^{-d\cdot z} Exponential=f=e−d⋅z
Density
。Linear
注意 − d ⋅ z -d\cdot z −d⋅z是 e e e的幂。
在CG函数刚好有一个:exp(a)的函数就表示: e a e^a ea
E x p o n e n t i a l S q u a r e d = f = e − ( d z ) 2 Exponential\space Squared=f=e^{-(dz)^2} Exponential Squared=f=e−(dz)2
这种方式是很方便学习用的。
可以对半透明物体应用雾的效果,因为他可以对每一个渲染的对象shader处理。
这种方式性能没有后效的雾好。
而且必须要在每个shader添加代码来实现,实在是麻烦。
因为如果没有Early-z的话,很多看不见的片段有可能都执行了一次frag的fog代码,那就白白浪费性能了。
所以我们下面实现的是后效雾,使用后效的方式来实现
这些后效雾,基本上都是需要:深度转世界坐标的,相关内容可以参考我之前的一篇:Unity Shader - 根据片段深度重建片段的世界坐标
后效雾我们可以两种来实现:深度雾、高度雾
就是所有像素都用深度来控制雾
shader
// jave.lin 2020.04.17 - 深度雾 - 代码写法很多可以优化,这里做学习用,便于高可读性
Shader "Custom/DepthFog" {
Properties {
[KeywordEnum(VIEWSPACE,WORLDSPACE)] _DIST_TYPE ("Distance type", int) = 0
[KeywordEnum(LINEAR,EXP,EXP2)] _FUNC_TYPE ("Calculate Func type", int) = 0
_MainTex ("Texture", 2D) = "white" {}
_FogColor ("Fog Color", Color) = (0.5, 0.5, 0.5, 1)
_Start ("Start", Float) = 0
_End ("End", Float) = 100
_Density ("Density", Range(0, 1)) = 0.3
}
SubShader {
ZWrite Off ZTest Always Cull Off
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _DIST_TYPE_VIEWSPACE _DIST_TYPE_WORLDSPACE
#pragma multi_compile _FUNC_TYPE_LINEAR _FUNC_TYPE_EXP _FUNC_TYPE_EXP2
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
uint id : SV_VertexID;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 ray : TEXCOORD1;
};
sampler2D _CameraDepthTexture;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _FogColor;
float4x4 _Ray;
float _Start;
float _End;
float _Density;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.ray = _Ray[v.id].xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target {
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
// return depth;
// 下面的dist就是我们博客文章中公式的z
float dist = 0;
// dist:depth to world pos
#if _DIST_TYPE_VIEWSPACE
// 以view space .z
float eyeZ = LinearEyeDepth(depth);
dist = eyeZ;
#else // _DIST_TYPE_WORLDSPACE
// dist:以world space distance
float linear01depth = Linear01Depth(depth);
// float3 wp = _WorldSpaceCameraPos.xyz + i.ray * linear01depth;
dist = length(i.ray * linear01depth);
#endif
float factor = 0;
#if _FUNC_TYPE_LINEAR
// factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
factor = (_End - dist) / (_End - _Start);
#elif _FUNC_TYPE_EXP
// factor = exp(-density*z)
factor = exp(-(_Density * dist));
#else // _FUNC_TYPE_EXP
// factor = exp(-(density*z)^2)
factor = exp(-pow(_Density * dist, 2));
#endif
factor = saturate(factor);
return lerp(_FogColor, tex2D(_MainTex, i.uv), factor);
}
ENDCG
}
}
}
world space是以相机与片段的世界坐标距离来控制的
view space是以相机与片段的视角坐标的z距离来控制的
// move the noise fog
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);
float noise = tex2D(_NoiseTex, wp.xz * _WorldPosScale + _Time.x * fixed2(_NoiseSpX, _NoiseSpY)).r * _NoiseScale;
factor *= noise;
factor = saturate(factor);
return lerp(_FogColor, tex2D(_MainTex, i.uv), lerp(1, factor, _WholeIntensity));
OK,深度雾,基本先讲到这里,下面我们就看一下高度雾吧。
如前面介绍所说,用高度控制,我们用两个参数:开始的高度,结束的高度。
Shader如下
// jave.lin 2020.04.17 - 高度雾 - 代码写法很多可以优化,这里做学习用,便于高可读性
Shader "Custom/HeightFog" {
Properties {
[KeywordEnum(VIEWSPACE,WORLDSPACE)] _DIST_TYPE ("Distance type", int) = 0
[KeywordEnum(LINEAR,EXP,EXP2)] _FUNC_TYPE ("Calculate Func type", int) = 0
_MainTex ("Texture", 2D) = "white" {} // source tex
_NoiseTex ("NoiseTex", 2D) = "white" {} // 噪点图
_FogColor ("Fog Color", Color) = (0.5, 0.5, 0.5, 1) // 雾的颜色
_WorldPosScale ("WorldPosScale", Range(0, 0.1)) = 0.05 // 世界坐标XY采样noiseTex的UV缩放值
_NoiseSpX ("Noise Speed X", Range(0, 1)) = 1 // 噪点滚动的速度(类似风吹动雾的效果)
_NoiseSpY ("Noise Speed Y", Range(0, 1)) = 1 // 噪点滚动的速度(类似风吹动雾的效果)
_HeightStart ("Height Start", Float) = 1 // 淡入雾效的开始高度
_HeightEnd ("Height End", Float) = 0 // 完全雾效的结束高度
_HeightNoiseScale ("Height Noise Scale", Range(0, 10)) = 1 // 高度噪点强度缩放
_WholeIntensity ("WholeIntensity", Range(0, 1)) = 1 // 整体效果的强度
}
SubShader {
ZWrite Off ZTest Always Cull Off
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _DIST_TYPE_VIEWSPACE _DIST_TYPE_WORLDSPACE
#pragma multi_compile _FUNC_TYPE_LINEAR _FUNC_TYPE_EXP _FUNC_TYPE_EXP2
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
uint id : SV_VertexID;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 ray : TEXCOORD1;
};
sampler2D _CameraDepthTexture;
sampler2D _MainTex;
sampler2D _NoiseTex;
fixed4 _FogColor;
float4x4 _Ray;
float _WorldPosScale;
float _NoiseSpX;
float _NoiseSpY;
float _HeightStart;
float _HeightEnd;
float _HeightNoiseScale;
float _WholeIntensity;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.ray = _Ray[v.id].xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target {
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
// return depth;
// world pos
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);
// move the noise fog
float noise = tex2D(_NoiseTex, wp.xz * _WorldPosScale + _Time.x * fixed2(_NoiseSpX, _NoiseSpY)).r;
float heightNoise = noise * _HeightNoiseScale;
// height fog
float factor = (_HeightEnd - wp.y - heightNoise) / (_HeightEnd - _HeightStart);
factor = saturate(factor);
fixed4 texCol = tex2D(_MainTex, i.uv);
fixed4 fogColor = lerp(texCol, _FogColor, _FogColor.a);
return lerp(fogColor, texCol, lerp(1, factor, _WholeIntensity));
}
ENDCG
}
}
}
运行效果
但是效果还是太单薄了写,没有深度感确实效果不太好,所以我们可以把:深度+高度混合,制作:深度高度雾
雾的实现方式也是多种多样的,按具体需求来定。
还有一些是使用RayMarching的方式来实现的(RayMarching我还没有学习)。
吐槽一下,虽然CLike的语言都有很强大的Macro 宏定义,GLSL,HLSL,CG,还有我们的ShaderLab都有宏,但是宏的可读性真的极差无比。。。
backup : UnityShader_Fog_2018.3.0f2