自言自语
磨磨唧唧拖了好几天 才算把这个搞得稍微明白点了。 各种计算也弄懂了。。 大概90%吧。。
今天实在太困了就先传个图。明天把代码和注释写好再补上吧。。 加油啊 坚持啊 老骚年~
·························································································································
2021.2.26 凌晨 更新对之前理解不详细的地方 添加到注释C#的注释中
效果
C#部分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class LinearFogWithDepthTexture : PostProcessWangBase
{
public float fogStart = 0f;
public float fogEnd =100f;
[Range(0f, 10f)]
public float fogDensity = 1f;
[ColorUsage(true, true)]
public Color fogColor = Color.white;
public Shader fogShader;
private Material omaterial = null;
public Material fogMaterial
{
get
{
if (omaterial == null)
{
omaterial = CheckShaderAndMaterial(fogShader, omaterial);
}
return omaterial;
}
}
// 因为需要相机的一些参数作为计算 所以需要声明两个相机
//第一个相机为Camera组件
private Camera myCamera ;
public Camera fogCamera
{
get
{
if (myCamera == null)
{
myCamera = GetComponent<Camera>();
}
return myCamera;
}
}
//第二个相机为相机的位置旋转等Transform参数
private Transform myCameraTrans;
public Transform fogCameraTrans
{
get
{
if(myCameraTrans == null )
{
myCameraTrans = fogCamera.transform;
}
return myCameraTrans;
}
}
//在激活可用状态下 获得深度值
private void OnEnable()
{
myCamera.depthTextureMode |= DepthTextureMode.Depth;
}
protected void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(fogMaterial == null )
{
Graphics.Blit(source, destination);
}
else
{
//声明一个单位矩阵用来存放后边需要用到的相应射线向量
Matrix4x4 frustumCorners = Matrix4x4.identity;
//获得相机的near值
float near = fogCamera.nearClipPlane;
// 通过 near*fov/2 公式来计算出近裁剪平面中心点到顶部的半高值 其中 Mathf.Deg2rad 为 角度转弧度 进行值的计算
float heightHalf = near * (fogCamera.fieldOfView / 2 * Mathf.Deg2Rad);
//用相机的向上方向的单位矢量 乘以半高值 得到 中心点到顶部的 向量
Vector3 upCenter = fogCameraTrans.up * heightHalf;
//同理 得到中心点到右边中点的向量 这里乘以的相机的宽高比 即已知高度 和宽高比 算出向右向量的值 再乘以向右方向的单位向量
Vector3 rightCenter = fogCameraTrans.right * heightHalf*fogCamera.aspect;
//根据向量的加减法 算出相应的四个角的向量 再通过相似三角形边长比相等定律 算出匹配的4个角相应射线
//难点就在这里的理解和计算 花个视锥体的图 就一目了然
Vector3 TL = near*fogCameraTrans.forward + upCenter - rightCenter;
//TL.magnitude 是指TL的模也就是TL的长度 根据相似三角形边长比相等 求出一个比值 可以通用用来计算TL射线到像素点的最终欧式距离 distance
//********************************
// deapth/distance = near/|TL|
// deapth = distance*near/|TL|
// distance = deapth*|TL|/near
// scaler = |TL|/|near|
// distance = scaler*depth 这一步是在shader中进行的 shader中可以获得深度值 用于重构当前像素的世界坐标以深度关联的表示
// 好吧推导到这里真的不理解了 已经得到了 scaler为什么下边还要去计算 rayTL等射线长度呢? 直接去shader中乘以深度值不就算出了坐标偏移量了么 现在除了少了一个方向因素就行 那么方向因素 只需要乘以单位向量就行.
// 这么一写又想明白了 所以 才有下边 归一化过后的单位向量 乘以 scaler的操作 所以要进行归一化操作
//进行归一化操作,TL射线的单位向量
TL.Normalize();
float scaler = TL.magnitude / near;
Vector3 rayTL = TL * scaler;
Vector3 TR = near * fogCameraTrans.forward + upCenter + rightCenter;
TR.Normalize();
Vector3 rayTR = TR * scaler;
Vector3 BL = near * fogCameraTrans.forward - upCenter - rightCenter;
BL.Normalize();
Vector3 rayBL = BL * scaler;
Vector3 BR = near * fogCameraTrans.forward- upCenter + rightCenter;
BR.Normalize();
Vector3 rayBR = BR * scaler;
//把射线存储在单位矩阵中 存储的射线信息是透视投影下的 注意ID顺序要和shader中的index判定一致
frustumCorners.SetRow(0, BL);
frustumCorners.SetRow(1, BR);
frustumCorners.SetRow(2, TL);
frustumCorners.SetRow(3, TR);
//传递参数到shader
fogMaterial.SetFloat("_FogStart", fogStart);
fogMaterial.SetFloat("_FogEnd", fogEnd);
fogMaterial.SetFloat("_FogDensity", fogDensity);
fogMaterial.SetColor("_FogColor", fogColor);
//传递存储射线的矩阵到shader
fogMaterial.SetMatrix("_FrustumMatrix", frustumCorners);
//通过才shader的材质球对图像做处理 输出
Graphics.Blit(source, destination, fogMaterial);
}
}
}
shader部分
Shader "TNShaderPractise/ShaderPractise_LinearFogWithDepthTexture"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
CGINCLUDE
#include "UnityCG.cginc"
float _FogStart;
float _FogEnd;
float _FogDensity;
fixed4 _FogColor;
float4x4 _FrustumMatrix;
sampler2D _MainTex;
//声明图素 作为平台差异判断
half4 _MainTex_TexelSize;
//固定变量名称来接收深度图
sampler2D _CameraDepthTexture;
struct a2v
{
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD1;
half2 uv_depth : TEXCOORD2;
float3 interRay: TEXCOORD3;
};
v2f vert(a2v v )
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv_depth = v.texcoord;
//平台差异判断 对图进行正确采样坐标修正
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y<0)
o.uv_depth.y = 1-o.uv_depth.y;
#endif
int index = 0;
//根据屏幕坐标的区域空间氛围4个块 然后以OPENGL原则为准 判断哪个角对应哪个射线
if (v.vertex.x<0.5&&v.vertex.y<0.5)
{
index = 0;
}
else if(v.vertex.x>0.5&&v.vertex.y<0.5)
{
index = 1;
}
else if(v.vertex.x<0.5&&v.vertex.y>0.5)
{
index = 2;
}
else
{
index = 3;
}
//再进行平台判断, 如果是DX平台就 反向取矩阵的行
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y<0)
index = 3-index;
#endif
//尊重该像素的射线对应的应该是传递过来的矩阵里的射线
o.interRay = _FrustumMatrix[index];
return o;
}
fixed4 frag(v2f i) :SV_Target
{
//采样深度图获得深度值
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
//将深度值映射到0到1区间 线性空间 这样好做世界坐标空间下位置判断 此处为了使LinearEyeDepth有意义 则需要在CGINCLUDE 代码块中引用 "UnityCG.cginc"
float linearDepth = LinearEyeDepth(depth);
//得到世界空间下该点的深度值表示
float3 worldPos = _WorldSpaceCameraPos+linearDepth*i.interRay;
//根据线性高度雾的计算公式 获得C#代码中传输过来的参数 算出雾的值
float fogDensity =( _FogEnd-worldPos.y )/(_FogEnd-_FogStart);
//将雾的值乘以系数 并限制在0到1区间 方便纹理渲染采样
fogDensity = saturate (fogDensity*_FogDensity);
fixed4 col = tex2D(_MainTex,i.uv);
//对雾的颜色和屏幕纹理图 以雾的浓度为标准进行插值
fixed4 finalColor = lerp(col,_FogColor,fogDensity);
//输出问题图像 呈现雾效
return finalColor;
}
ENDCG
Pass
{
ZWrite Off ZTest Always Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off
}