78.深度和法线纹理-效果实现-深度纹理实现全局雾效-具体实现
78.1 知识点
深度纹理实现全局雾效的基本原理
全局雾效的三种计算方式
Linear(线性)
公式:
f = (end - |d|) / (end - start)Exponential(指数)
公式:
f = 1 - 𝒆^(-density * |d|)Exponential Squared(指数的平方)
公式:
f = 1 - 𝒆^(- (density - |d|))
最终颜色计算
最终的颜色 = (1 - f) * 物体的颜色 + f * 雾的颜色
实现全局雾效的基本原理
- 求出像素的世界坐标,从而计算离摄像机的距离,并参与雾效公式的计算。
- 像素的世界坐标可以通过以下公式计算:
像素世界坐标 = 摄像机位置 + 观察空间线性深度值 * 摄像机指向像素世界坐标的方向向量
关键思路:
1. 屏幕后处理:
处理的内容是从屏幕抓取的图像,相当于一个面片,具有四个顶点(四个角)。2. 计算射线方向并传递到顶点着色器:
通过C#代码计算四个顶点在世界坐标系下的射线方向,传递给顶点着色器。
当数据传递到片元着色器时,每个像素对应的射线方向是基于四个顶点射线的插值计算,无需自己计算。3. 获取像素世界坐标:
利用以下公式,计算每个像素的世界空间位置:
像素世界坐标 = 摄像机位置 + 深度值 * 世界空间下射线方向4. 计算雾效颜色:
根据得到的世界空间下的位置,利用雾的公式计算出对应的雾效颜色。
利用深度纹理实现全局雾效屏幕后期处理效果对应 C# 实现
主要步骤
新建C#代码,取名FogWithDepthTexture
继承PostEffectBase,重写UpdateProperty
Start中开启深度纹理
声明雾相关属性,包括颜色、浓度、开始距离、最浓距离
根据上节课的原理,计算四个顶点的四个射线向量
通过4x4的矩阵装载各摄像向量,传递给材质。注意:为了方便之后考虑uv翻转问题,我们按左下、右下、右上、左上的逆时针顺序存储
将定义好的颜色、浓度、开始距离、最浓距离传递给材质球
新建C#代码,取名FogWithDepthTexture,继承PostEffectBase,重写UpdateProperty,Start中开启深度纹理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson78_FogWithDepthTexture : PostEffectBase
{
//雾的颜色
public Color fogColor = Color.gray;
//雾的浓度
[Range(0, 3)] public float fogDensity = 1f;
//雾开始的距离
public float fogStart = 0f;
//雾最浓时的距离
public float fogEnd = 5;
//4x4的矩阵 用于传递 4个向量参数
private Matrix4x4 rayMatrix;
void Start()
{
Camera.main.depthTextureMode = DepthTextureMode.Depth;
}
protected override void UpdateProperty()
{
if (material != null)
{
}
}
}
声明雾相关属性,颜色、浓度、开始距离、最浓距离
//雾的颜色
public Color fogColor = Color.gray;
//雾的浓度
[Range(0, 3)] public float fogDensity = 1f;
//雾开始的距离
public float fogStart = 0f;
//雾最浓时的距离
public float fogEnd = 5;
//4x4的矩阵 用于传递 4个向量参数
private Matrix4x4 rayMatrix;
根据深度纹理实现全局雾效的原理,计算四个顶点的四个射线向量。通过4x4的矩阵装载各摄像向量,传递给材质。将定义好的颜色、浓度、开始距离、最浓距离传递给材质球。
protected override void UpdateProperty()
{
if (material != null)
{
//得到摄像机 视口 夹角度数
float fov = Camera.main.fieldOfView / 2f;
//得到近裁剪面距离
float near = Camera.main.nearClipPlane;
//得到窗口比例
float aspect = Camera.main.aspect;
//计算高的一半
float halfH = near * Mathf.Tan(fov * Mathf.Deg2Rad);
//宽的一半
float halfW = halfH * aspect;
//计算竖直向上的和水平向右的偏移向量
Vector3 toTop = Camera.main.transform.up * halfH;
Vector3 toRight = Camera.main.transform.right * halfW;
//算出指向四个顶点的向量
Vector3 TL = Camera.main.transform.forward * near + toTop - toRight;
Vector3 TR = Camera.main.transform.forward * near + toTop + toRight;
Vector3 BL = Camera.main.transform.forward * near - toTop - toRight;
Vector3 BR = Camera.main.transform.forward * near - toTop + toRight;
//为了让深度值 计算出来是两点间距离 所以需要乘以一个缩放值
float scale = TL.magnitude / near;
//向量乘上缩放值 得到真正的最终想要的四条射线向量
TL = TL.normalized * scale;
TR = TR.normalized * scale;
BL = BL.normalized * scale;
BR = BR.normalized * scale;
//通过4x4的矩阵装载各摄像向量,传递给材质
//为了方便之后考虑uv翻转问题,我们按左下、右下、右上、左上的逆时针顺序存储
rayMatrix.SetRow(0, BL);
rayMatrix.SetRow(1, BR);
rayMatrix.SetRow(2, TR);
rayMatrix.SetRow(3, TL);
//设置材质球相关属性(Shader属性)
material.SetColor("_FogColor", fogColor);
material.SetFloat("_FogDensity", fogDensity);
material.SetFloat("_FogStart", fogStart);
material.SetFloat("_FogEnd", fogEnd);
material.SetMatrix("_RayMatrix", rayMatrix);
}
}
实现 利用深度纹理实现全局雾效屏幕后期处理效果 对应 Shader
主要步骤
新建Shader代码
- 取名与C#相同,删除无用代码。
声明属性,映射属性
- 属性和C#中的命名保持一致。
- 需要映射的属性包括深度纹理、矩阵、纹素属性。
屏幕后处理标配
- ZTest Always
- Cull Off
- ZWrite Off
v2f结构体
- 考虑翻转的深度纹理:
half2 uv_depth:TEXCOORD1
- 射线向量:
float4 ray:TEXCOORD2
- 考虑翻转的深度纹理:
顶点着色器
- 图片有四个顶点,会进入四次,需判断每个顶点使用哪一个射线向量。
- 坐标转换、uv赋值(考虑深度纹理翻转)。
- 根据uv坐标判断顶点位置,决定赋值哪一个向量,考虑翻转。
片元着色器
- 深度纹理采样,转换到观察空间下离摄像机的实际距离。
- 利用摄像机位置 + 深度值 * 射线向量,得到世界空间坐标。
- 利用雾公式计算混合因子,这里不使用传统的雾公式,而是实现一种特殊的基于高度的线性雾效,并结合浓度进行计算。
- 利用混合因子进行颜色混合。
FallBack Off
新建Shader代码,取名和C#相同,删除无用代码
Shader "Unlit/Lesson78_FogWithDepthTexture"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
ENDCG
}
}
Fallback Off
}
声明属性,映射属性,注意属性和C#中命名相同,还包括深度纹理、矩阵、纹素属性
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 主纹理
_FogColor("FogColor", Color) = (1,1,1,1) // 雾的颜色
_FogDensity("FogDensity", Float) = 1 // 雾的密度
_FogStart("FogStart", Float) = 0 // 雾的起始高度
_FogEnd("FogEnd", Float) = 10 // 雾的结束高度
}
sampler2D _MainTex; // 主纹理采样器
half4 _MainTex_TexelSize; // 主纹理像素大小(检查可能的翻转检测)
sampler2D _CameraDepthTexture; // 深度纹理采样器(定义好的,注意不能拼错!)
fixed4 _FogColor; // 雾的颜色
fixed _FogDensity; // 雾的密度
float _FogStart; // 雾的起始高度
float _FogEnd; // 雾的结束高度
//矩阵相关 里面存储了 4条射线向量
//0-左下 1-右下 2-右上 3-左上
float4x4 _RayMatrix; // 存储四个角的射线向量矩阵
声明屏幕后处理标配指令
ZTest Always // 始终通过深度测试
Cull Off // 关闭背面剔除
ZWrite Off // 禁止写入深度缓冲
声明v2f结构体,考虑翻转的深度纹理和射线向量
struct v2f
{
float2 uv : TEXCOORD0; // 主纹理的UV坐标
float2 uv_depth : TEXCOORD1; // 深度纹理的UV坐标
float4 ray : TEXCOORD2; // 射线方向,用于计算像素的世界空间坐标,指向四个角的方向向量 (传递到片元时 会自动进行插值 运算)
float4 vertex : SV_POSITION; // 顶点位置(裁剪空间)
};
顶点函数中,考虑图片有四个顶点,会进入四次,我们需要判断每一个顶点使用哪一个射线向量。进行坐标转换、uv赋值(考虑深度纹理翻转)。根据uv坐标判断顶点位置,决定赋值哪一个向量。同样需要考虑翻转。
v2f vert(appdata_base appdata_base)
{
v2f v2f;
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 转换为裁剪空间
v2f.uv = appdata_base.texcoord; // 传递UV坐标
v2f.uv_depth = appdata_base.texcoord;//先给深度纹理的UV坐标赋值一下
//顶点着色器函数 每一个顶点都会执行一次
//对于屏幕后处理来说 就会执行4次 因为有4个顶点 (4个角)
//通过uv坐标判断 当前的顶点位置
int index = 0;
if (appdata_base.texcoord.x < 0.5 && appdata_base.texcoord.y < 0.5)
index = 0;
else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y < 0.5)
index = 1;
else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y > 0.5)
index = 2;
else
index = 3;
//判断 是否需要进行纹理翻转 如果翻转了 深度的uv和对应顶点需要变化
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
{
v2f.uv_depth.y = 1 - v2f.uv_depth.y;
//索引也要变 比如左上变左下
index = 3 - index;
}
#endif
//根据顶点的位置 决定使用那一个射线向量
v2f.ray = _RayMatrix[index];
return v2f;
}
片元函数中,进行深度纹理采样,转换到观察空间下离摄像机的实际距离。利用 摄像机位置 + 深度值*射线向量 得到世界空间坐标。利用雾公式 算出混合因子 我们这里不使用传统雾公式我们实现一种特殊的基于高度的线性雾效,并且把浓度也用上,利用混合因子 进行颜色混合。
fixed4 frag(v2f v2f) : SV_Target
{
//观察空间下 离摄像机的实际距离(Z分量)
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, v2f.uv_depth));
//计算世界空间下 像素的坐标
float3 worldPos = _WorldSpaceCameraPos + linearDepth * v2f.ray;
//雾相关的计算
//混合因子
float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
//取0~1之间 超过会取极值
f = saturate(f * _FogDensity);
//利用插值 在两个颜色之间进行融合
fixed3 color = lerp(tex2D(_MainTex, v2f.uv).rgb, _FogColor.rgb, f);
return fixed4(color.rgb, 1);
}
不使用回退Shader
Fallback Off// 不使用回退Shader
挂载到摄像机关联shader后可以看到效果
78.2 知识点代码
Lesson78_深度和法线纹理_效果实现_深度纹理实现全局雾效_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson78_深度和法线纹理_效果实现_深度纹理实现全局雾效_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾 深度纹理实现全局雾效基本原理
//全局雾效的三种计算方式
//Linear(线性):f = (end - |d|) / (end – start)
//Exponential(指数): f = 1 – 𝒆^−𝒅𝒆𝒏𝒔𝒊𝒕𝒚∗|𝒅|
//Exponential Squared(指数的平方): f = 1 – 𝒆^−(𝒅𝒆𝒏𝒔𝒊𝒕𝒚−|𝒅|)
//最终的颜色 = (1-f)* 物体的颜色+ f * 雾的颜色
//实现全局雾效的基本原理
//求出像素的世界坐标,从而得到离摄像机的距离,从而参与到雾效公式的计算中
//像素的世界坐标 = 摄像机位置 + 观察空间线性深度值 * 摄像机指向像素世界坐标的方向向
//关键思路:
//1.屏幕后处理中处理的内容是一张抓取的屏幕图像,相当于是一个面片,它具有4个顶点(四个角)
//2.通过C#代码计算四个顶点在世界坐标系下的射线方向后传递给顶点着色器
// 当数据传递到片元着色器要处理每个像素时,像素对应的射线方向是基于4个顶点的射线插值计算而来(无需我们自己计算)
//3. 利用 像素世界坐标= 摄像机位置+ 深度值* 世界空间下射线方向得到对应像素在世界空间下位置
//4.利用得到的世界空间下位置利用雾的公式计算出对应雾效颜
#endregion
#region 知识点一 实现 利用深度纹理实现全局雾效屏幕后期处理效果 对应 C#
//1.新建C#代码,取名FogWithDepthTexture
//2.继承PostEffectBase,重写UpdateProperty
//3.Start中开启深度纹理
//4.声明雾相关属性
// 颜色、浓度、开始距离、最浓距离
//5.根据上节课的原理,计算四个顶点的四个射线向量
//6.通过4x4的矩阵装载各摄像向量,传递给材质
// 注意:为了方便之后考虑uv翻转问题,我们按左下、右下、右上、左上的逆时针顺序存储
//7.将定义好的颜色、浓度、开始距离、最浓距离传递给材质球
#endregion
#region 知识点二 实现 利用深度纹理实现全局雾效屏幕后期处理效果 对应 Shader
//1.新建Shader代码,取名和C#相同,删除无用代码
//2.声明属性,映射属性,注意属性和C#中命名相同
// 还有深度纹理、矩阵、纹素属性
//3.屏幕后处理标配
// ZTest Always
// Cull Off
// ZWrite Off
//4.v2f结构体
// 考虑翻转的深度纹理half2 uv_depth:TEXCOORD1
// 射线向量 loat4 ray:TEXCOORD2
//5.顶点着色器(图片有四个顶点,会进入四次,我们需要判断每一个顶点使用哪一个射线向量)
// 坐标转换、uv赋值(考虑深度纹理翻转)
// 根据uv坐标判断顶点位置,决定赋值哪一个向量
// 同样需要考虑翻转
//6.片元着色器
// 深度纹理采样,转换到观察空间下离摄像机的实际距离
// 利用 摄像机位置 + 深度值*射线向量 得到世界空间坐标
// 利用雾公式 算出混合因子 我们这里不使用传统雾公式
// 我们实现一种特殊的基于高度的线性雾效 并且把浓度也用上
// 利用混合因子 进行颜色混合
//7.FallBack Off
#endregion
}
}
Lesson78_FogWithDepthTexture.shader
Shader "Unlit/Lesson78_FogWithDepthTexture"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 主纹理
_FogColor("FogColor", Color) = (1,1,1,1) // 雾的颜色
_FogDensity("FogDensity", Float) = 1 // 雾的密度
_FogStart("FogStart", Float) = 0 // 雾的起始高度
_FogEnd("FogEnd", Float) = 10 // 雾的结束高度
}
SubShader
{
ZTest Always // 始终通过深度测试
Cull Off // 关闭背面剔除
ZWrite Off // 禁止写入深度缓冲
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex; // 主纹理采样器
half4 _MainTex_TexelSize; // 主纹理像素大小(检查可能的翻转检测)
sampler2D _CameraDepthTexture; // 深度纹理采样器(定义好的,注意不能拼错!)
fixed4 _FogColor; // 雾的颜色
fixed _FogDensity; // 雾的密度
float _FogStart; // 雾的起始高度
float _FogEnd; // 雾的结束高度
//矩阵相关 里面存储了 4条射线向量
//0-左下 1-右下 2-右上 3-左上
float4x4 _RayMatrix; // 存储四个角的射线向量矩阵
struct v2f
{
float2 uv : TEXCOORD0; // 主纹理的UV坐标
float2 uv_depth : TEXCOORD1; // 深度纹理的UV坐标
float4 ray : TEXCOORD2; // 射线方向,用于计算像素的世界空间坐标,指向四个角的方向向量 (传递到片元时 会自动进行插值 运算)
float4 vertex : SV_POSITION; // 顶点位置(裁剪空间)
};
v2f vert(appdata_base appdata_base)
{
v2f v2f;
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 转换为裁剪空间
v2f.uv = appdata_base.texcoord; // 传递UV坐标
v2f.uv_depth = appdata_base.texcoord; //先给深度纹理的UV坐标赋值一下
//顶点着色器函数 每一个顶点都会执行一次
//对于屏幕后处理来说 就会执行4次 因为有4个顶点 (4个角)
//通过uv坐标判断 当前的顶点位置
int index = 0;
if (appdata_base.texcoord.x < 0.5 && appdata_base.texcoord.y < 0.5)
index = 0;
else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y < 0.5)
index = 1;
else if (appdata_base.texcoord.x > 0.5 && appdata_base.texcoord.y > 0.5)
index = 2;
else
index = 3;
//判断 是否需要进行纹理翻转 如果翻转了 深度的uv和对应顶点需要变化
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
{
v2f.uv_depth.y = 1 - v2f.uv_depth.y;
//索引也要变 比如左上变左下
index = 3 - index;
}
#endif
//根据顶点的位置 决定使用那一个射线向量
v2f.ray = _RayMatrix[index];
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//观察空间下 离摄像机的实际距离(Z分量)
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, v2f.uv_depth));
//计算世界空间下 像素的坐标
float3 worldPos = _WorldSpaceCameraPos + linearDepth * v2f.ray;
//雾相关的计算
//混合因子
float f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
//取0~1之间 超过会取极值
f = saturate(f * _FogDensity);
//利用插值 在两个颜色之间进行融合
fixed3 color = lerp(tex2D(_MainTex, v2f.uv).rgb, _FogColor.rgb, f);
return fixed4(color.rgb, 1);
}
ENDCG
}
}
Fallback Off// 不使用回退Shader
}
Lesson78_FogWithDepthTexture.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson78_FogWithDepthTexture : PostEffectBase
{
//雾的颜色
public Color fogColor = Color.gray;
//雾的浓度
[Range(0, 3)] public float fogDensity = 1f;
//雾开始的距离
public float fogStart = 0f;
//雾最浓时的距离
public float fogEnd = 5;
//4x4的矩阵 用于传递 4个向量参数
private Matrix4x4 rayMatrix;
void Start()
{
Camera.main.depthTextureMode = DepthTextureMode.Depth;
}
protected override void UpdateProperty()
{
if (material != null)
{
//得到摄像机 视口 夹角度数
float fov = Camera.main.fieldOfView / 2f;
//得到近裁剪面距离
float near = Camera.main.nearClipPlane;
//得到窗口比例
float aspect = Camera.main.aspect;
//计算高的一半
float halfH = near * Mathf.Tan(fov * Mathf.Deg2Rad);
//宽的一半
float halfW = halfH * aspect;
//计算竖直向上的和水平向右的偏移向量
Vector3 toTop = Camera.main.transform.up * halfH;
Vector3 toRight = Camera.main.transform.right * halfW;
//算出指向四个顶点的向量
Vector3 TL = Camera.main.transform.forward * near + toTop - toRight;
Vector3 TR = Camera.main.transform.forward * near + toTop + toRight;
Vector3 BL = Camera.main.transform.forward * near - toTop - toRight;
Vector3 BR = Camera.main.transform.forward * near - toTop + toRight;
//为了让深度值 计算出来是两点间距离 所以需要乘以一个缩放值
float scale = TL.magnitude / near;
//向量乘上缩放值 得到真正的最终想要的四条射线向量
TL = TL.normalized * scale;
TR = TR.normalized * scale;
BL = BL.normalized * scale;
BR = BR.normalized * scale;
//通过4x4的矩阵装载各摄像向量,传递给材质
//为了方便之后考虑uv翻转问题,我们按左下、右下、右上、左上的逆时针顺序存储
rayMatrix.SetRow(0, BL);
rayMatrix.SetRow(1, BR);
rayMatrix.SetRow(2, TR);
rayMatrix.SetRow(3, TL);
//设置材质球相关属性(Shader属性)
material.SetColor("_FogColor", fogColor);
material.SetFloat("_FogDensity", fogDensity);
material.SetFloat("_FogStart", fogStart);
material.SetFloat("_FogEnd", fogEnd);
material.SetMatrix("_RayMatrix", rayMatrix);
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com