一、前言
Untiy内置的雾效可以产生基于距离的线性或指数雾效。
而如果我们要在自己的Shader中实现雾效,我们可能会使用到 #pragma multi_compile_fog ,UNITY_FOG_COORDS,UNITY_TRANSFER_FOG,UNITY_APPLY_FOG等等 内置宏。
基于屏幕后处理的全局雾效的实现:
这种方法我们不需要更改场景内渲染物体的Shader代码,仅仅使用一次屏幕后处理效果即可。
这种方法自由度高,我们可以模拟出各种雾效,均匀、基于距离的线性/指数、基于高度的雾效等等。
二、具体实现
这种屏幕后处理全局雾效果的关键在于 我们需要使用深度纹理中的深度值重建世界空间下的像素位置。
之前我们已经实现了重建世界坐标的方法:深度纹理 重建世界坐标实现像素速度存储—运动模糊 上面这个是在片元着色器中进行了复杂的矩阵运算,很影响性能。
更加好的重建世界坐标的办法是:
对图像空间下的视锥体射线(从摄像机出发,指向图像上某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息。然后我们再把射线和线性空间下的深度值相乘,再加上摄像机的世界位置,就可以得到该像素的世界空间位置了,
得到世界空间像素位置后,我们再使用各种公式,模拟全局雾效。
世界坐标重建具体见 P276-P278.
大概过程为:
通过摄像机世界空间位置加上偏移计算出像素世界空间位置
偏移计算通过 深度纹理计算得到的线性深度值 乘以 顶点着色器输出并插值后的射线(同时包含方向和距离),
上面插值的意思是 对摄像机近裁剪平面四个角的某个特定向量的插值。
然后就是计算这四个特定向量,使用摄像机相关系数和方向与矢量加减运算(P277)。
得到四个特定向量后,我们不能使用深度值直接与四个角的单位方向乘积进行计算偏移量,因为我们得到的线性深度值并非是点到摄像机的欧氏距离,而是z方向上的距离。
所以我们要把深度值转换成到摄像机的欧氏距离。
然后就不懂了,图13.7这里的像素深度值到底是在哪个平面???
琢磨两天了。???查一些资料也不懂啊。
只能确定射线是通过系数乘以方向,系数是通过相似三角形计算的.。
找到一个知乎回答大概明白什么意思了。
—链接: 上面这个问题
首先构造在世界空间中从摄像机指向屏幕像素点的向量。
o.worldSpaceDir = WorldSpaceViewDir(v.vertex);
将向量转换到观察空间,存储其z分量的值。注意向量和位置的空间转换是不同的,当w分量为0的时候Unity会将其视为向量,而当w分量为1的时候Unity将其视为位置。
o.viewSpaceZ = mul(UNITY_MATRIX_V, float4(o.worldSpaceDir,
0.0)).z;
在深度缓冲中采样。这里使用tex2Dproj而不是tex2D的原因是screenPos是用ComputeScreenPos来计算得到的,用tex2Dproj可以帮我们做透视除法。
float eyeDepth =
UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, i.screenPos));
eyeDepth = LinearEyeDepth(eyeDepth);
因为像素点的观察线性深度就是其在观察空间中的z分量,所以根据向量的z分量计算其缩放因子,将向量缩放到实际的长度。
i.worldSpaceDir *= -eyeDepth / i.viewSpaceZ;
最后以摄像机为起点,缩放后的向量为指向向量,得到像素点在世界空间中位置。
float3 worldPos = _WorldSpaceCameraPos + i.worldSpaceDir;
这里把问题描述成了:求观察空间下 视角方向的z轴、像素观察方向的线性深度,然后我们就可以求出缩放因子。
我看了一下i.worldSpaceDir *= -eyeDepth / i.viewSpaceZ;
其实就是上面书里求的比例,再乘以方向和linearDepth。
只不过这个使用的视角方向,书中使用的是近裁剪平面4个角,
书中解释4个角的原因是 对应近裁剪平面4个角计算出来后,传入顶点着色器,顶点着色器会根据当前的位置选择对应向量,插值后传到interpolatedRay。
希望有个大佬跳出来给我解释下 -o-
三、雾效计算
//雾效系数f,原始颜色和雾效颜色
float3 afterFog = f*fogColor+(1-f)*origColor;
//线性雾效系数f的计算
f = ( H(end)-y ) / (H(end)-H(start))
雾效其他 f 计算方法(线性、指数、指数平方见P278)
四、具体实现
摄像机代码:
using UnityEngine;
using System.Collections;
public class FogWithDepthTexture : PostEffectsBase {
public Shader fogShader;
private Material fogMaterial = null;
public Material material {
get {
fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);
return fogMaterial;
}
}
private Camera myCamera;
public Camera camera {
get {
if (myCamera == null) {
myCamera = GetComponent<Camera>();
}
return myCamera;
}
}
private Transform myCameraTransform;
public Transform cameraTransform {
get {
if (myCameraTransform == null) {
myCameraTransform = camera.transform;
}
return myCameraTransform;
}
}
[Range(0.0f, 3.0f)]
public float fogDensity = 1.0f; //雾的浓度
public Color fogColor = Color.white; //雾的颜色
public float fogStart = 0.0f; //起始高度
public float fogEnd = 2.0f; //终止高度
void OnEnable() {
camera.depthTextureMode |= DepthTextureMode.Depth; //设置深度纹理
}
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
Matrix4x4 frustumCorners = Matrix4x4.identity; //用于存储4个角的向量。
float fov = camera.fieldOfView;
float near = camera.nearClipPlane;
float aspect = camera.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cameraTransform.right * halfHeight * aspect;
Vector3 toTop = cameraTransform.up * halfHeight;
Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
float scale = topLeft.magnitude / near; //计算系数
//这里开始计算4个向量
topLeft.Normalize();
topLeft *= scale;
Vector3 topRight = cameraTransform.forward * near + toRight + toTop;
topRight.Normalize();
topRight *= scale;
Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale;
Vector3 bottomRight = cameraTransform.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); //向Shader中传递矩阵
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);
}
}
}
Shader代码:
Shader "Unity Shaders Book/Chapter 13/Fog With Depth Texture" {
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
}
SubShader {
CGINCLUDE
#include "UnityCG.cginc"
float4x4 _FrustumCornersRay;
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2; //存储插值后的像素向量
};
v2f vert(appdata_img 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;
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;
} else {
index = 3;
}
#if UNITY_UV_STARTS_AT_TOP //不同平台差异化处理
if (_MainTex_TexelSize.y < 0)
index = 3 - index; //索引更改 翻转
#endif
o.interpolatedRay = _FrustumCornersRay[index]; //通过索引值获取对应行的interpolatedRay
return o;
}
fixed4 frag(v2f i) : SV_Target {
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth)); //获取线性深度值
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.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;
}
ENDCG
Pass {
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off
}
完事儿
现在就是那个scale原理最后不懂,
正交摄像机对以上不适配。