目录
四、制作将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);
}
}
若有任何问题,可留言或评论.