shader反射——平面反射

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wodownload2/article/details/88956303

C#代码:

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

public class PlanarReflection : MonoBehaviour
{
    private Camera reflectionCamera = null;
    private RenderTexture reflectionRT = null;
    private Material reflectionMaterial = null;
    private static bool isReflectionCameraRendering = false;

    private void OnWillRenderObject()
    {
        if (isReflectionCameraRendering) return;
        isReflectionCameraRendering = true;

        if (reflectionCamera == null)
        {
            var go = new GameObject("Reflection Camera");
            reflectionCamera = go.AddComponent<Camera>();
            reflectionCamera.CopyFrom(Camera.current);
        }

        if (reflectionRT == null)
        {
            reflectionRT = RenderTexture.GetTemporary(1024, 1024, 24);
        }

        UpdateCameraParams(Camera.current, reflectionCamera);
        reflectionCamera.targetTexture = reflectionRT;
        reflectionCamera.enabled = false;

        var reflectM = CalculateReflectMatrix(transform.up, transform.position);
        reflectionCamera.worldToCameraMatrix = Camera.current.worldToCameraMatrix * reflectM;
        GL.invertCulling = true;
        reflectionCamera.Render();
        GL.invertCulling = false;

        if(reflectionMaterial == null)
        {
            var renderer = GetComponent<Renderer>();
            reflectionMaterial = renderer.sharedMaterial;
        }
        reflectionMaterial.SetTexture("_ReflectionTex", reflectionRT);
        isReflectionCameraRendering = false;
    }

    Matrix4x4 CalculateReflectMatrix(Vector3 normal, Vector3 positionOnPlane)
    {
        var d = -Vector3.Dot(normal, positionOnPlane);
        var reflectM = new Matrix4x4();
        reflectM.m00 = 1 - 2 * normal.x * normal.x;
        reflectM.m01 = -2 * normal.x * normal.y;
        reflectM.m02 = -2 * normal.x * normal.z;
        reflectM.m03 = -2 * d * normal.x;

        reflectM.m10 = -2 * normal.x * normal.y;
        reflectM.m11 = 1 - 2 * normal.y * normal.y;
        reflectM.m12 = -2 * normal.y * normal.z;
        reflectM.m13 = -2 * d * normal.y;

        reflectM.m20 = -2 * normal.x * normal.z;
        reflectM.m21 = -2 * normal.y * normal.z;
        reflectM.m22 = 1 - 2 * normal.z * normal.z;
        reflectM.m23 = -2 * d * normal.z;

        reflectM.m30 = 0;
        reflectM.m31 = 0;
        reflectM.m32 = 0;
        reflectM.m33 = 1;
        return reflectM;
    }

    private void UpdateCameraParams(Camera srcCamera, Camera destCamera)
    {
        if (destCamera == null || srcCamera == null)
            return;

        destCamera.clearFlags = srcCamera.clearFlags;
        destCamera.backgroundColor = srcCamera.backgroundColor;
        destCamera.farClipPlane = srcCamera.farClipPlane;
        destCamera.nearClipPlane = srcCamera.nearClipPlane;
        destCamera.orthographic = srcCamera.orthographic;
        destCamera.fieldOfView = srcCamera.fieldOfView;
        destCamera.aspect = srcCamera.aspect;
        destCamera.orthographicSize = srcCamera.orthographicSize;
    }
}

shader代码:

Shader "Custom/PlanarReflection"
{
	SubShader
	{
		Tags { "RenderType" = "Opaque" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float4 screenPos: TEXCOORD0;
			};

			sampler2D _ReflectionTex;

			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.screenPos = ComputeScreenPos(o.vertex);
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				fixed4 col = tex2D(_ReflectionTex,  i.screenPos.xy / i.screenPos.w);
				return col;
			}
			ENDCG
		}
	}
}

运行的结果为:
在这里插入图片描述
原理解释:
Planar Reflection
平面反射顾名思义,就是在平面上运用的反射,名字也正是这种反射方式的限制,只能用于平面,
而且是高度一致的平面,多个高度不一的平面效果也不正确。一般情况下,一个平面整体反射效果
基本可以满足需求,而且对于实时渲染反射效果,需要将反射的物体多渲染一次,控制好层级数量
的话,性能至少是在可以接受的范围,并且相对于其他几种反射效果来说,平面反射的效果是最好的,
所以Planar Reflection目前是实时反射效果中使用的比较多的一种方案。

平面反射效果实现
首先,我们需要一个相机,与当前正常相机关于平面对称,也就是说,我们把正常相机变换到这个
对称的位置,然后将这个相机的渲染结果输出到一张RT上,就可以得到对称位置的图像了。我们得到了
平面反射的矩阵R,下面我们需要考虑的就是在哪个阶段使用这个反射矩阵R。我们知道,渲染物体
需要通过MVP变换,物体首先通过M矩阵,从物体空间变换到世界空间,然后通过V矩阵,从世界空间
变换到视空间,最后通过投影矩阵P变换到裁剪空间。我们把R矩阵插在V之后,在一个物体在进行MV
变换后,变到正常相机坐标系下,然后再进行一次反射变换,就相当于变换到了相机对于屏幕对称的
相机坐标系下,然后再进行正常的投影变换,就可以得到反射贴图了。

要使用反射贴图,我们在shader中增加相应的Texture变量即可。不过这个贴图的采样并非使用正常的uv
坐标,因为我们的贴图是反射相机的输出的RT,假设这个RT我们输出在屏幕上,反射平面上当前像素点
对应位置我们屏幕上的位置作为uv坐标才能找到这一点对应RT上的位置。需要在vertex shader中通过
ComputeScreenPos计算屏幕坐标,fragment shader采样时进行透视校正纹理采样。

参考网址:
https://blog.csdn.net/puppet_master/article/details/80808486
项目地址:
https://gitee.com/yichichunshui/Reflection.git

猜你喜欢

转载自blog.csdn.net/wodownload2/article/details/88956303