首页 > 代码库 > Unity3d 实时折射与反射

Unity3d 实时折射与反射

这里只贴出了实时折射与反射的脚本与shader,

关于NGUI部分则请大家自行下载。

这个版本主要缺点是折射部分平面的Layer必须是water层,如果有哪位高手能把这一块去掉,请记得把代码回复到这篇文章下,谢谢!

Water.cs

using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
/// <summary>
/// 水面
/// </summary>
[AddComponentMenu("GameCore/Effect/Water/Water (Base)")]
[ExecuteInEditMode]
public class Water : MonoBehaviour
{
    public enum FlageWaterRefType
    {
        Both = 0,
        Reflection = 1,
        Refraction = 2
    }
    public bool DisablePixelLights = false;
    public LayerMask Layers = -1;
    public int TexSize = 512;
    public FlageWaterRefType RefType = FlageWaterRefType.Both;   
    public float ReflectClipPlaneOffset = 0;
    public float RefractionAngle = 0;

    private static Camera _reflectionCamera;
    private static Camera _refractionCamera;

    private int _OldTexSize = 0;
    private RenderTexture _reflectionRenderTex;
    private RenderTexture _refractionRenderTex;

    private bool _insideRendering = false;
    private float _refType = (float)FlageWaterRefType.Both;

    void OnWillRenderObject()
    {

        if (!enabled || !renderer || !renderer.sharedMaterial || !renderer.enabled)
            return;
        Camera cam = Camera.current;
        if (!cam)
            return;
        Material[] materials = renderer.sharedMaterials;
        if (_insideRendering)
            return;
        _insideRendering = true;
        int oldPixelLightCount = QualitySettings.pixelLightCount;
        if (DisablePixelLights)
            QualitySettings.pixelLightCount = 0;
        if (RefType == FlageWaterRefType.Both || RefType == FlageWaterRefType.Reflection)
        {
            DrawReflectionRenderTexture(cam);
            foreach (Material mat in materials)
            {
                if (mat.HasProperty("_ReflectionTex"))
                    mat.SetTexture("_ReflectionTex", _reflectionRenderTex);
            }
        }

        if (RefType == FlageWaterRefType.Both || RefType == FlageWaterRefType.Refraction)
        {
            this.gameObject.layer = 4;
            DrawRefractionRenderTexture(cam);
            foreach (Material mat in materials)
            {
                if (mat.HasProperty("_RefractionTex"))
                    mat.SetTexture("_RefractionTex", _refractionRenderTex);
            }
        }
        _refType = (float)RefType;
        Matrix4x4 projmtx = CoreTool.UV_Tex2DProj2Tex2D(transform, cam);
        foreach (Material mat in materials)
        {
            mat.SetMatrix("_ProjMatrix", projmtx);
            mat.SetFloat("_RefType", _refType);
        }
        if (DisablePixelLights)
            QualitySettings.pixelLightCount = oldPixelLightCount;
        _insideRendering = false;
    }

    /// <summary>
    /// 绘制反射RenderTexture
    /// </summary>
    private void DrawReflectionRenderTexture(Camera cam)
    {
        Vector3 pos = transform.position;
        Vector3 normal = transform.up;

        CreateObjects(cam,ref _reflectionRenderTex, ref _reflectionCamera);

        CoreTool.CloneCameraModes(cam, _reflectionCamera);

        float d = -Vector3.Dot(normal, pos) - ReflectClipPlaneOffset;
        Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);


        Matrix4x4 reflection = CoreTool.CalculateReflectionMatrix(Matrix4x4.zero, reflectionPlane);

        Vector3 oldpos = cam.transform.position;
        Vector3 newpos = reflection.MultiplyPoint(oldpos);
        _reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
        
        // Setup oblique projection matrix so that near plane is our reflection
        // plane. This way we clip everything below/above it for free.
        Vector4 clipPlane = CoreTool.CameraSpacePlane(_reflectionCamera, pos, normal, 1.0f, ReflectClipPlaneOffset);

        Matrix4x4 projection = cam.projectionMatrix;

        projection = CoreTool.CalculateObliqueMatrix(projection, clipPlane, -1);

        _reflectionCamera.projectionMatrix = projection;

        _reflectionCamera.cullingMask = ~(1 << 4) & Layers.value; // never render water layer
        _reflectionCamera.targetTexture = _reflectionRenderTex;

        GL.SetRevertBackfacing(true);
        _reflectionCamera.transform.position = newpos;
        Vector3 euler = cam.transform.eulerAngles;
        _reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
        _reflectionCamera.Render();
        _reflectionCamera.transform.position = oldpos;
        GL.SetRevertBackfacing(false);
    }

    /// <summary>
    /// 绘制折射RenderTexture
    /// </summary>
    private void DrawRefractionRenderTexture(Camera cam)
    {
        CreateObjects(cam, ref _refractionRenderTex, ref _refractionCamera);
        CoreTool.CloneCameraModes(cam, _refractionCamera);

        Vector3 pos = transform.position;
        Vector3 normal = transform.up;

        Matrix4x4 projection = cam.worldToCameraMatrix;
        projection *= Matrix4x4.Scale(new Vector3(1,Mathf.Clamp(1-RefractionAngle,0.001f,1),1));
        _refractionCamera.worldToCameraMatrix = projection;

        Vector4 clipPlane = CoreTool.CameraSpacePlane(_refractionCamera, pos, normal, 1.0f, 0);
        projection = cam.projectionMatrix;
        projection[2] = clipPlane.x + projection[3];//x
        projection[6] = clipPlane.y + projection[7];//y
        projection[10] = clipPlane.z + projection[11];//z
        projection[14] = clipPlane.w + projection[15];//w

        _refractionCamera.projectionMatrix = projection;

        _refractionCamera.cullingMask = ~(1 << 4) & Layers.value; // never render water layer
        _refractionCamera.targetTexture = _refractionRenderTex;       
        
        _refractionCamera.transform.position = cam.transform.position;
        _refractionCamera.transform.eulerAngles = cam.transform.eulerAngles;
        _refractionCamera.Render();        
    }

    void OnDisable()
    {
        if (_reflectionRenderTex)
        {
            DestroyImmediate(_reflectionRenderTex);
            _reflectionRenderTex = null;
        }        
        if (_reflectionCamera)
        {
            DestroyImmediate(_reflectionCamera.gameObject);
            _reflectionCamera = null;
        }

        if (_refractionRenderTex)
        {
            DestroyImmediate(_refractionRenderTex);
            _refractionRenderTex = null;
        }
        if (_refractionCamera)
        {
            DestroyImmediate(_refractionCamera.gameObject);
            _refractionCamera = null;
        }        
    }

    void CreateObjects(Camera srcCam, ref RenderTexture renderTex, ref Camera destCam)
    {
        // Reflection render texture
        if (!renderTex || _OldTexSize != TexSize)
        {
            if (renderTex)
                DestroyImmediate(renderTex);
            renderTex = new RenderTexture(TexSize, TexSize, 0);
            renderTex.name = "__RefRenderTexture" + renderTex.GetInstanceID();
            renderTex.isPowerOfTwo = true;
            renderTex.hideFlags = HideFlags.DontSave;
            renderTex.antiAliasing = 4;
            renderTex.anisoLevel = 0;
            _OldTexSize = TexSize;
        }

        if (!destCam) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
        {
            GameObject go = new GameObject("__RefCamera for " + srcCam.GetInstanceID(), typeof(Camera), typeof(Skybox));
            destCam = go.camera;
            destCam.enabled = false;
            destCam.transform.position = transform.position;
            destCam.transform.rotation = transform.rotation;
            destCam.gameObject.AddComponent("FlareLayer");
            go.hideFlags = HideFlags.HideAndDontSave;
        }
    }
}

WaterEditor.cs

using UnityEngine;
using System.Collections;
using System;
using UnityEditor;

[CustomEditor(typeof(Water))]
public class WaterEditor : Editor
{
    

    GUIContent[] _renderTextureOptions = new GUIContent[8] {new GUIContent("16"), new GUIContent("32"), new GUIContent("64"), new GUIContent("128"), 
        new GUIContent("256"), new GUIContent("512"), new GUIContent("1024"), new GUIContent("2048") };
    int[] _renderTextureSize = new int[8] { 16, 32, 64, 128, 256, 512, 1024, 2048 };
    public override void OnInspectorGUI()
    {
        Water water = target as Water;
        EditorGUILayout.PropertyField(this.serializedObject.FindProperty("RefType"), new GUIContent("RefType"));
        EditorGUILayout.PropertyField(this.serializedObject.FindProperty("DisablePixelLights"), new GUIContent("DisablePixelLights"));
        EditorGUILayout.PropertyField(this.serializedObject.FindProperty("Layers"), new GUIContent("Layers"));
        EditorGUILayout.IntPopup(this.serializedObject.FindProperty("TexSize"), _renderTextureOptions, _renderTextureSize, new GUIContent("TexSize"));


        if (NGUIEditorTools.DrawHeader("Reflect Settings"))
        {
            NGUIEditorTools.BeginContents();
            {
                EditorGUILayout.Slider(this.serializedObject.FindProperty("ReflectClipPlaneOffset"),0,0.1f,new GUIContent("ClipPlane Offset"));
            }
            NGUIEditorTools.EndContents();
        }

        if (NGUIEditorTools.DrawHeader("Refraction Settings"))
        {
            NGUIEditorTools.BeginContents();
            {
                EditorGUILayout.Slider(this.serializedObject.FindProperty("RefractionAngle"),0,1, new GUIContent("Refraction Angle"));
            }
            NGUIEditorTools.EndContents();
        }
        this.serializedObject.ApplyModifiedProperties();
    }
}

CoreTool.cs

using System.Collections;
using System;
using UnityEngine;

/// <summary>
/// 工具类
/// </summary>
public static class CoreTool
{
    #region Config配置
    /// <summary>
    /// 验证当前文件是否为配置文件
    /// </summary>
    /// <param name="filePath">文件路径</param>
    /// <returns></returns>
    public static bool IsConfig(string filePath)
    {
        return true;
    }
    #endregion

    #region Camera
    /// <summary>
    /// 将源摄像机状态克隆到目标相机
    /// </summary>
    /// <param name="src">源相机</param>
    /// <param name="dest">目标相机</param>
    public static void CloneCameraModes(Camera src, Camera dest)
    {
        if (dest == null)
            return;
        // set camera to clear the same way as current camera
        dest.clearFlags = src.clearFlags;
        dest.backgroundColor = src.backgroundColor;
        if (src.clearFlags == CameraClearFlags.Skybox)
        {
            Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
            Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
            if (!sky || !sky.material)
            {
                mysky.enabled = false;
            }
            else
            {
                mysky.enabled = true;
                mysky.material = sky.material;
            }
        }
        // update other values to match current camera.
        // even if we are supplying custom camera&projection matrices,
        // some of values are used elsewhere (e.g. skybox uses far plane)
        dest.depth = src.depth;
        dest.farClipPlane = src.farClipPlane;
        dest.nearClipPlane = src.nearClipPlane;
        dest.orthographic = src.orthographic;
        dest.fieldOfView = src.fieldOfView;
        dest.aspect = src.aspect;
        dest.orthographicSize = src.orthographicSize;
    }

    /// <summary>
    /// 计算反射矩阵
    /// </summary>
    /// <param name="reflectionMat">原始矩阵</param>
    /// <param name="plane">反射平面</param>
    /// <returns>反射矩阵</returns>
    public static Matrix4x4 CalculateReflectionMatrix(Matrix4x4 reflectionMat, Vector4 plane)
    {
        reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
        reflectionMat.m01 = (-2F * plane[0] * plane[1]);
        reflectionMat.m02 = (-2F * plane[0] * plane[2]);
        reflectionMat.m03 = (-2F * plane[3] * plane[0]);

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

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

        reflectionMat.m30 = 0F;
        reflectionMat.m31 = 0F;
        reflectionMat.m32 = 0F;
        reflectionMat.m33 = 1F;
        return reflectionMat;
    }

    /// <summary>
    /// 计算指定平面在摄像机中的空间位置
    /// </summary>
    /// <param name="cam">摄像机</param>
    /// <param name="pos">平面上的点</param>
    /// <param name="normal">平面法线</param>
    /// <param name="sideSign">1:平面正面,-1:平面反面</param>
    /// <param name="clipPlaneOffset">平面法线位置偏移量</param>
    /// <returns></returns>
    public static Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign,float clipPlaneOffset)
    {        
        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));
    }

    /// <summary>
    /// 由剪裁面计算投影倾斜矩阵
    /// </summary>
    /// <param name="projection">投影矩阵</param>
    /// <param name="clipPlane">剪裁面</param>
    /// <param name="sideSign">剪裁平面(-1:平面下面,1:平面上面)</param>
    public static Matrix4x4 CalculateObliqueMatrix(Matrix4x4 projection, Vector4 clipPlane,float sideSign)
    {
        Vector4 q = projection.inverse * new Vector4(
            sgn(clipPlane.x),
            sgn(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 + Mathf.Sign(sideSign)*projection[3];
        projection[6] = c.y + Mathf.Sign(sideSign) * projection[7];
        projection[10] = c.z + Mathf.Sign(sideSign) * projection[11];
        projection[14] = c.w + Mathf.Sign(sideSign) * projection[15];
        return projection;
    }

    private static float sgn(float a)
    {
        if (a > 0.0f) return 1.0f;
        if (a < 0.0f) return -1.0f;
        return 0.0f;
    }

    /// <summary>
    /// 由水平、垂直距离修改倾斜矩阵
    /// </summary>
    /// <param name="projMatrix">倾斜矩阵</param>
    /// <param name="horizObl">水平方向</param>
    /// <param name="vertObl">垂直方向</param>
    /// <returns>修改后的倾斜矩阵</returns>
    public static Matrix4x4 CalculateObliqueMatrix(Matrix4x4 projMatrix, float horizObl, float vertObl)
    {
        Matrix4x4 mat = projMatrix;
	    mat[0, 2] = horizObl;
	    mat[1, 2] = vertObl;
	    return mat;
    }
    #endregion

    #region Shader Matrix4x4
    /// <summary>
    /// tex2DProj到tex2D的uv纹理转换矩阵
    /// 在shader中,
    /// vert=>o.posProj = mul(_ProjMatrix, v.vertex);
    /// frag=>tex2D(_RefractionTex,float2(i.posProj) / i.posProj.w)
    /// </summary>
    /// <param name="transform">要显示纹理的对象</param>
    /// <param name="cam">当前观察的摄像机</param>
    /// <returns>返回转换矩阵</returns>
    public static Matrix4x4 UV_Tex2DProj2Tex2D(Transform transform,Camera cam)
    {
        Matrix4x4 scaleOffset = Matrix4x4.TRS(
            new Vector3(0.5f, 0.5f, 0.5f), Quaternion.identity, new Vector3(0.5f, 0.5f, 0.5f));
        Vector3 scale = transform.lossyScale;
        Matrix4x4 _ProjMatrix = transform.localToWorldMatrix * Matrix4x4.Scale(new Vector3(1.0f / scale.x, 1.0f / scale.y, 1.0f / scale.z));
        _ProjMatrix = scaleOffset * cam.projectionMatrix * cam.worldToCameraMatrix * _ProjMatrix;
        return _ProjMatrix;
    }
    #endregion
}

Shader

Shader "GameCore/Mobile/Water/Diffuse" 
{
    Properties {		
        _ReflectionTex ("Reflection", 2D) = "white" {}
		_RefractionTex ("Refraction", 2D) = "white" {}	
		_RefColor("Color",Color) = (1,1,1,1)
	}
	SubShader {
        Tags {
            "RenderType"="Opaque"}
		LOD 100
		Pass {
            CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			uniform float4x4 _ProjMatrix;
			uniform float _RefType;
            sampler2D _ReflectionTex;
			sampler2D _RefractionTex;
            float4 _RefColor;
            struct outvertex {
                float4 pos : SV_POSITION;
                float4 uv0 : TEXCOORD0;
				float4 refparam : COLOR0;//r:fresnel,g:none,b:none,a:none
            };
            
			outvertex vert(appdata_tan v) {
                outvertex o;
                o.pos = mul (UNITY_MATRIX_MVP,v.vertex);
                float4 posProj = mul(_ProjMatrix, v.vertex);
				o.uv0 = posProj;				
				float3 r =normalize(ObjSpaceViewDir(v.vertex));
				float d = saturate(dot(r,normalize(v.normal)));//r+(1-r)*pow(d,5)				
				o.refparam =float4(d,0,0,0);
				
				return o;
            }
										
			float4 frag(outvertex i) : COLOR {                
				half4 flecol = tex2D(_ReflectionTex,float2(i.uv0) / i.uv0.w);							
				half4 fracol = tex2D(_RefractionTex,float2(i.uv0) / i.uv0.w);				
				half4 outcolor = half4(1,1,1,1);				
				if(_RefType == 0)
				{
					outcolor = lerp(flecol,fracol,i.refparam.r);
				}
				else if(_RefType == 1)
				{
					outcolor = flecol;
				}
				else if(_RefType == 2)
				{
					outcolor = fracol;
				}	
                return outcolor*_RefColor;
            }
			ENDCG
		}
	}
}