学习资料:《Unity Shader入门精要》第13章
源代码:Github
获取深度和法线纹理
深度纹理:
- 延迟渲染:渲染到G-buffer中
- 通过单独的Pass渲染得到:使用着色器替换技术,选择Render Type为Opaque的物体,判断渲染队列是否<=2500,满足条件的就渲染到深度和法线纹理中
深度纹理的获取:
camera.depthTextureMode = DepthTextureMode.depth;
在Shader中访问深度纹理:_CameraDepthTexture
全局雾效
重建世界坐标
根据深度纹理重建每个像素在世界空间下的位置:对图像空间下的视锥体射线(摄像机指向图像上某点的射线)进行插值,射线存储了该像素在世界空间下到摄像机的方向信息。把射线与线性化后的视角空间下的深度值相乘,加上摄像机的世界位置,得到该像素在世界空间中的位置。
float4 worldPos = _WorldSpaceCameraPos + linearDepth * interpolatedRay;
计算摄像机到近裁剪平面的4个角的向量,屏幕后处理就是使用特定的材质渲染一个刚好填充整个屏幕的四边形面片,这个四边形面片的4个顶点就对应了近裁剪平面的4个角
雾的计算
float3 afterFog = f * fogColor + (1-f) * origColor;
基于高度的雾效:
f=\frac{H_{end}-y}{H_{end}-H_{start}}
获取摄像机的深度纹理
void OnEnable() {
camera.depthTextureMode |= DepthTextureMode.Depth;
}
在摄像机的FogWithDepthTexture
脚本中计算interpolatedRay
,传递给Shader:
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
Matrix4x4 frustumCorners = Matrix4x4.identity;
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;
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);
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);
}
}
在顶点着色器中用纹理坐标确定4个顶点对应的interpolatedRay
的索引:
o.uv = v.texcoord;
o.uv_depth = v.texcoord;
// 如果是DirectX开启抗锯齿,纹理坐标需要进行差异化处理
#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];
return o;
在片元着色器中重建世界坐标,产生雾效:
// SAMPLLE_DEPTH_TEXTURE:对深度纹理采样
// LinearEyeDepth:得到视角空间下的线性深度值
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;
效果展示:
fogDensity = 0.5
fogDensity = 1