# 【水面反射】

Markdown 图片

原理:创建反射专用的摄像机,把反射画面渲染到贴图,然后在使用反射的地方,使用屏幕坐标来采样反射渲染出来的贴图。

模糊Shader KawaseBlur.shader

Shader "Hidden/KawaseBlur"
{
    Properties
    {
        _MainTex("", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            Cull Off ZWrite Off ZTest Always
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment Frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;

            uniform half _Offset;
        
            half4 Frag(v2f_img i): SV_Target
            {
                half4 o = 0;
                o += tex2D(_MainTex, i.uv + float2(_Offset + 0.5, _Offset + 0.5) * _MainTex_TexelSize.xy);
                o += tex2D(_MainTex, i.uv + float2(-_Offset - 0.5, _Offset + 0.5) * _MainTex_TexelSize.xy);
                o += tex2D(_MainTex, i.uv + float2(-_Offset - 0.5, -_Offset - 0.5) * _MainTex_TexelSize.xy);
                o += tex2D(_MainTex, i.uv + float2(_Offset + 0.5, -_Offset - 0.5) * _MainTex_TexelSize.xy);
                return o * 0.25;
            }
            ENDCG
        }
    }
}

需要在水面的对象挂载 PlanarReflection.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

[ExecuteInEditMode]
public class PlanarReflection : MonoBehaviour {
    public LayerMask _reflectionMask = -1;
    public bool _reflectSkybox = false;
    public float _clipPlaneOffset = 0.07F;
    //反射图属性名
    const string _reflectionTex = "_ReflectionTex";
    Camera _reflectionCamera;
    Vector3 _oldpos;
    RenderTexture _bluredReflectionTexture;
    Material _sharedMaterial;
    //模糊效果相关参数
    public bool _blurOn = true;
    [Range(0.0f, 5.0f)]
    public float _blurSize = 1;
    [Range(0, 10)]
    public int _blurIterations = 2;
    [Range(1.0f, 4.0f)]
    public float _downsample = 1;
    //记录上述模糊参数,用于判断参数是否发生变化   
    private bool _oldBlurOn;
    private float _oldBlurSize;
    private int _oldBlurIterations;
    private float _oldDownsample;
    //模糊shader
    private Shader _blurShader;
    private Material _blurMaterial;
    //用来判断当前是否正在渲染反射图
    private static bool _insideRendering;

    Material BlurMaterial {
        get {
            if (_blurMaterial == null) {
                _blurMaterial = new Material(_blurShader);
                return _blurMaterial;
            }
            return _blurMaterial;
        }
    }

    void Awake() {
        _oldBlurOn = _blurOn;
        _oldBlurSize = _blurSize;
        _oldBlurIterations = _blurIterations;
        _oldDownsample = _downsample;
    }

    void Start() {
        _sharedMaterial = GetComponent<MeshRenderer>().sharedMaterial;
        _blurShader = Shader.Find("Hidden/KawaseBlur");
        if (_blurShader == null)
            Debug.LogError("缺少Hidden/KawaseBlur Shader");
    }

    bool _blurParamChanged;
    void Update()
    {
        if (_blurParamChanged)
        {
            _oldBlurOn = _blurOn;
            _oldBlurSize = _blurSize;
            _oldBlurIterations = _blurIterations;
            _oldDownsample = _downsample;
        }

        if (_blurOn != _oldBlurOn || _blurSize != _oldBlurSize || _blurIterations != _oldBlurIterations || _downsample!= _oldDownsample)
        {
            _blurParamChanged = true;
        }
    }

    //创建反射用的摄像机
    Camera CreateReflectionCamera(Camera cam) {
        //生成Camera
        String reflName = gameObject.name + "Reflection" + cam.name; 
        GameObject go = new GameObject(reflName);
        //go.hideFlags = HideFlags.HideAndDontSave;
        go.hideFlags = HideFlags.HideAndDontSave;
        Camera reflectCamera = go.AddComponent<Camera>();
        //设置反射相机的参数
        HoldCameraSettings(reflectCamera);
        //创建RT并绑定Camera
        if (!reflectCamera.targetTexture) {
            reflectCamera.targetTexture = CreateTexture(cam);
        }

        return reflectCamera;
    }
    //设置反射相机的参数
    void HoldCameraSettings(Camera heplerCam)
    {
        heplerCam.backgroundColor = Color.black;
        heplerCam.clearFlags = _reflectSkybox ? CameraClearFlags.Skybox : CameraClearFlags.SolidColor;
        heplerCam.renderingPath = RenderingPath.Forward;
        heplerCam.cullingMask = _reflectionMask;
        heplerCam.allowMSAA = false;
        heplerCam.enabled = false;
    }
    //创建RT 
    RenderTexture CreateTexture(Camera sourceCam) {
        int width = Mathf.RoundToInt(Screen.width / _downsample);
        int height = Mathf.RoundToInt(Screen.height / _downsample);
        RenderTextureFormat formatRT = sourceCam.allowHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
        RenderTexture rt = new RenderTexture(width, height, 24, formatRT);
        rt.hideFlags = HideFlags.DontSave;
        return rt;
    }
    //内置回调函数,物体渲染之前会先调用该函数
    void OnWillRenderObject() {
        Camera currentCam = Camera.current;
        if (currentCam == null) {
            return;
        }

#if !UNITY_EDITOR
        if (!currentCam.gameObject.CompareTag("MainCamera"))
            return;
#endif

        if (_insideRendering) {
            return;
        }
        _insideRendering = true;

        if (_reflectionCamera == null) {
            _reflectionCamera = CreateReflectionCamera(currentCam);
        }

        //渲染反射图
        RenderReflection(currentCam, _reflectionCamera);

        //是否对反射图进行模糊
        if (_reflectionCamera && _sharedMaterial) {
            if (_blurOn) {
                if (_bluredReflectionTexture == null)
                    _bluredReflectionTexture = CreateTexture(currentCam);
                PostProcessTexture(currentCam, _reflectionCamera.targetTexture, _bluredReflectionTexture);
                _sharedMaterial.SetTexture(_reflectionTex, _bluredReflectionTexture);
            }
            else {
                _sharedMaterial.SetTexture(_reflectionTex, _reflectionCamera.targetTexture);
            }
        }

        _insideRendering = false;
    }

    //调用反射相机,渲染反射图
    void RenderReflection(Camera currentCam, Camera reflectCamera) {
        if (reflectCamera == null) {
            Debug.LogError("反射Camera无效");
            return;
        }
        if (_sharedMaterial && !_sharedMaterial.HasProperty(_reflectionTex))
        {
            Debug.LogError("Shader中缺少_ReflectionTex属性");
            return;
        }
        //保持反射相机的参数
        HoldCameraSettings(reflectCamera);

        if (_reflectSkybox) {
            if (currentCam.gameObject.GetComponent(typeof(Skybox))) {
                Skybox sb = (Skybox)reflectCamera.gameObject.GetComponent(typeof(Skybox));
                if (!sb) {
                    sb = (Skybox)reflectCamera.gameObject.AddComponent(typeof(Skybox));
                }
                sb.material = ((Skybox)currentCam.GetComponent(typeof(Skybox))).material;
            }
        }

        bool isInvertCulling = GL.invertCulling;
        GL.invertCulling = true;

        Transform reflectiveSurface = this.transform; //waterHeight;

        Vector3 eulerA = currentCam.transform.eulerAngles;

        reflectCamera.transform.eulerAngles = new Vector3(-eulerA.x, eulerA.y, eulerA.z);
        reflectCamera.transform.position = currentCam.transform.position;

        Vector3 pos = reflectiveSurface.transform.position;
        pos.y = reflectiveSurface.position.y;
        Vector3 normal = reflectiveSurface.transform.up;
        float d = -Vector3.Dot(normal, pos) - _clipPlaneOffset;
        Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);

        Matrix4x4 reflection = Matrix4x4.zero;
        reflection = CalculateReflectionMatrix(reflection, reflectionPlane);
        _oldpos = currentCam.transform.position;
        Vector3 newpos = reflection.MultiplyPoint(_oldpos);

        reflectCamera.worldToCameraMatrix = currentCam.worldToCameraMatrix * reflection;

        Vector4 clipPlane = CameraSpacePlane(reflectCamera, pos, normal, 1.0f);

        Matrix4x4 projection = currentCam.projectionMatrix;
        projection = CalculateObliqueMatrix(projection, clipPlane);
        reflectCamera.projectionMatrix = projection;

        reflectCamera.transform.position = newpos;
        Vector3 euler = currentCam.transform.eulerAngles;
        reflectCamera.transform.eulerAngles = new Vector3(-euler.x, euler.y, euler.z);

        reflectCamera.Render();

        GL.invertCulling = isInvertCulling;
    }

    static Matrix4x4 CalculateObliqueMatrix(Matrix4x4 projection, Vector4 clipPlane) {
        Vector4 q = projection.inverse * new Vector4(
            Mathf.Sign(clipPlane.x),
            Mathf.Sign(clipPlane.y),
            1.0F,
            1.0F
            );
        Vector4 c = clipPlane * (2.0F / (Vector4.Dot(clipPlane, q)));
        // third row = clip plane - fourth row
        projection[2] = c.x - projection[3];
        projection[6] = c.y - projection[7];
        projection[10] = c.z - projection[11];
        projection[14] = c.w - projection[15];

        return projection;
    }

    static Matrix4x4 CalculateReflectionMatrix(Matrix4x4 reflectionMat, Vector4 plane) {
        reflectionMat.m00 = (1.0F - 2.0F * plane[0] * plane[0]);
        reflectionMat.m01 = (-2.0F * plane[0] * plane[1]);
        reflectionMat.m02 = (-2.0F * plane[0] * plane[2]);
        reflectionMat.m03 = (-2.0F * plane[3] * plane[0]);

        reflectionMat.m10 = (-2.0F * plane[1] * plane[0]);
        reflectionMat.m11 = (1.0F - 2.0F * plane[1] * plane[1]);
        reflectionMat.m12 = (-2.0F * plane[1] * plane[2]);
        reflectionMat.m13 = (-2.0F * plane[3] * plane[1]);

        reflectionMat.m20 = (-2.0F * plane[2] * plane[0]);
        reflectionMat.m21 = (-2.0F * plane[2] * plane[1]);
        reflectionMat.m22 = (1.0F - 2.0F * plane[2] * plane[2]);
        reflectionMat.m23 = (-2.0F * plane[3] * plane[2]);

        reflectionMat.m30 = 0.0F;
        reflectionMat.m31 = 0.0F;
        reflectionMat.m32 = 0.0F;
        reflectionMat.m33 = 1.0F;

        return reflectionMat;
    }

    Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign) {
        Vector3 offsetPos = pos + normal * _clipPlaneOffset;
        Matrix4x4 m = cam.worldToCameraMatrix;
        Vector3 cpos = m.MultiplyPoint(offsetPos);
        Vector3 cnormal = m.MultiplyVector(normal).normalized * sideSign;

        return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
    }

    //对反射图进行图像处理(利用command buffer实现)
    private Dictionary<Camera, CommandBuffer> _cameras = new Dictionary<Camera, CommandBuffer>();
    void PostProcessTexture(Camera cam, RenderTexture source, RenderTexture dest)
    {
        //参数有变化需要刷新commandbuffer
        if (_blurParamChanged)
        {
            if (_cameras.ContainsKey(cam))
                cam.RemoveCommandBuffer(CameraEvent.BeforeForwardOpaque, _cameras[cam]);
            _cameras.Remove(cam);
        }
        //已经设置了commandbuffer就不用再执行了
        if (_cameras.ContainsKey(cam))
            return;

        CommandBuffer buf = new CommandBuffer();
        buf.name = "Blur Reflection Texture";
        _cameras[cam] = buf;
        float width = source.width;
        float height = source.height;
        int rtW = Mathf.RoundToInt(width / _downsample);
        int rtH = Mathf.RoundToInt(height / _downsample);

        int blurredID = Shader.PropertyToID("_Temp1");
        int blurredID2 = Shader.PropertyToID("_Temp2");
        buf.GetTemporaryRT(blurredID, rtW, rtH, 0, FilterMode.Bilinear, source.format);
        buf.GetTemporaryRT(blurredID2, rtW, rtH, 0, FilterMode.Bilinear, source.format);

        buf.Blit((Texture)source, blurredID);
        for (int i = 0; i < _blurIterations; i++)
        {
            float iterationOffs = (i * 1.0f);
            buf.SetGlobalFloat("_Offset", iterationOffs / _downsample + _blurSize);
            buf.Blit(blurredID, blurredID2, BlurMaterial, 0);
            buf.Blit(blurredID2, blurredID, BlurMaterial, 0);
        }
        buf.Blit(blurredID, dest);

        buf.ReleaseTemporaryRT(blurredID);
        buf.ReleaseTemporaryRT(blurredID2);

        cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, buf);
    }
}

用于测试反射的水面Shader TestPlannarReflectionShader.shader

Shader "Unlit/TestPlannarReflectionShader"
{
    Properties
    {
        _ReflectionTex ("ReflectionTex", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 texcoord1 : TEXCOORD1;
            };

            sampler2D _ReflectionTex;
            float4 _ReflectionTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                float4 clipPos = UnityObjectToClipPos(v.vertex);
                float4 screenPos = ComputeScreenPos(clipPos);
                o.texcoord1 = screenPos;

                o.vertex = clipPos;
                o.uv = TRANSFORM_TEX(v.uv, _ReflectionTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 屏幕坐标
                float2 screenPos = i.texcoord1 = i.texcoord1 / i.texcoord1.w;

                fixed4 col = tex2D(_ReflectionTex, screenPos);
                return col;
            }
            ENDCG
        }
    }
}