目錄
四、製作將planeNormal和planePos傳遞給Shader的C#腳本
轉載:https://blog.csdn.net/linjf520/article/details/99647624
一、鏡面反射效果圖
二、製作Plane鏡面
不透明物體且先於箱子渲染,模板測試寫入1,是一個表面着色器Lambert漫反射+顏色
Shader "Unlit/PlaneShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_ColorTint("_Color Tint", Color) = (1,1,1,1)
}
SubShader
{
//Queue是Geometry+1,它會在MirrorShader之前渲染。即先於鏡面反射的物體渲染,爲了寫入模板值1
Tags { "RenderType" = "Opaque" "Queue" = "Geometry+1" }
LOD 300
//模板測試,寫入模板值爲1,目的:只讓鏡面部分產生鏡面效果,利用了模板測試進行的操作
Stencil
{
Ref 1
Comp Always
Pass Replace
}
CGPROGRAM
#pragma surface surf Lambert finalcolor:mycolor
#pragma target 3.0
sampler2D _MainTex;
sampler2D _BumpTex;
fixed4 _ColorTint;
struct Input {
float2 uv_MainTex;
};
void surf(Input IN, inout SurfaceOutput o) {
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb;
o.Alpha = tex.a;
}
void mycolor(Input IN, SurfaceOutput o, inout fixed4 color) {
color = color * _ColorTint;
}
ENDCG
}
FallBack "Legacy Shaders/Diffuse"
}
三、箱子Shader(被鏡面照射的物體)
原理:2個Pass,第一個Pass渲染出Plane下的鏡面物體,第二個Pass渲染出原本的物體;
第一個Pass主要進行如下步驟,具體也可直接看代碼理解。(可選看)
① Tags可看出是一個不透明隊列,且在Plane之後渲染,目的是爲了進行模板測試,只渲染出模板值爲1的部分,也就是位於Plane區域的部分。②Cull Front 裁剪正面,因爲鏡面物體渲染的是背面才正常,默認是裁剪背面,即渲染正面,你可以註釋掉這句代碼進行查看不正常的情況。
③ZTest Always 深度測試總是通過,目的是爲了將位於Plane物體下的鏡面物體渲染出來,否則鏡面物體無法通過深度測試而無法渲染出來,具體可註釋掉查看,默認是深度測試LEqual
④Blend 開啓透明混合(這就不解釋了吧)
⑤Stencil 深度測試, Ref指明模板值爲1,Comp Equal指明是隻有等於1的模板值區域能夠渲染出這個鏡面物體,若不瞭解什麼是模板測試,可百度或查看我這篇文章 https://blog.csdn.net/qq_39574690/article/details/106151722
⑥第一個Pass的頂點着色器計算出鏡面物體的頂點位置worldPos 和 物體和Plane鏡面的垂直距離distance,計算方式爲
distance = dot(planeNormal, 物體頂點位置-planePos);
dot(A,B) = |A||B|cos(A與B向量最小夾角),此時的A和B看成是單位向量,那麼dot(A,B)=cos(夾角)=垂直距離/|B|,此時還相差一個|B|距離(即(物體頂點位置-planePos)向量長度)的倍數,理論上要乘上這個向量長度纔是真正的垂直距離。所以你可以這麼做,對我代碼上的distance乘以這個向量長度。
計算出distance垂直距離後,通過planeNormal和distance計算出鏡面物體的頂點位置(世界空間),然後用這個新的頂點位置轉爲裁剪空間傳入v2f的vertex(作爲物體裁剪座標),所以此時已經完成了最關鍵的鏡面效果!下面是介紹透明、噪聲偏移效果。⑦Alpha透明值的計算,利用distance/最大可視範圍得到一個係數,用此係數進行lerp(maxAlpha, minAlpha, 係數)得到最終透明值,效果是離鏡面越遠的圖像越透明。
⑧噪聲偏移值offsetXY,它是在片元着色器進行計算的,從噪聲圖採樣得出一個噪聲偏移值,然後針對這個值先後進行了整體偏移offset,縮放scale,擾動衰減atten,其中擾動衰減在_NoiseAttenWeight值爲1時,模擬水平效果,離鏡面越遠的圖像受到噪聲偏移值的影響越大,不過在效果圖上表現的不是很明顯,所以你想要表達的很誇張的話,可以給noiseAtten乘以一個倍數即可。
⑨在片元着色器對三種情況進行了剔除discard操作:
⑴ 鏡面物體離Plane(鏡面)大於_MirrorVisableDistance時進行剔除
⑵透明度小於等於0時進行剔除
⑶鏡面物體超出Plane(鏡面)部分被剔除以上就是第一個Pass的介紹,而第二個Pass則是渲染正常物體情況,在片元着色器進行了對潛入鏡面的部分剔除discard
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Unlit/MirrorShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_NoiseTex("噪聲紋理", 2D) = "white" {}
[Space(10)]
_MirrorVisableDistance("鏡像可視最大深度", Range(0,4)) = 4
[Space(10)]
_MirrorMinAlpha("鏡面物體最小透明度", Range(0,1)) = 0
_MirrorMaxAlpha("鏡面物體最大透明度", Range(0,1)) = 1
[Space(10)]
_NoiseUVOffsetSpeedX("噪音UV X軸偏移速度", Range(0,10)) = 1
_NoiseUVOffsetSpeedY("噪音UV Y軸偏移速度", Range(0,10)) = 1
[Space(10)]
_NoiseOffset("噪音偏移值的整體偏移值", Range(0,0.9)) = 0.25
[Space(10)]
_NoiseScaleX("噪音縮放值X", Range(0,1)) = 1
_NoiseScaleY("噪音縮放值Y", Range(0,1)) = 1
[Space(10)]
//注意無擾動衰減,並不是無噪音偏移值的意思!衰減是針對噪音偏移值進行的
_NoiseAttenWeight("噪音偏移衰減權重(0時完全無擾動衰減,1時有擾動衰減)", Range(0,1)) = 1
[Space(10)]
_AlphaFScale("透明度衰減敏感度", Range(0, 5)) = 1
}
SubShader
{
//位於Plane之後渲染,注意看Queue是Geometry+2,目的是爲了進行模板測試
Tags { "RenderType" = "Transparent" "Queue" = "Geometry+2" }
Pass
{
Cull front //裁剪正面 因爲鏡面的物體是倒立的物體,背面是優先渲染的 可註釋掉這句代碼來看問題
ZTest Always //總是通過深度測試
ZWrite Off //禁止深度寫入
Blend SrcAlpha OneMinusSrcAlpha //開啓混合
//鏡面物體渲染只有通過模板測試,即模板值爲1的部分渲染
Stencil{
Ref 1
Comp Equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 worldPos : TEXCOORD1;
float4 worldNormal : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float3 planeNormal; //平面法線(世界空間的歸一化平面Y軸向量)
float3 planePos; //平面位置(世界座標)
float _MirrorVisableDistance;
float _MirrorMinAlpha;
float _MirrorMaxAlpha;
float _NoiseUVOffsetSpeedX;
float _NoiseUVOffsetSpeedY;
float _NoiseOffset;
fixed _NoiseScaleX;
fixed _NoiseScaleY;
fixed _NoiseAttenWeight;
float _AlphaFScale;
v2f vert(appdata v)
{
v2f o;
//1. 計算物體和平面距離distance 和 鏡面下的頂點位置worldPos(世界空間)
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
float3 p = worldPos.xyz - planePos.xyz;
float distance = dot(planeNormal, p); //dot(planeNormal, normalize(p)) * length(p); //Plane平面與物體頂點的垂直距離
worldPos.xyz = worldPos.xyz + (-planeNormal) * (distance * 2); //反向偏移2倍距離得到新的頂點位置(世界空間)
o.worldPos.xyz = worldPos.xyz;
o.worldPos.w = distance; //w值存儲distance
float alpha = lerp(_MirrorMaxAlpha, _MirrorMinAlpha, _AlphaFScale * (distance / _MirrorVisableDistance));
o.worldNormal.xyz = UnityObjectToWorldNormal(v.normal).xyz;
o.worldNormal.w = alpha; //w值存儲alpha
o.vertex = mul(UNITY_MATRIX_VP, worldPos); //世界轉裁剪空間
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
if (i.worldPos.w > _MirrorVisableDistance) discard; //距離大於可視距離拋棄
if (i.worldNormal.w <= 0) discard; //透明度小於等於0拋棄
float3 dir = i.worldPos.xyz - planePos; //注意此時worldPos是原物體頂點位置的反向位置(它是從頂點着色器計算出來的)
half d = dot(dir, planeNormal);
if (d > 0) discard; //超出鏡面拋棄,在鏡面下方d<=0
//獲取噪聲值
float2 offsetXY = float2(tex2D(_NoiseTex, i.uv + fixed2(_Time.x * _NoiseUVOffsetSpeedX, 0)).r,
tex2D(_NoiseTex, i.uv + fixed2(0, _Time.x * _NoiseUVOffsetSpeedY)).r);
offsetXY -= _NoiseOffset; //噪音偏移向量 整體偏移
offsetXY *= fixed2(_NoiseScaleX, _NoiseScaleY); //噪音偏移向量 縮放
//當_NoiseAttenWeight=1時,模擬水面-越深的圖像偏移越大,越淺的圖像偏移越小
//當_NoiseAttenWeight=0時,水面下的圖像都進行噪音偏移(影響程度一樣)
float noiseAtten = i.worldPos.w / _MirrorVisableDistance; //擾動衰減值(0~1) 越近鏡面的噪音偏移值越小(完全貼近的爲0),否則反之
noiseAtten = lerp(1, noiseAtten, _NoiseAttenWeight); //_NoiseAttenWeight爲0時,無擾動 此時噪音偏移值影響最大, 反之,越遠平面的噪音偏移越大
offsetXY *= noiseAtten; //噪音偏移向量 衰減
fixed4 col = tex2D(_MainTex, i.uv + offsetXY);
return fixed4(col.rgb, i.worldNormal.w);
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float3 planeNormal;
float3 planePos;
v2f vert(appdata v)
{
v2f o;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 dir = i.worldPos - planePos;//注意此時的worldPos是正常的物體頂點世界位置
half d = dot(dir, planeNormal);
if (d < 0) discard; //正常物體渲染時,低於鏡面的拋棄
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
四、製作將planeNormal和planePos傳遞給Shader的C#腳本
可掛載到任意一個物體上,啓動遊戲時會將Plane物體的法線和位置傳遞給Mat材質的Shader上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MirrorHelper : MonoBehaviour
{
public GameObject plane;
public Material mat;
void Start()
{
mat.SetVector("planeNormal", plane.transform.up);
mat.SetVector("planePos", plane.transform.position);
}
void Update()
{
mat.SetVector("planeNormal", plane.transform.up);
mat.SetVector("planePos", plane.transform.position);
}
}
若有任何問題,可留言或評論.