shader反射——平面反射
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.****.net/puppet_master/article/details/80808486
项目地址:
https://gitee.com/yichichunshui/Reflection.git