unity shader:使用噪声

噪声纹理的应用:噪声往往会应用到规则的事物里,从而产生一种杂乱无章的特殊效果。

噪声纹理的创建:噪声纹理本质上是一个程序纹理,是由一些程序算法生成的纹理,相关纹理参数都在程序里面进行控制。常用的噪声纹理是用于生成自然的perlin_noise 以及用于多孔的worley_noise

消融效果:表现效果就是从不同的区域开始,并向随机方向扩张,最后整个物体都将消失不见。实现原理就是:使用噪声纹理进行取样,将取样的结果和消融参数进行透明度测试处理,不裁剪掉的像素就和另外两个颜色来进行插值处理从而达到烧焦的效果。并且将光照阴影和衰减也按照同样的透明度测试进行处理,避免错误的投射阴影。核心实现代码如下:

Properties {
        // 消融控制参数,小于这个参数的像素值会被裁剪掉
        _BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0
        // 烧焦效果的延伸宽度
        _LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1
        // 主纹理,用来获取漫反射
        _MainTex ("Base (RGB)", 2D) = "white" {}
        // 法线纹理,用来计算阴影
        _BumpMap ("Normal Map", 2D) = "bump" {}
        // 烧焦效果的第一个颜色
        _BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1)
        // 烧焦效果的第二个颜色
        _BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1)
        // 噪声纹理,用来采样进行透明度测试,达到消融效果
        _BurnMap("Burn Map", 2D) = "white"{}
    }
SubShader {
    Pass {
        v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap);
                o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
                // 法线变换到切线空间,获取正确的阴影信息
                TANGENT_SPACE_ROTATION; 
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                TRANSFER_SHADOW(o);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
                // 小于_BurnAmount的像素进行裁剪达到消融效果
                clip(burn.r - _BurnAmount);

                float3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));

                fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
                // 在0到_LineWidth之间获取烧焦混合系数
                fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
                // 对烧焦两个颜色按照混合系数进行插值
                fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t);
                burnColor = pow(burnColor, 5);

                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                // 将源颜色和烧焦颜色按照混合系数进行插值处理,得到最终烧焦效果,_BurnAmount为0时,step返回0,此时显示源颜色
                fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));

                return fixed4(finalColor, 1);
            }
    }

    Pass {  
            v2f vert(appdata_base v) {
                v2f o;
                // 获取阴影,将阴影信息存放在阴影映射纹理中
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

                o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
                // 对透明度测试裁剪的颜色不进行投射阴影操作
                clip(burn.r - _BurnAmount);
                // 投射阴影,从阴影映射纹理中进行采样并返回像素
                SHADOW_CASTER_FRAGMENT(i)
            }
        }
}

水波效果:实现原理如下所示:
1.模拟折射:使用噪声纹理来作为存储法线信息的高度图,按照时间的推移来获取指定偏移量作为采样坐标,从噪声纹理中进行采样来获取变化的法线方向,然后用这个法线方向从GrabPass抓取到的渲染纹理中进行采样,从而获取折射颜色。
2.模拟反射:使用立方体纹理模拟周围环境,并使用反射方向从该纹理中采样,从而获取反射颜色。
3.使用菲涅尔反射公式来混合反射和折射光照颜色,得到最终的水波颜色。
核心代码如下所示:

Properties {
        // 主纹理颜色系数,用来混合反射颜色
        _Color ("Main Color", Color) = (0, 0.15, 0.115, 1)
        // 主纹理
        _MainTex ("Base (RGB)", 2D) = "white" {}
        // 水波噪声纹理,存放法线信息,用来模拟折射
        _WaveMap ("Wave Map", 2D) = "bump" {}
        // 立方体纹理,用来模拟反射
        _Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
        // 噪声纹理偏移系数
        _WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01
        _WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01
        // 折射图像的扭曲程度
        _Distortion ("Distortion", Range(0, 100)) = 10
}

SubShader {
    Pass {
        v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                // 获取抓取屏幕的渲染纹理采样坐标
                o.scrPos = ComputeGrabScreenPos(o.pos);
                // 获取主纹理采样坐标
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                // 获取存放法线的噪声纹理采样坐标
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);

                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                // 获取世界空间下的副切线方向  
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
                // 将世界空间下的切线,副切线,法线,坐标存放起来
                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                // 获取世界空间下的视角方向
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                // 获取按照时间变化的偏移量
                float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);

                // 对噪声纹理按照坐标偏移量进行采样,获取法线信息,此处进行了两层交叉采样,用来模拟波动效果
                fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
                fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
                fixed3 bump = normalize(bump1 + bump2);

                // 获取法线按照折射纹理文素大小的偏移值
                float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
                i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
                // 对渲染纹理按照法线偏移后的坐标按照透视除法后进行采样,获取折射颜色
                fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;

                // 获取世界空间中法线的方向
                bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
                fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
                // 获取世界空间中的反射方向
                fixed3 reflDir = reflect(-viewDir, bump);
                // 对立方体纹理按照反射方向进行采样,获取反射颜色
                fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb;

                // 获取菲涅尔系数来混合反射和折射颜色
                fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4);
                fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);

                return fixed4(finalColor, 1);
            }
    }
}

全局雾效:
表现效果:不同的高度的雾具有不同的浓度,而且具有缥缈的效果。
实现原理:
1.利用屏幕后处理技术来抓取屏幕作为渲染纹理,并且采样获取源屏幕颜色。
2.使用深度纹理来重塑顶点世界空间坐标。
3.随着时间变化按照偏移速度获取噪声纹理采样坐标,并对该纹理进行采样来获取噪声系数。
4.将噪声系数,雾效浓度按照高度进行处理,从而获取不同高度对应的雾效浓度。
5.将源颜色和雾效颜色按照雾效浓度进行线性插值,从而实现缥缈的雾效。
核心代码如下所示:

Properties {
        // 主纹理,用来获取源颜色
        _MainTex ("Base (RGB)", 2D) = "white" {}
        // 雾效浓度系数,用来和噪声纹理采样数值进行相乘,获取不同浓度
        _FogDensity ("Fog Density", Float) = 1.0
        // 雾效的颜色
        _FogColor ("Fog Color", Color) = (1, 1, 1, 1)
        // 雾效开始点
        _FogStart ("Fog Start", Float) = 0.0
        // 雾效结束点
        _FogEnd ("Fog End", Float) = 1.0
        // 噪声纹理,采样数值和用来和雾效浓度进行相乘,获取不同浓度
        _NoiseTex ("Noise Texture", 2D) = "white" {}
        // 噪声纹理变化速度,和时间相乘获取不同的噪声纹理采样坐标
        _FogXSpeed ("Fog Horizontal Speed", Float) = 0.1
        _FogYSpeed ("Fog Vertical Speed", Float) = 0.1
        // 噪声纹理的控制程度,为0时不使用噪声
        _NoiseAmount ("Noise Amount", Float) = 1
    }
SubShader {
    pass {
        v2f vert(appdata_img v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);

            o.uv = v.texcoord;
            o.uv_depth = v.texcoord;

            #if UNITY_UV_STARTS_AT_TOP
            if (_MainTex_TexelSize.y < 0)
                o.uv_depth.y = 1 - o.uv_depth.y;
            #endif

            int index = 0;
            if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) {
                index = 0;
            } else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) {
                index = 1;
            } else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) {
                index = 2;
            } else {
                index = 3;
            }
            #if UNITY_UV_STARTS_AT_TOP
            if (_MainTex_TexelSize.y < 0)
                index = 3 - index;
            #endif

            o.interpolatedRay = _FrustumCornersRay[index];

            return o;
        }

        fixed4 frag(v2f i) : SV_Target { 
            // 获取深度纹理中当前像素深度
            float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
            // 利用深度纹理从新构造世界坐标
            float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;
            // 获取噪声纹理采样坐标
            float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
            // 从噪声纹理中采样,获取噪声系数
            float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount;
            // 将雾效浓度和噪声系数相乘处理,得到不同的雾效浓度 
            float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); 
            fogDensity = saturate(fogDensity * _FogDensity * (1 + noise));
            // 将源颜色和雾效颜色按照雾效浓度进行线性插值,从而得到缥缈雾效
            fixed4 finalColor = tex2D(_MainTex, i.uv);
            finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);

            return finalColor;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/zjz520yy/article/details/78891746