UnityShader_倒影,水波倒影(代码已更新)(2)

相较于上一个水波倒影,这个水波倒影要更为复杂,但也更为真实,大体思路如下
利用脚本抓取水面的镜像,并在Shader中进行处理

先上个Gif解解馋,代码后面整理好再上传,最近实在事情多,早上优化到晚上的工作

原理剖析:
首先镜面反射的原理: 如下图,入射光经过表面光滑的平面产生关于平面法线的反射光,人眼收集到反射光产生成像
UnityShader_倒影,水波倒影(代码已更新)(2)_第1张图片
使用上面原理,我们只需要收集 从物体发出的反射光即可,看下图
我们从镜面看到的倒影其实是从 图中物体反射光方向 发出的光,因此,我们可以在视角方向关于镜面对称的位置 A 设置一个相机,方向与物体反射光方向反向,然后将 A 位置渲染的图像映在镜面,即产生我们的倒影。
UnityShader_倒影,水波倒影(代码已更新)(2)_第2张图片
问题: 当出现如下图,C’为反射相机,C为视角方向,P为镜面。
此时C’的相机范围能够照射到AB两个区域的物体,但是实际上,B区域的物体不应该被渲染出来,因为被P镜面挡住了,此时我们应该更改C’反射相机的剪裁面,将近剪裁面改成P所在的平面。(关于远近剪裁面详见《Unity Shader入门精要 4.6.7》,第二张图是关于远近剪裁面的简易图)
在这里插入图片描述
UnityShader_倒影,水波倒影(代码已更新)(2)_第3张图片
以上就是在镜面中的两个核心问题。下面我们来看看如何将上面问题解决并用于代码中。
关于问题1,反射矩阵:
如下图(加粗为矢量),Q为被反射点,Q’为反射点,P为镜面,n为镜面的单位法线,d为平面到原点的距离
k为平面P到Q点的方向向量,M为k所在直线与平面P的交点,且kn平行
n M+d=0 ,(M是平面上任意一点的时候也满足上式)
∴Q’ = Q - 2kn
k= Qn - Mn
∴Q’ = Q - 2n(Qn -(-d))= Q-2n(Qn + d)

Q’x = (1-2nxnx)Qx-2nxnyQy-2nxnzQz-2nxd
Q’y = -2
nxnyQx+(1-2nyny)Qy-2nynzQz-2nyd
Q’z = -2nxnzQx-2nynzQy+(1-2nznz)Qz-2nz*d
上式可以写成如下
UnityShader_倒影,水波倒影(代码已更新)(2)_第4张图片
反射矩阵就是中间那个矩阵。
UnityShader_倒影,水波倒影(代码已更新)(2)_第5张图片
一篇同样关于反射矩阵的描写
关于问题2,替换剪裁平面:
剪裁矩阵的相关计算链接

C#的取镜面代码

using UnityEngine;

using System.Collections;

[ExecuteInEditMode] 
public class Mirror : MonoBehaviour
{
    public bool m_DisablePixelLights = true;
    public int m_TextureSize = 256;
    public float m_ClipPlaneOffset = 0.07f;
	public bool m_IsFlatMirror = true;
    public Vector3 offset = new Vector3(0,0,0);
   
    public LayerMask m_ReflectLayers = -1;
       
    private Hashtable m_ReflectionCameras = new Hashtable(); 
   
    private RenderTexture m_ReflectionTexture = null;
    private int m_OldReflectionTextureSize = 0;
    
    //是否已经在渲染
    private static bool s_InsideRendering = false;

    public void OnWillRenderObject()
    {
        if( !enabled || !GetComponent<Renderer>() || !GetComponent<Renderer>().sharedMaterial || !GetComponent<Renderer>().enabled )
            return;
           
        Camera cam = Camera.current;//玩家视角相机
        if( !cam )
            return;
   
        if( s_InsideRendering )
            return;
        s_InsideRendering = true;
       
        Camera reflectionCamera;//反射相机
        CreateMirrorObjects( cam, out reflectionCamera );
       
        Vector3 pos = transform.position + offset;
		Vector3 normal;
		if(m_IsFlatMirror){
        	normal = transform.up;
		}
		else{ 
			normal= transform.position - cam.transform.position ;
			normal.Normalize();
		}
        int oldPixelLightCount = QualitySettings.pixelLightCount;//限制像素光的数量
        if( m_DisablePixelLights )
            QualitySettings.pixelLightCount = 0;
       
        UpdateCameraModes( cam, reflectionCamera );

        float d = -Vector3.Dot (normal, pos) - m_ClipPlaneOffset;
        Vector4 reflectionPlane = new Vector4 (normal.x, normal.y, normal.z, d);
   
        Matrix4x4 reflection = Matrix4x4.zero;//反射矩阵
        CalculateReflectionMatrix (ref reflection, reflectionPlane);
        Vector3 oldpos = cam.transform.position;
        Vector3 newpos = reflection.MultiplyPoint( oldpos );//反射相机的位置?
        reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;//反射相机的worldToCameraMatrix矩阵进行反射
   
        // 计算剪切面(把原相机的位置及法线经上面计算的反射矩阵变化)
        Vector4 clipPlane = CameraSpacePlane( reflectionCamera, pos, normal, 1.0f );
        Matrix4x4 projection = cam.projectionMatrix; //投影矩阵
        CalculateObliqueMatrix (ref projection, clipPlane);//计算出剪切面相关的投影矩阵,剪切面以下内容不显示
        reflectionCamera.projectionMatrix = projection;
       
        reflectionCamera.cullingMask = ~(1<<4) & m_ReflectLayers.value; //设置可以反射的物体,已经忽略了本身
        reflectionCamera.targetTexture = m_ReflectionTexture;
        //注意当相机镜像后,其渲染的模型的顶点绕序也会镜像,需要将背面裁剪设置为正面裁剪,渲染结束后再修改回来
        GL.invertCulling = 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.invertCulling = false;
        Material[] materials = GetComponent<Renderer>().sharedMaterials;
        foreach( Material mat in materials ) {
            if( mat.HasProperty("_Ref") )
                mat.SetTexture( "_Ref", m_ReflectionTexture );
        }
        if( m_DisablePixelLights )
            QualitySettings.pixelLightCount = oldPixelLightCount;
       
        s_InsideRendering = false;
    }
   
    void OnDisable()
    {
        if( m_ReflectionTexture ) {
            DestroyImmediate( m_ReflectionTexture );
            m_ReflectionTexture = null;
        }
        foreach( DictionaryEntry kvp in m_ReflectionCameras )
            DestroyImmediate( ((Camera)kvp.Value).gameObject );
        m_ReflectionCameras.Clear();
    }
   
    /// 
    /// 设置目标相机的参数,大部分从源相机中复制过去
    /// 
    /// 
    /// 
    private void UpdateCameraModes( Camera src, Camera dest )
    {
        if( dest == null )
            return;

        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;
            }
        }

        dest.farClipPlane = src.farClipPlane;
        dest.nearClipPlane = src.nearClipPlane;
        dest.orthographic = src.orthographic;
        dest.fieldOfView = src.fieldOfView;
        dest.aspect = src.aspect;
        dest.orthographicSize = src.orthographicSize;
		dest.renderingPath = src.renderingPath;
    }
    /// 
    /// 创建反射相机及反射相机对应的照射纹理图
    /// 
    /// 
    /// 
    private void CreateMirrorObjects( Camera currentCamera, out Camera reflectionCamera )
    {
        reflectionCamera = null;
       

        if( !m_ReflectionTexture || m_OldReflectionTextureSize != m_TextureSize )
        {
            if( m_ReflectionTexture )
                DestroyImmediate( m_ReflectionTexture );
            m_ReflectionTexture = new RenderTexture( m_TextureSize, m_TextureSize, 16 );
            m_ReflectionTexture.name = "__MirrorReflection" + GetInstanceID();
            m_ReflectionTexture.isPowerOfTwo = true;
            m_ReflectionTexture.hideFlags = HideFlags.DontSave;
            m_OldReflectionTextureSize = m_TextureSize;
        }
       

        reflectionCamera = m_ReflectionCameras[currentCamera] as Camera;
        if( !reflectionCamera ) 
        {
            GameObject go = new GameObject( "Mirror Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox) );
            reflectionCamera = go.GetComponent<Camera>();
            reflectionCamera.enabled = false;
            reflectionCamera.transform.position = transform.position;
            reflectionCamera.transform.rotation = transform.rotation;
            go.hideFlags = HideFlags.HideAndDontSave;
            m_ReflectionCameras[currentCamera] = reflectionCamera;
        }       
    }
    
    /// 
    /// 阶跃函数 -1,0,1
    /// 
    /// 
    /// 
    private static float sgn(float a)
    {
        if (a > 0.0f) return 1.0f;
        if (a < 0.0f) return -1.0f;
        return 0.0f;
    }
    /// 
    /// 计算cam相机空间下的平面法线及位置
    /// 
    /// 计算的相机
    /// 
    /// 
    /// 1表示剪裁平面的法线为单位向量
    /// Vector4( cam平面法线, -cpos的法线 )
    private Vector4 CameraSpacePlane (Camera cam, Vector3 pos, Vector3 normal, float sideSign)
    {
        Vector3 offsetPos = pos + normal * m_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) );
    }
   
    /// 
    /// 将视角相机的远裁面替换成plane
    /// 
    /// 
    /// vector4(法线,平面位置)
    private static void CalculateObliqueMatrix (ref Matrix4x4 projection, Vector4 clipPlane)
    {
        //逆矩阵 * (sgn(clipPlane.x),sgn(clipPlane.y),1,1)
        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)));

        //矩阵第三列的值 = clipplane - 矩阵第四列的值
        projection[2]   = c.x - projection[3];
        projection[6]   = c.y - projection[7];
        projection[10]  = c.z - projection[11];
        projection[14]  = c.w - projection[15];
    }


    /// 
    /// 计算反射矩阵
    /// 
    /// 
    /// 
    private static void CalculateReflectionMatrix (ref 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;
    }
}

Shader中的渲染代码

Shader "Mirrors/Bumped Specular" 
{
	Properties {
		_Color ("Main Color", Color) = (1,1,1,1)
		_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
		[NoScaleOffset] _NoiseTex ("NoiseTex", 2D) = "white" {}             // 噪点图
		_BlendLevel("Main Material Blend Level",Range(0,1))=1
		_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)
		_Shininess ("Shininess", Range (0.03, 128)) = 0.078125
		_BumpMap ("Normalmap", 2D) = "bump" {}
		_Bumpness ("Bump Rate",Range(0,10))= 0.5
		_Ref ("Auto Generate!", 2D) = "white" {}
		_RefColor("Reflection Color",Color) = (1,1,1,1)
		_RefRate ("Reflective Rate", Range (0, 1)) = 1
		_Distortion ("Reflective Distortion", Range (0, 1)) = 0				// 镜面扭曲程度
		_NoiseScaleX ("NoiseScaleX", Range(0, 1)) = 0.1                     // 水平噪点放大系数
		_NoiseScaleY ("NoiseScaleY", Range(0, 1)) = 0.1                     // 垂直放大系数
		_NoiseSpeedX ("NoiseSpeedX", Range(0, 10)) = 1                      // 水平扰动速度
		_NoiseSpeedY ("NoiseSpeedY", Range(0, 10)) = 1                      // 垂直扰动速度
		_NoiseBrightOffset ("NoiseBrightOffset", Range(0, 0.9)) = 0.25      // 噪点图整体的数值偏移
		// _NoiseFalloff ("NoiseFalloff", Range(0, 1)) = 1                     // 扰动衰减
		// _MirrorRange ("MirrorRange", Range(0, 1)) = 1
	}
	SubShader 
	{ 
		Pass
		{
			Tags {"LightMode" = "ForwardBase" "RenderType"="Opaque" }
			LOD 400
			
			CGPROGRAM
			// #pragma surface surf BlinnPhong
			#pragma multi_compile_fwdbase
			//顶点函数定义
			#pragma vertex vert  
			//片元函数定义
			#pragma fragment frag
			// #pragma target 3.0
			// #pragma debug
			//引入必要的Unity库 如下面的UnityObjectToClipPos 就是库中函数
			#include "UnityCG.cginc"
			//引入光照库 _LightColor0需要用
			#include "Lighting.cginc"

			sampler2D _MainTex;
			sampler2D _BumpMap;
			sampler2D _NoiseTex;
			sampler2D _Ref;
			float4 _MainTex_ST;//图片的(平铺和偏移系数)如果要使图片的Tilling和Offset生效就必须定义
			float4 _BumpMap_ST;
			float4 _NoiseTex_ST;
			fixed4 _Color;
			half _Shininess;
			half _RefRate;
			half _Bumpness;
			half _BlendLevel;
			half _Distortion;
			fixed4 _RefColor;
			fixed _NoiseScaleX, _NoiseScaleY;
			fixed _NoiseSpeedX, _NoiseSpeedY;
			fixed _NoiseBrightOffset;
			// fixed _NoiseFalloff;
			// float _MirrorRange;

			struct appdata 
			{
				float4 vertex : POSITION;//每个顶点结构体必须有的
				float3 normal : NORMAL;//定义法线
				float4 tangent :TANGENT;//定义切线
				float4 maintex : TEXCOORD0;//存储主贴图纹理的坐标信息
				float4 noisetex : TEXCOORD1;//存储噪声纹理的坐标信息
				float4 bumptex : TEXCOORD2;//存储法线纹理的坐标信息
				float2 reftex : TEXCOORD3;//存储相机拍摄纹理的坐标信息
			};

			struct v2f
			{
				float4 pos : SV_POSITION;//每个片元结构体必须有的
				float3 lightDir : TEXCOORD0;
				float3 viewDir : TEXCOORD1;
				float4 uv1 : TEXCOORD2;//用于存储主贴图、噪声纹理信息
				float4 uv2 : TEXCOORD3;//用于存储法线、相机拍摄纹理信息
				float4 projPos :TEXCOORD4;
			};
			
			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);//把顶点从模型空间转换到剪裁空间
				o.uv1.xy = TRANSFORM_TEX(v.maintex, _MainTex);
				o.uv1.zw = TRANSFORM_TEX(v.noisetex, _NoiseTex);
				o.uv2.xy = TRANSFORM_TEX(v.bumptex, _BumpMap);
				o.projPos = ComputeGrabScreenPos(o.pos);
				// o.uv2.zw = v.reftex;
				//这里是UnityCG.cginc 库里面定义函数,利用normal和tangent生成对应的rotation矩阵(模型空间转到切线空间的变换矩阵)
				//这也是在上面必须定义normal和tangent的原因
				TANGENT_SPACE_ROTATION;
				o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));//将模型顶点的光照方向转到切线空间
				o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex));//将模型顶点的视角方向转到切线空间
				return o;
			} 

			fixed4 frag (v2f i) : SV_Target//返回一个RGBA到模型上
			{
				fixed3 lightDir = normalize(i.lightDir);
				fixed3 viewDir = normalize(i.viewDir);
				
				//这里十分奇怪,要在片元着色器里面计算下面的才不会发生偏移,如果在顶点着色器中计算的话下面的会发生随视角靠近扭曲的现象
				i.uv2.zw = i.projPos.xy/i.projPos.w;

				fixed2 ouvxy = fixed2( // 噪点图采样,用于主纹理的UV偏移的
				tex2D(_NoiseTex, i.uv1.zw + fixed2(_Time.x * _NoiseSpeedX, 0)).r,
				tex2D(_NoiseTex, i.uv1.zw + fixed2(0, _Time.x * _NoiseSpeedY)).r);
				ouvxy -= _NoiseBrightOffset; // 0~1 to ==> -_NoiseBrightOffset~ 1 - _NoiseBrightOffset
				ouvxy *= fixed2(_NoiseScaleX, _NoiseScaleY);    // 扰动放大系数
				
				// float scale = i.projPos.x / _MirrorRange;          // 用距离来作为扰动衰减
				// scale = lerp(scale, 1, (1 - _NoiseFalloff));    // 距离越近扰动越是衰减(即:与镜面距离越近,基本是不扰动的,所以我们可以看到边缘与镜面的像素是吻合的)
				// ouvxy *= scale;

				fixed4 packedNormal = tex2D(_BumpMap,i.uv2.xy + ouvxy);//采样_BumpMap里面的法线信息
				//UnpackNormal英文含义就是 解压法线 将法线从颜色信息里面解压出来
				//这里涉及到一个知识点 为什么法线贴图是蓝色调的
				//法线贴图里面法线都是存储在切线空间里面的
				//详细请看文末提到的博客
				fixed3 tangentNormal = UnpackNormal(packedNormal);
				//切线空间的法线是单位长度为1的,所以只要求出其中两个就可以利用长度求出另一个值
				tangentNormal.xy *= _Bumpness;
				tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy,tangentNormal.xy)));

				
				fixed3 albedo = tex2D(_MainTex,i.uv1.xy).rgb * _Color * _BlendLevel;//采样主贴图的纹理颜色
				//半罗伯特反射
				fixed3 diffuse = _LightColor0.rgb * albedo * (1+dot(lightDir,tangentNormal))/2;
				//Blinn-Phong模型高光 
				fixed3 halfView = normalize(lightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _SpecColor * pow(saturate(dot(tangentNormal,halfView)),_Shininess);

				fixed3 reflect = tex2D(_Ref,i.uv2.zw + tangentNormal.xy*_Distortion + ouvxy).rgb * _RefRate ;
				return fixed4(diffuse + specular + reflect,1);
			}
			ENDCG
		}
	}
	FallBack "Specular"
}

下图为QualitySettings.pixelLightCount参数设置的含义
UnityShader_倒影,水波倒影(代码已更新)(2)_第6张图片

完整代码百度盘,提取码:oyy9

你可能感兴趣的:(UnityShader)