Cg Programming/Unity/Water Reflection and Refraction
< Cg Programming | Unity
This page or section is an undeveloped draft or outline. You can help to develop the work, or you can ask for assistance in the project room. |
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;
}
}
}
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();
}
}
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 "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
}
}
}