Trang chủ / Blog / Shader Programming Cơ Bản: Tạo Hiệu Ứng...
Unity Development 03/05/2026 10 phút đọc

Shader Programming Cơ Bản: Tạo Hiệu Ứng Đẹp Cho Game 2D

Hướng dẫn shader programming cơ bản cho game 2D trong Unity - vertex/fragment shaders, ShaderGraph, dissolve, outline, water effect.

L
LamGame Game Developer & Writer

Shader Programming Cơ Bản: Tạo Hiệu Ứng Đẹp Cho Game 2D Trong Unity

Shader là chương trình chạy trên GPU, quyết định cách mỗi pixel được render lên màn hình. Hiểu và viết shader là kỹ năng quan trọng giúp bạn tạo ra các hiệu ứng visual ấn tượng mà không thể đạt được chỉ bằng sprite và animation thông thường. Bài viết này sẽ hướng dẫn bạn từ khái niệm cơ bản đến việc tạo các hiệu ứng thực tế cho game 2D trong Unity. Khám phá thêm các dự án game 2D với shader đẹp tại kho source game LamGame.

1. Shader Pipeline Cơ Bản

Trong rendering pipeline hiện đại, có hai loại shader chính mà game developer cần quan tâm: Vertex ShaderFragment Shader (còn gọi là Pixel Shader). Vertex Shader xử lý từng vertex của mesh - biến đổi vị trí từ object space sang screen space, truyền dữ liệu như UV coordinates và vertex color sang fragment shader. Fragment Shader xử lý từng pixel (fragment) - quyết định màu sắc cuối cùng của pixel đó dựa trên texture, lighting, và các tính toán custom.

Trong Unity, shader được viết bằng HLSL (High Level Shading Language) bọc trong cấu trúc ShaderLab. ShaderLab là ngôn ngữ markup của Unity để định nghĩa shader properties, render state, và các pass. Một shader Unity cơ bản có cấu trúc như sau:

Shader "Custom/BasicSprite"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Tint Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off
        ZWrite Off

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
                float4 color : COLOR;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 color : COLOR;
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            float4 _Color;

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = input.uv;
                output.color = input.color;
                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                half4 texColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                return texColor * _Color * input.color;
            }
            ENDHLSL
        }
    }
}

2. Hiệu Ứng Dissolve (Tan Biến)

Dissolve effect là một trong những hiệu ứng phổ biến nhất trong game 2D - khi enemy chết, sprite tan biến dần thay vì biến mất đột ngột. Kỹ thuật cốt lõi là sử dụng một noise texture và một threshold value. Với mỗi pixel, so sánh giá trị noise tại vị trí đó với threshold - nếu noise value nhỏ hơn threshold thì discard pixel đó (làm nó trong suốt). Tăng dần threshold từ 0 đến 1 để tạo animation dissolve.

// Fragment shader cho Dissolve Effect
TEXTURE2D(_NoiseTex);
SAMPLER(sampler_NoiseTex);
float _DissolveAmount; // 0 = bình thường, 1 = hoàn toàn biến mất
float4 _EdgeColor;     // Màu viền dissolve (thường là cam/đỏ)
float _EdgeWidth;      // Độ rộng viền

half4 frag(Varyings input) : SV_Target
{
    half4 texColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
    float noise = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, input.uv).r;

    // Discard pixel nếu noise thấp hơn threshold
    clip(noise - _DissolveAmount);

    // Tạo viền phát sáng ở edge của dissolve
    float edge = smoothstep(_DissolveAmount, _DissolveAmount + _EdgeWidth, noise);
    half4 finalColor = lerp(_EdgeColor, texColor * _Color, edge);
    finalColor.a = texColor.a;

    return finalColor;
}

Để sử dụng, tạo Material với shader này, gán noise texture (có thể dùng Perlin noise hoặc Voronoi), và animate property _DissolveAmount từ 0 đến 1 bằng script hoặc Animation. Bạn có thể tạo nhiều variation bằng cách thay đổi noise texture - Voronoi noise tạo hiệu ứng vỡ vụn, Perlin noise tạo hiệu ứng cháy, gradient noise tạo hiệu ứng quét từ một hướng.

3. Hiệu Ứng Outline (Viền)

Outline effect giúp highlight character hoặc object quan trọng trong game. Có nhiều cách tạo outline cho sprite 2D, phương pháp phổ biến nhất là sample texture ở các offset xung quanh pixel hiện tại. Nếu pixel hiện tại trong suốt nhưng có pixel không trong suốt ở gần, đó là edge và cần vẽ outline.

// Fragment shader cho Outline Effect
float4 _OutlineColor;
float _OutlineThickness;
float4 _MainTex_TexelSize; // Unity tự động set: (1/width, 1/height, width, height)

half4 frag(Varyings input) : SV_Target
{
    half4 texColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
    float2 texelSize = _MainTex_TexelSize.xy * _OutlineThickness;

    // Sample 8 hướng xung quanh
    float outline = 0;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2( texelSize.x, 0)).a;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2(-texelSize.x, 0)).a;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2(0,  texelSize.y)).a;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2(0, -texelSize.y)).a;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2( texelSize.x,  texelSize.y)).a;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2(-texelSize.x,  texelSize.y)).a;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2( texelSize.x, -texelSize.y)).a;
    outline += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + float2(-texelSize.x, -texelSize.y)).a;
    outline = saturate(outline);

    // Nếu pixel hiện tại trong suốt nhưng có neighbor không trong suốt -> outline
    half4 finalColor = lerp(_OutlineColor * outline, texColor, texColor.a);
    finalColor.a = max(texColor.a, outline * _OutlineColor.a);

    return finalColor;
}

4. Hiệu Ứng Water (Nước 2D)

Water effect cho game 2D thường kết hợp nhiều kỹ thuật: UV distortion để tạo gợn sóng, color gradient cho depth, và transparency. Ý tưởng chính là offset UV coordinates theo thời gian sử dụng sine wave hoặc noise texture, tạo ra hiệu ứng nước chuyển động liên tục mà không cần animation frame nào.

// Fragment shader cho 2D Water
float _WaveSpeed;
float _WaveAmplitude;
float _WaveFrequency;
float4 _ShallowColor;
float4 _DeepColor;

half4 frag(Varyings input) : SV_Target
{
    float2 uv = input.uv;
    // Tạo distortion bằng sine wave
    float wave = sin(uv.x * _WaveFrequency + _Time.y * _WaveSpeed) * _WaveAmplitude;
    wave += sin(uv.x * _WaveFrequency * 0.5 + _Time.y * _WaveSpeed * 1.3) * _WaveAmplitude * 0.5;
    uv.y += wave;

    half4 texColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
    // Blend giữa shallow và deep color dựa trên UV.y
    half4 waterColor = lerp(_DeepColor, _ShallowColor, input.uv.y);

    return texColor * waterColor;
}

5. Unity ShaderGraph: Visual Shader Editor

Nếu bạn không muốn viết code HLSL, Unity cung cấp ShaderGraph - một visual editor cho phép tạo shader bằng cách kết nối các node. ShaderGraph đặc biệt phù hợp cho artist và designer muốn tạo hiệu ứng mà không cần kiến thức lập trình sâu. Tất cả các hiệu ứng trên (dissolve, outline, water) đều có thể tạo bằng ShaderGraph.

Để tạo ShaderGraph cho 2D, click chuột phải trong Project window, chọn Create → Shader Graph → URP → Sprite Lit Shader Graph (hoặc Sprite Unlit nếu không cần lighting). Kéo thả các node như Sample Texture 2D, Time, Sine, Lerp, và kết nối chúng để tạo hiệu ứng. ShaderGraph tự động generate HLSL code tối ưu từ node graph của bạn.

Một số node quan trọng trong ShaderGraph cho game 2D: Time node cung cấp giá trị thời gian cho animation. UV node cho tọa độ texture. Noise nodes (Simple Noise, Gradient Noise, Voronoi) tạo pattern ngẫu nhiên. Fresnel Effect tạo glow ở edge. Tiling And Offset cho scrolling texture. StepSmoothstep cho hard/soft cutoff.

6. Performance Tips Cho Shader 2D

Shader chạy trên GPU nên thường rất nhanh, nhưng trên mobile vẫn cần chú ý tối ưu. Sử dụng half (16-bit float) thay vì float (32-bit) khi precision không quan trọng - hầu hết color calculations chỉ cần half precision. Giảm số lần sample texture - mỗi texture sample là một operation tốn kém trên mobile GPU. Tránh branching (if/else) trong shader vì GPU xử lý branching kém hiệu quả. Sử dụng step()lerp() thay cho if/else khi có thể. Và cuối cùng, sử dụng shader LOD để fallback sang shader đơn giản hơn trên thiết bị yếu.

Shader programming mở ra một thế giới sáng tạo vô hạn cho game 2D. Bắt đầu với ShaderGraph để hiểu concepts, sau đó chuyển sang viết HLSL khi cần control nhiều hơn. Nếu bạn đam mê technical art và shader development, hãy xem các cơ hội việc làm game tại LamGame để tìm vị trí phù hợp.

Bài viết hữu ích?
Chia sẻ:
FB X TG
L

LamGame

Game Developer & Technical Writer tại LamGame.vn. Chia sẻ kiến thức về game development, Unity, AI tools cho cộng đồng developer Việt Nam.

Đọc thêm bài viết hay

Khám phá kiến thức game dev, tips & tricks từ cộng đồng

← Về trang Blog