maltadirk 发表于 2022-8-24 10:47

Unity屏幕后处理(基于深度纹理实现的全局雾效)

由于Unity内置雾效限制比较多,书中介绍了使用深度纹理实现的全局雾效后处理。原理为对图像空间下的视锥体射线(摄像机到图像某点的射线)进行插值,得到像素点在世界空间下到摄像机的方向,然后通过该射线和深度值,摄像机的位置重新构建出该像素在世界坐标的位置来计算模拟全局雾效。接下来进入实战环节。
挂载在相机上的后处理脚本实现如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FogWithDepthTexture : PostEffectsBase
{
    public Shader FogWithDepthTextureShader;
   
   
    public float _FogDensity = 1.0f; //雾的浓度
    public Color _FogColor = Color.white; //雾的颜色
    public float _FogStart = 0.0f; //雾的起始高度
    public float _FogEnd = 2.0f;//雾的终止高度
   
    private Material _FogWithDepthTextureMaterial;

    private Camera _MyCamera;
    private Transform _MyCameraTransform;

    public Camera Camera
    {
      get
      {
            if (_MyCamera == null)
            {
                _MyCamera = GetComponent<Camera>();
            }
            return _MyCamera;
      }
    }

    public Transform CameraTransform
    {
      get
      {
            if (_MyCameraTransform == null)
            {
                _MyCameraTransform = Camera.transform;
            }

            return _MyCameraTransform;
      }
    }

    public Material material
    {
      get
      {
            _FogWithDepthTextureMaterial = CheckShaderAndCreateMaterial(FogWithDepthTextureShader, _FogWithDepthTextureMaterial);
            return _FogWithDepthTextureMaterial;
      }
    }

    private void OnEnable()
    {
      Camera.depthTextureMode |= DepthTextureMode.Depth;
    }


    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
      if (material != null)
      {
            Matrix4x4 frustumCorners = Matrix4x4.identity;
            //摄像机垂直视野的角度
            float vFov = Camera.fieldOfView;
            //近剪切平面到相机的距离
            float near = Camera.nearClipPlane;
            //屏幕宽高比(宽度除以高度)
            float aspect = Camera.aspect;

            //通过 近剪切平面到相机的距离 和 垂直视野的角度 得到 近剪切平面的高度的一半
            float halfHeight = near * Mathf.Tan(vFov * 0.5f * Mathf.Deg2Rad);
            //近剪切平面向右的向量
            Vector3 toRight = CameraTransform.right * halfHeight * aspect;
            //近剪切平面向上的向量
            Vector3 toTop = CameraTransform.up * halfHeight;

            //近剪切平面左上角的射线
            var forward = CameraTransform.forward;
            Vector3 topLeft = forward * near+ toTop - toRight;
            float scale = topLeft.magnitude / near;
            
            topLeft.Normalize();
            topLeft *= scale;

            Vector3 topRight = forward * near + toRight + toTop ;
            topRight.Normalize();
            topRight *= scale;

            Vector3 bottomLeft = forward * near - toTop - toRight;
            bottomLeft.Normalize();
            bottomLeft *= scale;

            Vector3 bottomRight = 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);
      }
    }
}
使用_FrustumCornersRay变量来存储摄像机到摄像机视锥体近平面的四个角对应的归一化射线方向。

用来渲染后处理效果的Shader实现如下:
Shader "Unlit/FogWithDepthTexture"
{
    Properties
    {
      _MainTex ("Texture", 2D) = "white" {}
      _FogDensity("_FogDensity",Float) = 1.0
      _FogColor("_FogColor",Color) = (1,1,1,1)
      _FogStart("_FogStart",Float) = 1.0
      _FogEnd("_FogEnd",Float) = 1.0
    }
    SubShader
    {
      CGINCLUDE
      #include "UnityCG.cginc"

      float4x4 _FrustumCornersRay;//ps:虽未在Properties声明,但依然可由脚本传递给Shader
      sampler2D _MainTex;
      half4 _MainTex_TexelSize;
      sampler2D _CameraDepthTexture;//声明深度纹理,Unity会把深度纹理赋值到此变量
      float _FogDensity;
      fixed4 _FogColor;
      float _FogStart;
      float _FogEnd;

      struct appdata
      {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
      };

      struct v2f
      {
            float4 pos : SV_POSITION;
            float2 uv : TEXCOORD0;
            float2 uv_depth : TEXCOORD1;
            float4 interpolateRay : TEXCOORD2;
      };

      v2f vert (appdata v)
      {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.uv;
            o.uv_depth = v.uv;

            //根据UV的坐标点得到合适的角的方向进行插值[左下,右下,右上,左上]
            int index = 0;
            if (v.uv.x < 0.5 && v.uv.y < 0.5)
            {
                index = 0;
            }
            else if (v.uv.x > 0.5 && v.uv.y < 0.5)
            {
                index = 1;
            }
            else if (v.uv.x > 0.5 && v.uv.y > 0.5)
            {
                index = 2;
            }
            else
            {
                index = 3;
            }

            #if UNITY_UV_STARTS_AT_TOP
            if (_MainTex_TexelSize.y < 0)
            {
                o.uv_depth.y = 1 - o.uv_depth.y;
                index = 3 - index;
            }
            #endif

            //根据UV的值使用四个角中合适的方向进行插值
            o.interpolateRay = _FrustumCornersRay;

            return o;
      }

      fixed4 frag (v2f i) : SV_Target
      {
            //使用 SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth)对深度纹理 _CameraDepthTexture进行采样
            //使用 LinearEyeDepth 得到视角空间下的线性深度值
            float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth));

            //使用相机的世界坐标的位置、相机到该像素点的方向、该像素点的深度值 来计算得到像素点的世界坐标
            float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolateRay.xyz;

            //雾的当前高度
            float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
            //计算雾的浓度
            fogDensity = saturate(fogDensity * _FogDensity);

            fixed4 col = tex2D(_MainTex, i.uv);
            //通过雾的浓度对像素的源时颜色和雾的颜色进行插值
            col.rgb = lerp(col.rgb, _FogColor.rgb, fogDensity);
            
            return col;
      }
      
      ENDCG
      
      Pass
      {
            ZTest Always
            Cull Off
            ZWrite Off
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
      }
    }
    Fallback Off
}
渲染结果如下图:



此篇文章是在学习《Unity Shader入门精要》一书的笔记,加入了一些个人理解,如有错误之处,还请各位不吝赐教
页: [1]
查看完整版本: Unity屏幕后处理(基于深度纹理实现的全局雾效)