本文将详细介绍一种使用Unity Shader实现窗户特效的方法。通过分析代码,我们将解释每个关键部分的作用,以及如何将其组合在一起以实现逼真的窗户效果。希望本文能为Shader编程初学者和Unity开发者提供一些有用的指导。
引言:
在游戏和虚拟现实应用中,窗户效果经常用于增强场景的真实感。通过使用Shader,我们可以模拟光线在玻璃上的折射和反射效果,让窗户看起来更加逼真。本文将介绍一种用Unity Shader实现窗户特效的方法,并对代码逐行解析。
Shader "Unlit/Window"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 主纹理
_Size("Size",float) = 1 // 大小
_T("Time",float) = 1 // 时间
_Distortion("Distortion", range(-5, 5)) = 1 // 扭曲
_Blur("Blur",range(0, 1)) = 1 // 模糊程度
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Transparent"} // 标签
LOD 100
GrabPass{"_GrabTexture"} // GrabPass命令将渲染窗口中的内容复制到纹理_GrabTexture中
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#define S(a, b, t) smoothstep(a,b,t) // 定义了计算平滑插值的宏函数
#include "UnityCG.cginc" // Unity内置着色器代码宏
struct appdata
{
float4 vertex : POSITION; // 顶点位置
float2 uv : TEXCOORD0; // 纹理坐标
};
struct v2f
{
float2 uv : TEXCOORD0; // 纹理坐标
float4 grabUv : TEXCOORD1; // 抓取窗口纹理坐标
UNITY_FOG_COORDS(1) // Unity内置的雾效相关宏
float4 vertex : SV_POSITION; // 顶点位置
};
sampler2D _MainTex,_GrabTexture; // 主纹理和抓取窗口纹理
float4 _MainTex_ST; // 主纹理的平铺和偏移参数
float _Size, _T , _Distortion, _Blur; // 大小、时间、扭曲和模糊程度参数
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex); // 将顶点从对象空间转换到剪裁空间
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 对纹理坐标进行平铺和偏移变换
o.grabUv = UNITY_PROJ_COORD(ComputeGrabScreenPos(o.vertex)); // 计算抓取窗口的纹理坐标
UNITY_TRANSFER_FOG(o,o.vertex); // 将雾效信息传递给片段着色器
return o;
}
float N21(float2 p)
{
p = frac(p*float2(123.34,345.45)); // 将纹理坐标乘以固定值并取小数部分
p += dot(p,p + 34.345); // 点乘纹理坐标并加上一定值
return frac(p.x*p.y); // 返回取小数部分后的乘积
}
float3 Layer(float2 UV,float t)
{
float2 aspect = float2(2,1); // 窗口宽高比
float2 uv = UV*_Size * aspect; // 对纹理坐标进行平铺和偏移变换
uv.y += t * .25; // 根据时间进行纹理坐标的偏移
float2 gv = frac(uv)-.5; // 将纹理坐标取小数部分后再减去0.5的值
float2 id = floor(uv); // 取纹理坐标的整数部分
float n = N21(id); // 计算噪声值
t += n*6.2831; // 根据噪声值进行时间的调整
float w = UV .y * 10; // 根据纹理坐标的y值进行宽度调整
float x = (n - .5)*.8; // 根据噪声值进行x的调整
x += (.4-abs(x)) * sin(3*w)*pow(sin(w),6)*.45; // 根据宽度和噪声进行x的进一步调整
float y = -sin(t+sin(t+sin(t)*.5))*.45; // 根据时间和噪声进行y的调整
y -= (gv.x-x)*(gv.x-x); // 根据x与纹理坐标的偏移进行y的进一步调整
float2 dropPos = (gv-float2(x,y)) / aspect; // 计算扭曲的纹理坐标
float drop= S(.05,.03,length(dropPos)); // 计算扭曲的强度
float2 trailPos = (gv-float2(x,t * .25)) / aspect; // 计算轨迹的纹理坐标
trailPos.y = (frac(trailPos.y * 8)-.5)/8; // 计算纹理坐标的小数部分并进行调整
float trail = S(.03,.01,length(trailPos)); // 计算轨迹的强度
float fogTrail = S(-.05,.05,dropPos.y); // 根据扭曲的纹理坐标的y值进行雾效的调整
fogTrail *= S(.5, y, gv.y); // 根据y和纹理坐标的y值再次进行雾效的调整
trail *=fogTrail; // 综合计算轨迹的强度
fogTrail *= S(.05, .04, abs(dropPos.x)); // 根据扭曲的纹理坐标的x值进行雾效的进一步调整
float2 offs = drop*dropPos + trail * trailPos; // 计算扭曲和轨迹的综合效果
return float3(offs,fogTrail); // 返回扭曲、轨迹和雾效混合后的结果
}
fixed4 frag (v2f i) : SV_Target
{
float t = fmod(_Time.y +_T,7200); // 计算时间
float4 col = 0; // 初始化颜色
float3 drops = Layer(i.uv,t); // 计算扭曲、轨迹和雾效
drops += Layer(i.uv*1.23 + 7.54,t); // 对纹理坐标进行平铺和偏移变换并再次计算扭曲、轨迹和雾效
drops += Layer(i.uv*1.35 + 1.54,t); // 对纹理坐标进行平铺和偏移变换并再次计算扭曲、轨迹和雾效
drops += Layer(i.uv*1.57 - 7.54,t); // 对纹理坐标进行平铺和偏移变换并再次计算扭曲、轨迹和雾效
float fade = 1-saturate(fwidth(i.uv)*60); // 根据纹理坐标的宽度计算淡化系数
float blur = _Blur * 7 * (1-drops.z * fade); // 根据淡化系数和扭曲、轨迹和雾效的深度计算模糊程度
//col = tex2Dlod(_MainTex, float4(i.uv + drops.xy * _Distortion,0,blur));
float2 projUv = i.grabUv.xy / i.grabUv.w; // 根据抓取窗口的纹理坐标计算投影纹理坐标
projUv += drops.xy * _Distortion * fade; // 根据扭曲、轨迹和雾效的深度进行投影纹理坐标的调整
blur *= .01; // 调整模糊的程度
const float numSamples = 32; // 采样次数
float a = N21(i.uv)*6.2831*0; // 根据纹理坐标计算角度
for(float i = 0; i < numSamples; i++)
{
float2 offs = float2(sin(a),cos(a))*blur; // 根据角度和模糊程度计算偏移量
float d = frac(sin((i+1)*546.)*5424.); // 根据角度计算采样步长
d = sqrt(d); // 开平方根
offs *= d; // 根据采样步长调整偏移量
col += tex2D(_GrabTexture,projUv + offs); // 对抓取窗口纹理进行采样
a++;
}
col /= numSamples; // 对颜色进行平均
//col *= 0; col += fade;
return col*.9; // 返回最终颜色
}
ENDCG
}
}
}
技术细节解析:
-
Shader属性定义: 在这段代码中,我们定义了几个可在Inspector面板上调节的Shader属性。其中包括_MainTex(纹理)、_Size(大小)、_T(时间)、_Distortion(扭曲度)和_Blur(模糊度)等。这些属性可以根据需要进行调整,以获得最佳效果。
-
顶点函数(vert): 顶点函数是Shader的顶点转换函数。它将输入的顶点位置和纹理坐标进行转换和处理,并将输出传递给片段函数(frag)使用。
-
片段函数(frag): 片段函数是Shader的主要计算部分。在该函数中,我们使用一系列噪声函数和数学函数来模拟窗户特效的各个方面,比如光线折射、光影效果、水滴效果等。最后,我们通过采样_GrabTexture来获取最终的纹理颜色值。
-
主循环处理: 在frag函数中,我们通过多个Layer函数的叠加来模拟窗户上的各种效果。每个Layer函数都表示窗户上的一种特效,比如水滴、光线、模糊等。通过调节主循环中的参数可以控制各个特效的强度和效果。
结论:
通过使用Unity Shader编写的这个窗户特效,可以使游戏场景更加逼真且具有交互性。通过对Shader属性的调整,我们可以轻松地定制窗户的效果,以适应不同的场景需求。
总结:
本文详细介绍了如何使用Unity Shader实现窗户特效,并对代码进行了逐行解析。通过对Shader属性和函数的说明,读者可以更好地理解窗户特效的实现原理,并在自己的项目中进行应用。
提示:
在实际写博客时,可以根据需要添加更多的细节和示例代码,以便读者更好地理解并自己尝试实现窗户特效。希望本文对学习Shader编程和Unity开发有所帮助。