Unity-实现黑客帝国数字雨效果

文章目录[x]
  1. 0.1:文字的随机变化
  2. 0.2:下雨的矩形列
  3. 1:应用到场景中

黑客帝国的数字雨特效令印象深刻,网上(GitHub)已经有人在Unity中实现了类似的效果:

一、2D效果的实现

这个2D的效果主要由两部分组成,一是随机变化的文字,二是如同下雨一样的矩形列。

文字的随机变化

我们先来看如何实现随机变化的文字。

准备一张1024 x 1024大小的文字贴图,r通道包含了将要用于随机显示的16行 x 16列的文字信息:

我们使用compute shader生成一张白噪声RT来达到随机的目的:

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel Generate_White_Noise


// The below macro is used to get a random number which varies across different generations. 
#define rnd(seed, constant)  wang_rnd(seed +triple32(_session_rand_seed) * constant) 


uint triple32(uint x)
{
     x ^= x >> 17;
     x *= 0xed5ad4bbU;
     x ^= x >> 11;
     x *= 0xac4c1b51U;
     x ^= x >> 15;
     x *= 0x31848babU;
     x ^= x >> 14;
     return x;
}

float wang_rnd(uint seed)
{
      uint rndint = triple32(seed);
      return ((float)rndint) / float(0xFFFFFFFF); // 0xFFFFFFFF is max unsigned integer in hexa decimal
}

// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> _white_noise;
uint                _session_rand_seed;

#define image_dimension 512

[numthreads(8,8,1)] // 16 in 16 image. 
void Generate_White_Noise(uint3 id : SV_DispatchThreadID)
{

     uint  pixel_unique_id     = id.x + id.y * image_dimension;
     float rand_x              = rnd(pixel_unique_id, 1);
     float rand_y              = rnd(pixel_unique_id, 861);
           _white_noise[id.xy] = float4(rand_x, rand_y, 0.0, 0.0);

}

我们每隔一段时间就改变一次随机数种子来输出变化的白噪声贴图:

随后我们在绘制屏幕时使用白噪声贴图来采样我们的文本贴图:

Shader "Unlit/ScreenSpaceMatrixEffect"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv     : TEXCOORD0;
            };
            
            struct v2f
            {
                    float2 uv     : TEXCOORD0;
                    float4 vertex : SV_POSITION;
            };
            
            uint      _screen_width;
            uint      _screen_height;
            
            sampler2D _white_noise;
            sampler2D _font_texture;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv     = v.uv;
                
                return o;
            }
            
            float text(float2 coord)
            {
                float2 uv    = frac (coord.xy/ 16.);  // 获取当前block的uv
                float2 block = floor(coord.xy/ 16.);  // 获取当前block的ID. 左下角是(0,0),右上角是(15,15)
                uv = uv * 0.7 + .1;  // 做点缩放让字符更大
                    
                float2 rand  = tex2D(_white_noise,    // 从变化的白噪声贴图中获取随机值
                block.xy/float2(512.,512.)).xy;       // 512 是白噪声贴图的宽度. 除以宽度让每个block对应噪声贴图的一个像素
        
                rand  = floor(rand*16.);              // 每个随机值被用于在16列的font_texture中采样
                uv   += rand;   // 让uv做随机偏移
        
                uv   *= 0.0625;   // 将uv从0-16. 映射成0到1,以便从font_texture中采样
                uv.x  = -uv.x;
                return tex2D(_font_texture, uv).r;
            }
            
#define scale 0.6
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = float4(0.,0.,0.,1.);
                col.xyz = text(i.uv * float2(_screen_width, _screen_height)*scale);
                return col;
            }
            ENDCG
        }
    }
}

最后得到文本随机变化的效果:

下雨的矩形列

float3 rain(float2 fragCoord)
{
    fragCoord.x  = floor(fragCoord.x/ 16.);  // 得到列号  
    float offset = sin (fragCoord.x*15.);    // 每一列雨柱需要有不同的起点,对列号取sin值作为偏移
    float speed  = cos (fragCoord.x*3.)*.15 + .35; // 列号取cos值得到不同的速度.加上0.35保证是正数 
    float y      = frac((fragCoord.y / _screen_height)  // 这一行让y映射成0到1
                        + _Time.y * speed + offset);    // 给y加上不同的速度和偏移
    
    return float3(.1, 1., .35) / (y*20.);               // y值越大越暗
}
                  
#define scale 0.6
fixed4 frag (v2f i) : SV_Target
{
    fixed4 col     = float4(0.,0.,0.,1.);
    col.xyz = rain(i.uv * float2(_screen_width, _screen_height)*scale);
    return col;
}

雨的效果比较简单,就是自上而下颜色越来越亮,再水平分列,给每一列不同的偏移和速度就行了。这是只有雨柱的效果:

最后我们把文本变化和雨柱效果相乘,就是2D的数字雨效果了。

fixed4 frag (v2f i) : SV_Target
{
    fixed4 col     = float4(0.,0.,0.,1.);
    col.xyz = text(i.uv * float2(_screen_width, _screen_height)*scale)*rain(i.uv * float2(_screen_width, _screen_height)*scale);
    return col;
}

应用到场景中

我们如何让数字雨沿着场景模型流动呢?这里需要用到triplaner mapping三平面映射的技术。简单来说就是利用世界空间坐标在三个方向上采样纹理,最后通过法线计算权重混合结果。核心代码如下:

float3 MatrixEffect(float2 coord) 
{
   float3 col = float3(0., 0., 0.);
   float3 rain_col = rain(coord* float2(dropLength, dropLength)*scale);

   if (global_colored == 1)
      rain_col = rain_colored(coord * float2(dropLength, dropLength)*scale);
   return text(coord * float2(dropLength, dropLength)*scale) *rain_col;
}

fixed4 frag (v2f i) : SV_Target
{
    fixed4 col      = float4(0.,0.,0.,1.);
    float3 colFront = MatrixEffect(i.worldPos.xy + sin(i.worldPos.zz));
    float3 colSide  = MatrixEffect(i.worldPos.zy + sin(i.worldPos.xx));
    float3 colTop   = MatrixEffect(i.worldPos.xz + sin(i.worldPos.yy));
             
    float3 blendWeight  = pow(normalize(abs(i.normal)), sharpness);
    blendWeight /= (blendWeight.x+ blendWeight.y+ blendWeight.z);
    col.xyz      = colFront * blendWeight.z + colSide  * blendWeight.x + colTop   * blendWeight.y;
             
    //控制距离相机一定范围内才有特效
    //float distance_to_center = distance(i.worldPos.xyz, _Global_Effect_center.xyz);
    //float control_value      = saturate(_Global_Transition_value);
    //if (control_value * 60.0f < distance_to_center) col = col * 0.0f; 
             
    //特效从中间向两边展开,.cs来赋值_Global_Transition_value
    //float2 screenPos = i.screenPos.xy / i.screenPos.w;
    //col      *= split_from_midle(screenPos.x, _Global_Transition_value, 0.0f);
    col = min(1.5,col);
    return col;
}

最终实现文字在建筑上流动的效果(如下图),在实际项目中用的时候一定要小心,因为这个效果看起来像一间被咒文封印住的房子。

点赞

发表回复

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像(已失效)