Cheatsheet

Unity Graphics từ cơ bản đến nâng cao

Chọn render pipeline, ánh sáng và bóng, shader, post-processing, Renderer Feature và tối ưu đồ hoạ trong Unity 6.

  • Unity
  • URP
  • Rendering

1. Tổng quan: chọn Render Pipeline

Dự án mới trên Unity 6 nên dùng URP, trừ khi cần đồ họa AAA trên PC/console thì chọn HDRP. Built-in đang dần bị thay thế, chỉ giữ cho dự án cũ.

Tiêu chíBuilt-inURPHDRP
Nền tảngMọi nền tảngMọi nền tảng (mobile, VR, web, console, PC)PC, console đời mới
Hiệu năngTrung bìnhNhẹ, tối ưu nhấtNặng
Chất lượng hìnhKháTốt, có thể nâng caoRất cao, physically based
Shader viết taySurface Shader, CGPROGRAMHLSL + include của URPHLSL + include của HDRP
Shader GraphHỗ trợ hạn chếCóCó
Tùy biến pipelineCommand BufferRenderer Feature, Render GraphCustom Pass
Rendering pathForward, DeferredForward, Forward+, Deferred, Deferred+Deferred, Forward

Khái niệm nền tảng

  • Draw call: một lệnh CPU gửi GPU để vẽ một mesh với một material. Càng ít càng tốt.
  • SetPass call: số lần đổi trạng thái shader. Thường đắt hơn draw call.
  • Forward: mỗi object tính ánh sáng trực tiếp; tốt khi ít đèn, hỗ trợ MSAA và vật trong suốt.
  • Forward+: chia màn hình thành tile, cho phép nhiều đèn mà không giới hạn 8 đèn/object.
  • Deferred: vẽ G-Buffer trước, tính ánh sáng sau; tốt khi rất nhiều đèn, không có MSAA.
  • Linear vs Gamma: luôn dùng Linear color space (Project Settings > Player) để ánh sáng chính xác.
  • HDR: bật trên camera/pipeline asset để có Bloom và tonemapping đúng.

Thiết lập URP nhanh

  1. Tạo asset: Create > Rendering > URP Asset (with Universal Renderer).
  2. Gán vào Project Settings > Graphics và Quality > Render Pipeline Asset.
  3. Chuyển material cũ: Window > Rendering > Render Pipeline Converter.

2. Cơ bản: các thành phần đồ họa

Một object hiển thị cần 3 thứ: Mesh (hình dạng), Material (cách tô) và Renderer (component vẽ). Material lại tham chiếu tới một Shader và các Texture.

Thành phầnVai tròGhi nhớ nhanh
MeshĐỉnh, tam giác, UV, normal, tangentBật Read/Write chỉ khi cần sửa mesh lúc runtime (tốn RAM gấp đôi)
MeshFilterGiữ tham chiếu tới MeshGetComponent<MeshFilter>().sharedMesh
MeshRendererVẽ mesh tĩnhCài đặt shadow, light probe, layer
SkinnedMeshRendererVẽ mesh có xươngĐắt hơn; dùng GPU Skinning (Player Settings)
SpriteRendererVẽ sprite 2DDùng Sprite Atlas để batch
MaterialInstance cấu hình của shaderrenderer.material tạo bản sao; sharedMaterial thì không
ShaderChương trình GPUURP mặc định: Lit, Simple Lit, Unlit
TextureẢnh dữ liệuBật mipmap cho 3D, tắt cho UI
CameraGóc nhìn, culling mask, clear flagsURP: Base + Overlay camera stack
LightDirectional, Point, Spot, AreaArea chỉ bake (trừ HDRP)

Kênh texture chuẩn của URP Lit

  • Base Map: màu albedo (sRGB bật).
  • Normal Map: Texture Type = Normal map (sRGB tắt).
  • Metallic Map: R = metallic, A = smoothness.
  • Occlusion: kênh G.
  • Emission: màu tự phát sáng, kết hợp Bloom để có hiệu ứng glow.

Thứ tự render (Render Queue)

QueueGiá trịDùng cho
Background1000Skybox
Geometry2000Vật đục (mặc định)
AlphaTest2450Cutout (lá cây, hàng rào)
Transparent3000Vật trong suốt, vẽ từ xa đến gần
Overlay4000Lens flare, UI trên cùng

3. Ánh sáng và bóng

Quy tắc vàng: bake mọi thứ tĩnh, chỉ để realtime cho đèn và vật thực sự chuyển động.

Light ModeTính khi nàoChi phí runtimeDùng cho
RealtimeMỗi frameCaoĐèn pin, đèn nhấp nháy, chu kỳ ngày đêm
BakedLúc bakeGần như 0Đèn tĩnh chiếu vật tĩnh
MixedDirect realtime + indirect bakedTrung bìnhMặt trời chiếu cả vật tĩnh lẫn động

Mixed Lighting Mode

  • Baked Indirect: bóng realtime, ánh sáng gián tiếp baked. Chất lượng tốt, tốn hơn.
  • Shadowmask: bóng vật tĩnh lưu vào texture, vật động vẫn có bóng realtime. Cân bằng nhất.
  • Subtractive: rẻ nhất, cho mobile thấp cấp; chỉ một directional light.

Global Illumination

  • Đánh dấu vật tĩnh: Static > Contribute GI.
  • Mở Window > Rendering > Lighting để bake; dùng Progressive GPU lightmapper cho nhanh.
  • Lightmap UV: bật Generate Lightmap UVs trong Model Import Settings.
  • Light Probes: cho vật động nhận ánh sáng baked. Unity 6 có Adaptive Probe Volumes (APV), tự rải probe, thay cho Light Probe Group thủ công.
  • Reflection Probes: chụp cubemap môi trường cho phản chiếu. Mode Baked cho tĩnh, Realtime chỉ khi thật cần (rất đắt).

Cài đặt bóng (URP Asset > Shadows)

Thông sốTác dụngGợi ý
Max DistanceKhoảng cách tối đa có bóng50–80 m cho mobile, 150 m cho PC
Cascade CountSố tầng shadow map1–2 mobile, 4 PC
Shadow ResolutionĐộ nét bóng1024–2048 mobile, 4096 PC
Depth BiasChống shadow acne (sọc bóng)Tăng nếu thấy sọc
Normal BiasChống peter-panning (bóng tách khỏi vật)Giảm nếu bóng hở chân
Soft ShadowsBóng mềmTắt trên mobile yếu

Lỗi ánh sáng thường gặp

  • Lightmap có vệt, đốm: tăng Lightmap Resolution hoặc sửa UV chồng lấn.
  • Vật động bị tối: thiếu Light Probe quanh khu vực đó.
  • Bóng nhòe ở xa: giảm Max Distance hoặc chỉnh tỷ lệ cascade split.

4. Shader

Dùng Shader Graph cho phần lớn hiệu ứng; chỉ viết HLSL tay khi cần kiểm soát hiệu năng hoặc tính năng Shader Graph không có.

Khung shader Unlit cho URP (tương thích SRP Batcher)

Shader "Custom/UnlitURP"
{
    Properties
    {
        _BaseMap ("Base Map", 2D) = "white" {}
        _BaseColor ("Base Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }
        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }

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

            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                half4 _BaseColor;
            CBUFFER_END

            struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; };
            struct Varyings  { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; };

            Varyings vert (Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

            half4 frag (Varyings IN) : SV_Target
            {
                return SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor;
            }
            ENDHLSL
        }
    }
}

Hàm HLSL thường dùng trong URP

Hàm / biếnTác dụng
TransformObjectToHClip(p)Object space sang clip space
TransformObjectToWorld(p)Object space sang world space
TransformObjectToWorldNormal(n)Normal sang world space
GetWorldSpaceViewDir(p)Hướng từ điểm tới camera
GetMainLight()Lấy directional light chính (cần include Lighting.hlsl)
_Time.yThời gian (giây) để làm animation
SampleSceneDepth(uv)Đọc depth texture (bật Depth Texture trong URP Asset)
SampleSceneColor(uv)Đọc opaque texture cho hiệu ứng khúc xạ
lerp, saturate, smoothstep, stepNội suy, kẹp 0–1, chuyển mềm, ngưỡng cứng

Render state trong ShaderLab

LệnhVí dụDùng cho
BlendBlend SrcAlpha OneMinusSrcAlphaTrong suốt thông thường
BlendBlend One OneAdditive (lửa, ánh sáng)
ZWriteZWrite OffVật trong suốt
ZTestZTest AlwaysVẽ xuyên tường (outline, x-ray)
CullCull Off / Front / BackHai mặt, outline lật mặt
StencilStencil { Ref 1 Comp Equal }Portal, mask, cổng không gian

Công thức hiệu ứng phổ biến

  • Fresnel / rim light: pow(1 - saturate(dot(N, V)), power).
  • Dissolve: lấy noise, clip(noise - _Cutoff); viền phát sáng bằng step(noise, _Cutoff + edge).
  • Lambert diffuse: saturate(dot(N, L)) * lightColor.
  • Blinn-Phong specular: pow(saturate(dot(N, normalize(L + V))), shininess).
  • Toon shading: step hoặc ramp texture trên giá trị N·L.
  • UV scroll: uv + _Time.y * _Speed.

Mẹo Shader Graph

  • Dùng Sub Graph để tái sử dụng logic.
  • Custom Function node để chèn HLSL tay.
  • Đặt property là Exposed mới chỉnh được từ material và script.
  • Keyword (Boolean/Enum) tạo biến thể; quá nhiều keyword làm tăng thời gian build.

5. Post-processing

Trong URP/HDRP, post-processing chạy qua hệ thống Volume: tạo GameObject > Volume > Global Volume, thêm Override, và bật Post Processing trên camera.

  • Global Volume: áp dụng toàn cảnh.
  • Local Volume: cần Collider; hiệu ứng thay đổi khi camera đi vào vùng (Blend Distance để chuyển mềm).
  • Priority: số lớn hơn thắng khi nhiều Volume chồng nhau.
Hiệu ứngTác dụngChi phíMẹo
TonemappingNén HDR về màn hìnhThấpDùng ACES cho điện ảnh, Neutral để giữ màu gốc
Color AdjustmentsExposure, contrast, saturationThấpChỉnh trước Color Grading
BloomVùng sáng tỏa hào quangTrung bìnhThreshold khoảng 1; cần HDR và màu emission lớn hơn 1
VignetteTối góc màn hìnhThấpIntensity 0.2–0.35 là đủ
SSAOBóng tối ở góc, kheCaoLà Renderer Feature trong URP; tránh trên mobile yếu
Depth of FieldLàm mờ theo khoảng cáchCaoGaussian rẻ hơn Bokeh
Motion BlurMờ khi chuyển độngTrung bìnhThường tắt cho game cạnh tranh
Chromatic AberrationLệch màu ở viềnThấpDùng nhẹ tay
Film GrainHạt nhiễu phimThấpGiúp che banding
Lift Gamma Gain / Color CurvesGrading chi tiếtThấpDùng LUT (Color Lookup) để đồng bộ với Photoshop/DaVinci

Chỉnh Volume bằng script

using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

if (volume.profile.TryGet(out Vignette vignette))
{
    vignette.intensity.Override(0.5f); // ví dụ khi bị trúng đòn
}

Lưu ý: volume.profile tạo bản sao lúc runtime; volume.sharedProfile sửa luôn asset gốc.

Anti-aliasing

Kỹ thuậtĐặt ở đâuĐặc điểm
MSAAURP AssetSắc nét, chỉ với Forward, tốn băng thông
FXAACameraRẻ nhất, hơi mờ
SMAACameraCân bằng giữa chất lượng và chi phí
TAACameraĐẹp nhất cho cảnh tĩnh, có thể bóng ma khi chuyển động
STP / DLSS / FSRCamera, URP AssetUpscaling: render độ phân giải thấp rồi phóng to

6. Nâng cao

Từ Unity 6, URP dùng Render Graph làm mặc định: pass tùy biến viết trong RecordRenderGraph thay cho Execute kiểu cũ.

Renderer Feature với Render Graph (Unity 6, URP)

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;

public class TintFeature : ScriptableRendererFeature
{
    class TintPass : ScriptableRenderPass
    {
        readonly Material mat;
        public TintPass(Material m)
        {
            mat = m;
            renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        }

        public override void RecordRenderGraph(RenderGraph rg, ContextContainer frameData)
        {
            var res = frameData.Get<UniversalResourceData>();
            if (res.isActiveTargetBackBuffer) return;

            TextureHandle src = res.activeColorTexture;
            var desc = rg.GetTextureDesc(src);
            desc.name = "_TintTemp";
            desc.clearBuffer = false;
            TextureHandle dst = rg.CreateTexture(desc);

            rg.AddBlitPass(new RenderGraphUtils.BlitMaterialParameters(src, dst, mat, 0), "Tint");
            res.cameraColor = dst; // các pass sau đọc kết quả này
        }
    }

    public Material material;
    TintPass pass;

    public override void Create() => pass = new TintPass(material);

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData data)
    {
        if (material != null) renderer.EnqueuePass(pass);
    }
}

Thêm vào: URP Renderer asset > Add Renderer Feature. Dùng Window > Analysis > Render Graph Viewer để xem các pass và tài nguyên.

RenderPassEvent hay dùng

EventDùng cho
BeforeRenderingOpaquesVẽ trước vật đục (mask, stencil)
AfterRenderingOpaquesOutline, decal tùy biến, đọc depth
AfterRenderingSkyboxHiệu ứng nước, khúc xạ
BeforeRenderingPostProcessingHiệu ứng toàn màn hình trong HDR
AfterRenderingPostProcessingHiệu ứng sau tonemapping (pixelate, UI blur)

Compute Shader

// Wave.compute
#pragma kernel CSMain
RWStructuredBuffer<float3> _Positions;
float _Time;

[numthreads(64, 1, 1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    float3 p = _Positions[id.x];
    p.y = sin(p.x + _Time);
    _Positions[id.x] = p;
}
int k = cs.FindKernel("CSMain");
var buffer = new ComputeBuffer(count, sizeof(float) * 3);
cs.SetBuffer(k, "_Positions", buffer);
cs.SetFloat("_Time", Time.time);
cs.Dispatch(k, Mathf.CeilToInt(count / 64f), 1, 1);
// Nhớ buffer.Release() trong OnDestroy

Tránh buffer.GetData() mỗi frame vì CPU phải chờ GPU; dùng AsyncGPUReadback nếu cần đọc về.

Vẽ số lượng lớn

Kỹ thuậtKhi nào dùngGhi chú
SRP BatcherMặc định trong URP/HDRPShader phải để property trong CBUFFER UnityPerMaterial; giảm chi phí CPU khi đổi material
GPU InstancingNhiều bản cùng mesh, cùng materialTích Enable GPU Instancing trên material; không chạy song song với SRP Batcher cho cùng object
Graphics.RenderMeshInstancedCỏ, đá, đạn: hàng nghìn bản không cần GameObjectTối đa 1023 instance mỗi lần gọi với một số API cũ
Graphics.RenderMeshIndirectSố lượng do GPU quyết định (compute culling)Kết hợp compute shader và GraphicsBuffer args
GPU Resident Drawer (Unity 6)Cảnh nhiều MeshRenderer tĩnhBật trong URP Asset; cần Forward+ và BatchRendererGroup variants = Keep All
BatchRendererGroupHệ thống render tùy biến hiệu năng caoAPI cấp thấp, dùng cùng DOTS/Entities Graphics

CommandBuffer (Built-in và code cũ)

var cmd = new CommandBuffer { name = "Outline" };
cmd.DrawRenderer(targetRenderer, outlineMat);
camera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, cmd);

Trong URP hiện đại, logic tương tự nằm trong Renderer Feature thay vì gắn vào camera.

MaterialPropertyBlock

Đổi màu từng object mà không tạo material mới: block.SetColor("_BaseColor", c); renderer.SetPropertyBlock(block);. Lưu ý: MaterialPropertyBlock làm object rớt khỏi SRP Batcher, nên với URP ưu tiên giữ ít biến thể material hoặc dùng instancing.

7. Tối ưu hiệu năng

Đo trước, tối ưu sau: xác định game đang nghẽn CPU hay GPU rồi mới chọn kỹ thuật.

Bước chẩn đoán

  1. Mở Profiler (Ctrl+7), xem CPU Usage và GPU Usage, đo trên thiết bị thật chứ không phải Editor.
  2. Nếu main thread hoặc render thread chờ lâu: nghẽn CPU, giảm draw call và SetPass.
  3. Nếu Gfx.WaitForPresent hoặc GPU time cao: nghẽn GPU, giảm overdraw, độ phân giải, shader nặng.
  4. Thử giảm Render Scale: FPS tăng rõ nghĩa là nghẽn fill rate (GPU).
Công cụXem được gì
ProfilerThời gian CPU/GPU, bộ nhớ, rendering stats
Frame DebuggerTừng draw call, lý do batch bị tách
Render Graph ViewerPass, texture tạo ra và đọc ghi
Rendering DebuggerOverdraw, mipmap, lighting, material debug
Memory Profiler (package)Texture, mesh chiếm RAM
RenderDocPhân tích frame mức GPU (Editor hoặc PC)
Xcode GPU Capture / Android GPU Inspector / Snapdragon ProfilerPhân tích GPU trên mobile

Giảm tải CPU

  • Đánh dấu Static Batching cho vật tĩnh; dùng ít material, gộp texture vào atlas.
  • Dùng LOD Group: thường 3 mức, bỏ mức cuối bằng Culled.
  • Occlusion Culling: Window > Rendering > Occlusion Culling, bake cho cảnh trong nhà hoặc thành phố nhiều vật che.
  • Giảm Far Clip Plane của camera; dùng Layer Cull Distances cho vật nhỏ.
  • Hạn chế đèn realtime có bóng; mỗi đèn shadow vẽ lại cảnh thêm lần nữa.

Giảm tải GPU

  • Hạn chế overdraw: ít lớp trong suốt chồng nhau, particle nhỏ hơn, UI gọn.
  • Hạ Render Scale (0.7–0.85 trên mobile) kết hợp upscaler.
  • Shader: dùng half trên mobile, tránh rẽ nhánh động, ít sample texture.
  • Tắt Depth/Opaque Texture trong URP Asset nếu không hiệu ứng nào dùng.
  • Dùng Simple Lit hoặc Baked Lit thay Lit cho vật phụ.

Nén texture theo nền tảng

Nền tảngĐịnh dạng khuyên dùngGhi chú
AndroidASTC (6x6 cho màu, 4x4 cho normal/UI)ETC2 làm dự phòng cho máy cũ
iOSASTCMọi thiết bị A8 trở lên
PC / ConsoleBC7 (màu chất lượng cao), BC5 (normal), BC1/DXT1 (không alpha)BC6H cho HDR
WebGLASTC hoặc DXT tùy trình duyệtCân nhắc build hai bộ
  • Đặt Max Size vừa đủ; texture 4K cho vật nhỏ là lãng phí.
  • Bật Crunch Compression để giảm dung lượng file (không giảm RAM GPU).
  • Mipmap Streaming giúp giảm bộ nhớ texture cho cảnh lớn.

Ngân sách tham khảo cho mobile tầm trung

Chỉ sốMục tiêu
SetPass callsDưới 100
BatchesDưới 200–300
Tam giác trên màn hình100k–300k
Đèn realtime có bóng1 (directional)

Đây là mức tham khảo chung, cần chỉnh theo thiết bị mục tiêu và đo thực tế.

8. Bảng tra nhanh

API C# thường dùng

Mục đíchCode
Đổi màu (tạo bản sao material)renderer.material.SetColor("_BaseColor", Color.red);
Đổi màu chung cho mọi objectrenderer.sharedMaterial.SetColor("_BaseColor", c);
Lấy ID property (nhanh hơn chuỗi)static readonly int BaseColor = Shader.PropertyToID("_BaseColor");
Biến global cho mọi shaderShader.SetGlobalFloat("_WindStrength", 0.5f);
Bật/tắt keywordmaterial.EnableKeyword("_EMISSION");
Bật emissionmaterial.SetColor("_EmissionColor", c * intensity);
Đổi Render Pipeline lúc chạyGraphicsSettings.defaultRenderPipeline = urpAsset;
Đổi mức chất lượngQualitySettings.SetQualityLevel(2, true);
Đổi render scale (URP)((UniversalRenderPipelineAsset)GraphicsSettings.currentRenderPipeline).renderScale = 0.8f;
Chụp màn hìnhScreenCapture.CaptureScreenshot("shot.png");
Render camera ra texturecamera.targetTexture = renderTexture;
Cấu hình camera URPcamera.GetUniversalAdditionalCameraData().renderPostProcessing = true;
Giới hạn FPSApplication.targetFrameRate = 60;

Phím tắt và menu hữu ích

Thao tácPhím / menu
ProfilerCtrl+7 (Cmd+7 trên macOS)
Frame DebuggerWindow > Analysis > Frame Debugger
Lighting windowWindow > Rendering > Lighting
Rendering DebuggerWindow > Analysis > Rendering Debugger
Chế độ hiển thị Scene (Wireframe, Overdraw…)Menu Draw Mode góc trên trái Scene view
Thống kê nhanhNút Stats trên Game view
Căn camera Scene theo vậtChọn vật rồi nhấn F
Đưa camera game về góc nhìn SceneCtrl+Shift+F (Align With View)

Lỗi thường gặp và cách sửa

Triệu chứngNguyên nhân thường gặpCách sửa
Material màu hồng tímShader không hợp với pipelineDùng Render Pipeline Converter hoặc đổi sang shader URP
Bloom không hoạt độngChưa bật HDR hoặc Post Processing trên cameraBật HDR trong URP Asset và Post Processing trên camera
Post-processing không hiệnVolume sai layer hoặc chưa có VolumeKiểm tra Volume Mask trên camera và layer của Volume
Vật trong suốt hiển thị sai thứ tựSắp xếp theo tâm objectChỉnh Sorting Priority, tách mesh, hoặc dùng alpha clip
Bóng sọc (shadow acne)Bias quá thấpTăng Depth Bias
Bóng tách khỏi chân vậtBias quá caoGiảm Normal Bias
Màu nhợt hoặc quá đậmSai color space hoặc sRGB của textureDùng Linear; tắt sRGB cho mask, normal map
Normal map trông lồi lõm saiTexture chưa đặt kiểu Normal mapĐổi Texture Type sang Normal map
Batch không gộpKhác material, MaterialPropertyBlock, lightmap khác nhauXem lý do trong Frame Debugger
Shader biên dịch lâu, build nặngQuá nhiều shader variantDùng shader_feature thay multi_compile, bật strip trong URP Asset
Lightmap lấm tấmThiếu sample hoặc UV chồngTăng sample, bật Filtering, sửa lightmap UV

multi_compile và shader_feature

  • multi_compile: luôn build mọi biến thể; dùng cho keyword bật tắt lúc runtime.
  • shader_feature: chỉ build biến thể mà material đang dùng; dùng cho tùy chọn cố định trên material.
  • Thêm hậu tố _local (ví dụ shader_feature_local) để keyword không chiếm giới hạn keyword toàn cục.

Nội dung dựa trên Unity 6 và URP 17; tên menu có thể khác ở phiên bản cũ hơn.