美文网首页首页投稿(暂停使用,暂停投稿)程序员unity3D技术分享
有序的无序:unity shader噪声图以及消融效果的实现

有序的无序:unity shader噪声图以及消融效果的实现

作者: FindCrt | 来源:发表于2017-01-26 16:33 被阅读2035次

    先上一张效果图:


    消融效果

    这篇文章我准备写两部分:

    • 使用噪声图在unity shader里实现物体消融的效果
    • 消融是随机的,但每次过程又是固定的,这一种“有序的无序”模式的理解,这一点是我更想说的,消融效果是帮助解释这个概念。

    有规律的随机

    玩游戏的时候,有时会有火焰特效,有次我盯住一个火焰看,发现火焰是循环播放的,比如下面这张图。类似火焰这种东西,是变化无常的,一般就是随机来做,比如用unity自带的粒子系统来实现,那么火焰的形态是完全随机无序的,是我们无法控制的。但又是怎么让规定的样式进行晃动呢?这里就有一个冲突,就是火焰的形态的无序随机的,否则就会不像;然后火焰这种无序又是按照一种既定的规律重复的进行。

    简单说,随机是13、313、54、114、521、14、41、4521、421这样任意的,足够时间,都是随机的;而有规律的随机是13、313、54、114、521、14、41、4521、421这样随机了一轮后,下一轮跟这个是相同的,这样重复。可以理解为套了两层,第一层重复,是有规律的,第二层随机。

    这就是我的困惑,怎么构造这种“有序的无序” ?

    重复的火焰特效

    消融效果

    先上代码,如果你不了解unity shader,也可以继续看看,我会把里面的逻辑用常人理解的方式理一遍。

    Shader "Unlit/Dissolve"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
            _BurnMap ("Fire Map", 2D) = "white" {}
            _BurnSpeed ("Burn Speed", float) = 1.0
            _Specular ("Specular", range(0, 1)) = 0.5
            _Gloss ("Gloss", range(8, 256)) = 20
            //_BurnAmount ("BurnAmount", range(0,1)) = 0
    
            _BurnFirstColor ("Burn First Color", Color) = (1,0,0,1)
            _BurnSecondColor ("Burn Second Color", Color) = (0,0,0,1)
            _BurnRange ("Burn Range", float) = 0.1
        }
        SubShader
        {
            Tags { "LightMode"="ForwardBase" }
    
    
            Pass
            {
                Cull Off
    
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
                #include "Lighting.cginc"
    
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
                sampler2D _BurnMap;
                fixed _BurnSpeed;
                fixed _Gloss;
                float _Specular;
    
                fixed _BurnAmount;
                fixed4 _BurnFirstColor;
                fixed4 _BurnSecondColor;
                float _BurnRange;
    
                struct a2v{
                    float4 vertex : POSITION;
                    float4 normal : NORMAL;
                    float4 uv : TEXCOORD0;
                };
    
                struct v2f{
                    float4 pos : SV_POSITION;
                    float2 uv : TEXCOORD0;
                    float4 worldPos : TEXCOORD1;
                    float3 worldNormal : TEXCOORD2;
                };
    
                v2f vert (a2v v)
                {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                    o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
    
                    return o;
                }
                
                fixed4 frag (v2f i) : SV_Target
                {
                    //burn map
                    fixed3 burn = tex2D(_BurnMap, i.uv).rgb;
                    _BurnAmount += _Time.y * _BurnSpeed;
                    clip(burn.r - _BurnAmount);
    
    
                    // sample the texture
                    fixed3 albedo = tex2D(_MainTex, i.uv).rgb;
    
                    //diffuse
                    fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
    
                    fixed3 diffuse = _LightColor0.rgb * saturate(dot(lightDir, i.worldNormal)) * albedo;
                
                    //specular
                    fixed3 reflectDir = normalize(reflect(-lightDir, i.worldNormal));
                    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                    fixed3 specular = _LightColor0.rgb * _Specular * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
    
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
    
                    fixed3 finalColor = specular + diffuse + ambient;
    
                    //在消融的边缘位置,添加红色和黑色,模拟烧焦的效果。当前正在烧的边缘就是那些r - _BurnAmount刚好为0的位置。
                    //float burnRate = 1 - saturate((burn.r - _BurnAmount) / _BurnRange);
                    float burnRate = 1 - smoothstep(0, _BurnRange, burn.r - _BurnAmount);
                    fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, burnRate);
                    //burnColor = pow(burnColor, 5);
                    finalColor = lerp(finalColor, burnColor, burnRate);
    
                    return fixed4(finalColor, 1);
                }
                ENDCG
            }
        }
    }
    
    • shader里有个函数clip可以丢弃像素,就是不渲染模型的这个点了。什么是物体的消融?不就是你本来看到的是物体A,然后变成了看到物体A后面的背景了。使用丢弃像素的操作,本来渲染物体A的,变成显示后面的东西了,消融效果就出来了。

    • 消融还有个特性,就是它像水滴在纸上面那样逐渐扩撒开来,而不是无规律的随意消融。那么问题就转移为:按照一个逐渐扩散的模式丢弃模型的像素。

    • 如果要像现实里一样,那么消融扩散的方向、大小、快慢都该是无规律任意的,这是这个问题里的无序。但是如果要用程序随机计算哪些地方消融,那么就是不断出现一个个小洞那样的效果,要模拟扩散消融,个人觉得那就太难了。而现在的做法就是使用纹理图

    • 比如我用相机给现实的火拍一张照,把这个作为模板。物体的表面有个uv坐标系,简单说就是3维物体表面跟2维的平面建立一个对应关系,那么物体表面任意一点,我都可以找到之前拍的火焰纹理图上一个对应点,然后根据这个点的颜色里的红色多少决定是否丢弃。

    火焰纹理

    主要代码就是这一段,其他只是实现光照和边缘烧焦效果。

    fixed3 burn = tex2D(_BurnMap, i.uv).rgb;
    _BurnAmount += _Time.y * _BurnSpeed;
    clip(burn.r - _BurnAmount);
    

    _BurnMap是火焰纹理图,tex2D(_BurnMap, i.uv)这个函数就是根据点的uv左边取到对应纹理图上那一点的颜色信息,clip(burn.r - _BurnAmount);如果这个一点的r(红色)通道值小于_BurnAmount,就剔除这个像素。因为_BurnAmount += _Time.y * _BurnSpeed;``_BurnAmount不断增大,那么就会有越来越多的像素被丢弃,最后整个物体都被丢弃,彻底消融。

    因为纹理图本身就是扩散模式的,所以物体被剔除也是这个模式。

    如果把物体表面的纹理也换成火焰纹理,就可以很明显的看到,从最红的位置开始消融,消融的位置跟纹理的颜色分布是完全一样的。

    火焰纹理消融

    纹理的力量

    回到上面

    怎么构造这种“有序的无序” ?

    使用纹理。纹理上面的内容本身是混乱无序的,但是纹理图已经是一张固定的图,我们参照纹理来决定哪里消融,那纹理图就是有规律的了,是有序的。

    在unity里,shader里是使用纹理,那么在其他地方呢?更抽象的说,就是使用一个模板,把无序记录下来,然后按照这个已经记录下来的模板来操作,这样就可以实现“有序的无序”

    最后一个问题,这样做有什么好处?简便直接效率高。

    资源

    消融效果的材质包放在github这个库里,DissoveShader.unitypackage就是。

    相关文章

      网友评论

        本文标题:有序的无序:unity shader噪声图以及消融效果的实现

        本文链接:https://www.haomeiwen.com/subject/ayixittx.html