实现窗户特效的Unity Shader解析

        本文将详细介绍一种使用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
    }
}
}

技术细节解析:

  1. Shader属性定义: 在这段代码中,我们定义了几个可在Inspector面板上调节的Shader属性。其中包括_MainTex(纹理)、_Size(大小)、_T(时间)、_Distortion(扭曲度)和_Blur(模糊度)等。这些属性可以根据需要进行调整,以获得最佳效果。

  2. 顶点函数(vert): 顶点函数是Shader的顶点转换函数。它将输入的顶点位置和纹理坐标进行转换和处理,并将输出传递给片段函数(frag)使用。

  3. 片段函数(frag): 片段函数是Shader的主要计算部分。在该函数中,我们使用一系列噪声函数和数学函数来模拟窗户特效的各个方面,比如光线折射、光影效果、水滴效果等。最后,我们通过采样_GrabTexture来获取最终的纹理颜色值。

  4. 主循环处理: 在frag函数中,我们通过多个Layer函数的叠加来模拟窗户上的各种效果。每个Layer函数都表示窗户上的一种特效,比如水滴、光线、模糊等。通过调节主循环中的参数可以控制各个特效的强度和效果。

结论:

        通过使用Unity Shader编写的这个窗户特效,可以使游戏场景更加逼真且具有交互性。通过对Shader属性的调整,我们可以轻松地定制窗户的效果,以适应不同的场景需求。

总结:

        本文详细介绍了如何使用Unity Shader实现窗户特效,并对代码进行了逐行解析。通过对Shader属性和函数的说明,读者可以更好地理解窗户特效的实现原理,并在自己的项目中进行应用。

提示:

        在实际写博客时,可以根据需要添加更多的细节和示例代码,以便读者更好地理解并自己尝试实现窗户特效。希望本文对学习Shader编程和Unity开发有所帮助。

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值