找回密码
 立即注册
查看: 283|回复: 2

[笔记] 如何在Unity中实现黑客帝国数字雨效果?

[复制链接]
发表于 2023-1-9 13:51 | 显示全部楼层 |阅读模式
效果展示

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



完整效果演示

作者还有相应的博文介绍他的做法,大家可以去围观:
https://medium.com/@shahriyarshahrabi/shader-studies-matrix-effect-3d2ead3a84c5
本篇文章也会带你先睹为快。
如何做到演示效果

2D的效果

我们先实现一个小目标,在2D下实现如下的效果:



2D效果

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

我们先来看如何实现随机变化的文字。
准备一张1024 x 1024大小的文字贴图,r通道包含了将要用于随机显示的16行 x 16列的文字信息:



文本贴图

我们使用compute shader生成一张白噪声RT来达到随机的目的:
compute shader:
// 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三平面映射的技术。简单来说就是利用世界空间坐标在三个方向上采样纹理,最后通过法线计算权重混合结果。核心代码如下:
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;
   
    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;
}资源

完整的项目地址在这里:
https://github.com/IRCSS/MatrixVFX
大家可以下载玩玩。其中用到的3D模型由于github的lfs的流量原因,可能下载不了,可以在sketchfab上下载obj文件: https://skfb.ly/6UoNJ
注意美术资源采用CC Attribution-NonCommercial协议请勿商用
原作者的灵感来自于shadertoy的这个案例:https://www.shadertoy.com/view/ldccW4
原作者的博文还列出了一些三平面映射技术的文章,可以一读:
https://catlikecoding.com/unity/tutorials/advanced-rendering/triplanar-mapping/
https://medium.com/@bgolus/normal-mapping-for-a-triplanar-shader-10bf39dca05a
https://www.ronja-tutorials.com/2018/05/11/triplanar-mapping.html
如果你喜欢这个系列可以扫描下面的二维码关注我的公众号:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-1-9 14:01 | 显示全部楼层
沙发
发表于 2023-1-9 14:08 | 显示全部楼层
uint triple32 这个里面的算法看不懂。。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-6 02:21 , Processed in 0.093622 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表