UnityShader28:噪声纹理

 

一、噪声纹理

听名字挺蒙的,其实它的作用就是展现出一个事物“混沌”或“杂乱无章”的效果,就像雾气和雪地,它们绝对不会是均匀单一的,不然给人的感觉就像是屏幕变灰了,以及往地上涂了一层白色油漆一样,很笨

Shadertoy上关于Perlin噪声的实现

噪声纹理本质上是一种“固定”的随机,本章第二节使用的噪声纹理如左下:

何为“固定”的随机呢?在 C++ 中我们会使用 Random() 函数来解决一类问题,图形领域也一样,只不过有时只使用简单的随机生成器反而会带来更不自然的效果,右上图就是一张完全随机的噪声纹理,可以看出来它有点太过于杂乱无章了,而生活中的木头纹路、天空中的云彩、山脉地形等等,它们都是趋于分形(fractal)的,即在体现“随机”的基础上却又包含了不同程度的细节以及规律

因此,后面的学者们根据效率、用途、自然程度(即效果好坏)等方面的衡量,提出了许多希望用程序模拟自然噪声的方法,根据不同的算法,可以生成一些看上去“规律”的随机,例如:Perlin 噪声被大量用于云朵、火焰和地形等自然环境的模拟;Simplex 噪声在其基础上进行了改进,提到了效率和效果;而 Worley 噪声被提出用于模拟一些多孔结构,例如纸张、木纹等

其所谓“固定”的随机就是按照特定算法生成的随机序列或噪声纹理中特定的一种,也因此,噪声纹理也被认为是一种程序纹理

 

二、消融效果

过程中使用了 Smoothstep

  • smoothstep 平滑插值公式,计算方法为 \text { smoothstep }(x)=3 t^{2}-2 t^{3},其中 t = \frac{x-min}{max-min}

其它没有什么难点,shader 中有注释

关于 ShadowCast 的部分可以参考 UnityShader17:光照属性与阴影

Shader "Jaihk662/Burn"
{
    Properties
    {
        _BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0              //物体消融程度(0:正常;1:完全消融)
        _LineWidth ("Burn Line Width", Range(0.0, 0.2)) = 0.1           //模拟烧焦效果时边缘的线宽
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _BumpMap ("Normal Map", 2D) = "bump" {}
        _BurnMap ("Burn Map", 2D) = "white" {}                          //消融噪声纹理
        _BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1)       //消融边界颜色
        _BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1)
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}
        LOD 200
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }        //开启前向渲染,设置 multi_compile_fwdbase 编译指令,可以得到正确的平行光光照及其它光源顶点光照
			Cull Off            //发生消融效果时可能会看到模拟内部构造,因此不要开启面剔除
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
 
            fixed _BurnAmount;
			fixed _LineWidth;
			sampler2D _MainTex;
			sampler2D _BumpMap;
			fixed4 _BurnFirstColor;
			fixed4 _BurnSecondColor;
			sampler2D _BurnMap;
			
			float4 _MainTex_ST;
			float4 _BumpMap_ST;
			float4 _BurnMap_ST;
            struct _2vert
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
				float4 tangent: TANGENT; 
				float2 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
				float2 uvMainTex: TEXCOORD0;
				float2 uvBumpMap: TEXCOORD1;
				float2 uvBurnMap: TEXCOORD2;
				float3 lightDir: TEXCOORD3;
				float3 worldPos: TEXCOORD4;
				SHADOW_COORDS(5)
            };
 
            vert2frag vert(_2vert v) 
            {
                vert2frag 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;     //拿到切线空间变换矩阵rotation
                o.lightDir = normalize(mul(rotation, ObjSpaceLightDir(v.vertex)).xyz);
  				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

  				TRANSFER_SHADOW(o);             //为了得到阴影信息,需要计算世界空间下的顶点位置和阴影纹理采样坐标 _ShadowCoord
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target
            {
                fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
                clip(burn.r - _BurnAmount);     //消融效果的关键①:采样结果小于消融阈值_BurnAmount时,剔除该片段

                //单纯计算漫反射光照
                fixed3 normal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));
                fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(normal, i.lightDir));

                //消融效果的关键②:使用smoothstep差值拿到消融混合系数t,当t为1表示在消融→完全消融的边界处,当t为0表示在正常物体颜色→开始消融的边界处
                fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
                //拿到当前烧焦颜色,使用次方计算是为了使得更接近烧焦的颜色
                fixed3 burnColor = pow(lerp(_BurnFirstColor, _BurnSecondColor, t), 5);

                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				fixed3 finalColor = lerp(diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));        //step(a, x):如果 x ≥ a 返回1,否则返回0
                //step(0.0001, _BurnAmount)是为了保证如果_BurnAmount为0时,永远显示物体颜色
				
				return fixed4(finalColor, 1);
            } 
            ENDCG
        }
        PASS
        {
			Tags { "LightMode" = "ShadowCaster" }
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_shadowcaster
			#include "UnityCG.cginc"
			
			fixed _BurnAmount;
			sampler2D _BurnMap;
			float4 _BurnMap_ST;
			
			struct vert2frag
            { 
			    V2F_SHADOW_CASTER;
                float2 uvBurnMap: TEXCOORD1;
			};
			
			vert2frag vert(appdata_base v)
            {
				vert2frag o;
				TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);
				return o;
			}
			fixed4 frag(vert2frag i): SV_Target
            {
                fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
				clip(burn.r - _BurnAmount);
			    SHADOW_CASTER_FRAGMENT(i)
			}
			ENDCG
		}
    }
    FallBack "Diffuse"
}

 

三、水波效果

也可以在这一章着色器上稍做修改,利用噪声纹理生成其对应的法线纹理,实现一个简单的水面效果

过程中用到了菲涅尔反射

  • F_{\text {schlick }}(\mathbf{v}, \mathbf{n})=F_{0}+\left(1-F_{0}\right)(1-\mathbf{v} \cdot \mathbf{n})^{5}
Shader "Jaihk662/Water"
{
    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
    {
        Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
        GrabPass { "_RefractionTex" }
        LOD 200
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }        //开启前向渲染,设置 multi_compile_fwdbase 编译指令,可以得到正确的平行光光照及其它光源顶点光照
			Cull Off            //发生消融效果时可能会看到模拟内部构造,因此不要开启面剔除
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
 
            fixed4 _Color;
			sampler2D _MainTex;
			sampler2D _WaveMap;
			samplerCUBE _Cubemap;
			fixed _WaveXSpeed;
			fixed _WaveYSpeed;
			float _Distortion;	
			sampler2D _RefractionTex;
			float4 _RefractionTex_TexelSize;

            float4 _WaveMap_ST;
            float4 _MainTex_ST;
            struct _2vert
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
				float4 tangent: TANGENT; 
				float2 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
				float4 scrPos: TEXCOORD0;
				float4 uv: TEXCOORD1;
				float4 TtoW1: TEXCOORD2;  
				float4 TtoW2: TEXCOORD3;  
				float4 TtoW3: TEXCOORD4; 
            };
 
            vert2frag vert(_2vert v) 
            {
               vert2frag o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.scrPos = ComputeGrabScreenPos(o.pos);         //获得屏幕图像的采样坐标,考虑过平台差异,输入裁剪空间坐标,输出齐次坐标系下的屏幕坐标值(就是屏幕坐标乘上w,只计算xy,zw不变)
              	o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);
				
				float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float3 wNormal = UnityObjectToWorldNormal(v.normal);
                float3 wTangent = UnityObjectToWorldDir(v.tangent);
                float3 wBinormal = cross(wNormal, wTangent) * v.tangent.w;
                o.TtoW1 = float4(wTangent.x, wBinormal.x, wNormal.x, wPos.x);
                o.TtoW2 = float4(wTangent.y, wBinormal.y, wNormal.y, wPos.y);
                o.TtoW3 = float4(wTangent.z, wBinormal.z, wNormal.z, wPos.z);
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target
            {
                float3 worldPos = float3(i.TtoW1.w, i.TtoW2.w, i.TtoW3.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 normal = normalize(bump1 + bump2);     //得到法向量均值
				
                //计算折射,考虑玻璃材质
                float2 offest = normal.xy * _Distortion * _RefractionTex_TexelSize.xy;
                i.scrPos.xy = i.scrPos.xy + offest;
                fixed3 refractCol = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb;      //根据偏移后的坐标进行采样,得到折射颜色
				
                //正常计算反射
                normal = normalize(half3(dot(i.TtoW1.xyz, normal), dot(i.TtoW2.xyz, normal), dot(i.TtoW3.xyz, normal)));
                fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
				fixed3 reflectionDir = reflect(-viewDir, normal);
				fixed3 reflectionCol = texCUBE(_Cubemap, reflectionDir).rgb * texColor.rgb * _Color.rgb;

                //菲涅尔系数
				fixed fresnel = pow(1 - saturate(dot(viewDir, normal)), 4);
				fixed3 finalColor = reflectionCol * fresnel + refractCol * (1 - fresnel);
				
				return fixed4(finalColor, 1);
            } 
            ENDCG
        }
    }
    FallBack Off
}

 

参考文章:

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader中的热扰动是指在图像或模型表面上模拟出类似于热量或火焰效果的一种特效。这种特效能够给予游戏或动画更加真实、生动的视觉体验。 实现热扰动的主要思路是通过对模型或纹理坐标进行扰动,使其在时间上波动不定,从而营造出热量交互的效果。在Shader中,我们可以使用噪声生成器来获取这种时间上的波动效果。 通过在Shader中加入热扰动效果,我们可以对模型或纹理产生随机的偏移变化,以模拟出火焰或热量的动态效果。这种效果能够让材质看起来像是处于不断变化的状态,增加了画面的动感和真实感。 一种常见的实现方法是使用Perlin Noise,也可以使用其他的噪声生成器。Perlin Noise是一种艺术家和程序员常用的生成随机数的方法,它能够生成具有良好连续性和变化性的随机数序列。在Shader中,我们可以根据时间和空间的变化来计算并应用Perlin Noise,从而生成出热扰动的效果。 除了Perlin Noise外,还可以使用其他形式的噪声生成器,如Simplex Noise等,来实现热扰动效果。这些噪声生成器能够产生更为复杂、自然的扰动效果,使热扰动看起来更加真实。 总之,通过在Unity Shader中加入热扰动效果,我们能够为游戏或动画场景带来更加生动、逼真的视觉效果。这种技术可以让纹理和模型看起来像是处于不断变化的状态,增加了画面的动感和真实感,提升了用户的视觉体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值