Home Deferred Gbuffer 가져오기
Post
Cancel

Deferred Gbuffer 가져오기

머리

학교 프로젝트가 Unity URP Deferred 사용하는데, 메쉬 블렌딩 효과를 낼수있는 쉐이더 제작을 요청했습니다.

그 중 몇가지 문제가 있었고, 결국 G버퍼를 가져와서 SceneColor(_CameraOpaqueTexture) 대신 사용하는 방법에 대해 알아봤습니다.

몸통

G버퍼 데이터

UnityGBuffer.hlsl 파일을 참조해 본다면, GBuffer0,1,2,3에 각각 어떤 데이터가 들어가는지 알 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
FragmentOutput SurfaceDataToGbuffer(SurfaceData surfaceData, InputData inputData, half3 globalIllumination, int lightingMode)
{
    half3 packedNormalWS = PackNormal(inputData.normalWS);

    uint materialFlags = 0;

    // SimpleLit does not use _SPECULARHIGHLIGHTS_OFF to disable specular highlights.

    #ifdef _RECEIVE_SHADOWS_OFF
    materialFlags |= kMaterialFlagReceiveShadowsOff;
    #endif

    #if defined(LIGHTMAP_ON) && defined(_MIXED_LIGHTING_SUBTRACTIVE)
    materialFlags |= kMaterialFlagSubtractiveMixedLighting;
    #endif

    FragmentOutput output;
    output.GBuffer0 = half4(surfaceData.albedo.rgb, PackMaterialFlags(materialFlags));   // albedo          albedo          albedo          materialFlags   (sRGB rendertarget)
    output.GBuffer1 = half4(surfaceData.specular.rgb, surfaceData.occlusion);            // specular        specular        specular        occlusion
    output.GBuffer2 = half4(packedNormalWS, surfaceData.smoothness);                     // encoded-normal  encoded-normal  encoded-normal  smoothness
    output.GBuffer3 = half4(globalIllumination, 1);                                      // GI              GI              GI              unused          (lighting buffer)
    #if _RENDER_PASS_ENABLED
    output.GBuffer4 = inputData.positionCS.z;
    #endif
    #if OUTPUT_SHADOWMASK
    output.GBUFFER_SHADOWMASK = inputData.shadowMask; // will have unity_ProbesOcclusion value if subtractive lighting is used (baked)
    #endif
    #ifdef _WRITE_RENDERING_LAYERS
    uint renderingLayers = GetMeshRenderingLayer();
    output.GBUFFER_LIGHT_LAYERS = float4(EncodeMeshRenderingLayer(renderingLayers), 0.0, 0.0, 0.0);
    #endif

    return output;
}

...

// This will encode SurfaceData into GBuffer
FragmentOutput BRDFDataToGbuffer(BRDFData brdfData, InputData inputData, half smoothness, half3 globalIllumination, half occlusion = 1.0)
{
    half3 packedNormalWS = PackNormal(inputData.normalWS);

    uint materialFlags = 0;

    #ifdef _RECEIVE_SHADOWS_OFF
    materialFlags |= kMaterialFlagReceiveShadowsOff;
    #endif

    half3 packedSpecular;

    #ifdef _SPECULAR_SETUP
    materialFlags |= kMaterialFlagSpecularSetup;
    packedSpecular = brdfData.specular.rgb;
    #else
    packedSpecular.r = brdfData.reflectivity;
    packedSpecular.gb = 0.0;
    #endif

    #ifdef _SPECULARHIGHLIGHTS_OFF
    // During the next deferred shading pass, we don't use a shader variant to disable specular calculations.
    // Instead, we can either silence specular contribution when writing the gbuffer, and/or reserve a bit in the gbuffer
    // and use this during shading to skip computations via dynamic branching. Fastest option depends on platforms.
    materialFlags |= kMaterialFlagSpecularHighlightsOff;
    packedSpecular = 0.0.xxx;
    #endif

    #if defined(LIGHTMAP_ON) && defined(_MIXED_LIGHTING_SUBTRACTIVE)
    materialFlags |= kMaterialFlagSubtractiveMixedLighting;
    #endif

    FragmentOutput output;
    output.GBuffer0 = half4(brdfData.albedo.rgb, PackMaterialFlags(materialFlags));  // diffuse           diffuse         diffuse         materialFlags   (sRGB rendertarget)
    output.GBuffer1 = half4(packedSpecular, occlusion);                              // metallic/specular specular        specular        occlusion
    output.GBuffer2 = half4(packedNormalWS, smoothness);                             // encoded-normal    encoded-normal  encoded-normal  smoothness
    output.GBuffer3 = half4(globalIllumination, 1);                                  // GI                GI              GI              unused          (lighting buffer)
    #if _RENDER_PASS_ENABLED
    output.GBuffer4 = inputData.positionCS.z;
    #endif
    #if OUTPUT_SHADOWMASK
    output.GBUFFER_SHADOWMASK = inputData.shadowMask; // will have unity_ProbesOcclusion value if subtractive lighting is used (baked)
    #endif
    #ifdef _WRITE_RENDERING_LAYERS
    uint renderingLayers = GetMeshRenderingLayer();
    output.GBUFFER_LIGHT_LAYERS = float4(EncodeMeshRenderingLayer(renderingLayers), 0.0, 0.0, 0.0);
    #endif

    return output;
}

저희가 필요한건 알베도 혹은 디퓨즈이니, GBuffer0만 꺼내쓰면 됩니다.

사용하기

첫번째로 정의입니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    SamplerState my_point_clamp_sampler;
    TEXTURE2D_X_HALF(_GBuffer0);
    TEXTURE2D_X_HALF(_GBuffer1);
    TEXTURE2D_X_HALF(_GBuffer2);
    #if _RENDER_PASS_ENABLED

        #define GBUFFER0 0
        #define GBUFFER1 1
        #define GBUFFER2 2
        #define GBUFFER3 3

        FRAMEBUFFER_INPUT_HALF(GBUFFER0);
        FRAMEBUFFER_INPUT_HALF(GBUFFER1);
        FRAMEBUFFER_INPUT_HALF(GBUFFER2);
        FRAMEBUFFER_INPUT_FLOAT(GBUFFER3);
        #if OUTPUT_SHADOWMASK
        #define GBUFFER4 4
        FRAMEBUFFER_INPUT_HALF(GBUFFER4);
        #endif
    #else
        #ifdef GBUFFER_OPTIONAL_SLOT_1
        TEXTURE2D_X_HALF(_GBuffer4);
        #endif
    #endif
    
    #if defined(GBUFFER_OPTIONAL_SLOT_2) && _RENDER_PASS_ENABLED
    TEXTURE2D_X_HALF(_GBuffer5);
    #elif defined(GBUFFER_OPTIONAL_SLOT_2)
    TEXTURE2D_X(_GBuffer5);
    #endif
    #ifdef GBUFFER_OPTIONAL_SLOT_3
    TEXTURE2D_X(_GBuffer6);
    #endif

Gbuffer를 가져와 사용하는 “Hidden/Universal Render Pipeline/StencilDeferred” 를 참조하면,

위와 같은 영역에서 G버퍼를 가져와 사용함을 알 수 있습니다.

그리고 사용부를 본다면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    half4 shadowMask = 1.0;

    #if _RENDER_PASS_ENABLED
    float d        = LOAD_FRAMEBUFFER_INPUT(GBUFFER3, input.positionCS.xy).x;
    half4 gbuffer0 = LOAD_FRAMEBUFFER_INPUT(GBUFFER0, input.positionCS.xy);
    half4 gbuffer1 = LOAD_FRAMEBUFFER_INPUT(GBUFFER1, input.positionCS.xy);
    half4 gbuffer2 = LOAD_FRAMEBUFFER_INPUT(GBUFFER2, input.positionCS.xy);
    #if defined(_DEFERRED_MIXED_LIGHTING)
    shadowMask = LOAD_FRAMEBUFFER_INPUT(GBUFFER4, input.positionCS.xy);
    #endif
    #else
    // Using SAMPLE_TEXTURE2D is faster than using LOAD_TEXTURE2D on iOS platforms (5% faster shader).
    // Possible reason: HLSLcc upcasts Load() operation to float, which doesn't happen for Sample()?
    float d        = SAMPLE_TEXTURE2D_X_LOD(_CameraDepthTexture, my_point_clamp_sampler, screen_uv, 0).x; // raw depth value has UNITY_REVERSED_Z applied on most platforms.
    half4 gbuffer0 = SAMPLE_TEXTURE2D_X_LOD(_GBuffer0, my_point_clamp_sampler, screen_uv, 0);
    half4 gbuffer1 = SAMPLE_TEXTURE2D_X_LOD(_GBuffer1, my_point_clamp_sampler, screen_uv, 0);
    half4 gbuffer2 = SAMPLE_TEXTURE2D_X_LOD(_GBuffer2, my_point_clamp_sampler, screen_uv, 0);
    #if defined(_DEFERRED_MIXED_LIGHTING)
    shadowMask = SAMPLE_TEXTURE2D_X_LOD(MERGE_NAME(_, GBUFFER_SHADOWMASK), my_point_clamp_sampler, screen_uv, 0);
    #endif
    #endif

위와 같이 샘플링 하고있죠.

여기서 screen_uv는 일반적으로 쉐이더그래프를 통해 만들어진 SurfaceDescriptionInputs 의 NDCPosition과 같은 값이며,

StencilDeferred의 좌표계변화식만 따오고보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    float4 posHCS = TransformWorldToHClip(IN.WorldSpacePosition);

    
    float3 screenUV = posHCS.xyw;
    #if UNITY_UV_STARTS_AT_TOP
    screenUV.xy = screenUV.xy * float2(0.5, -0.5) + 0.5 * screenUV.z;
    #else
    screenUV.xy = screenUV.xy * 0.5 + 0.5 * screenUV.z;
    #endif

    float2 screen_uv = screenUV.xy / screenUV.z;
    
    #if defined(SUPPORTS_FOVEATED_RENDERING_NON_UNIFORM_RASTER)
        float2 undistorted_screen_uv = screen_uv;
        UNITY_BRANCH if (_FOVEATED_RENDERING_NON_UNIFORM_RASTER)
        {
            screen_uv = input.positionCS.xy * _ScreenSize.zw;
        }
    #endif

와 같습니다.

그러므로

1
    half4 gbuffer0 = SAMPLE_TEXTURE2D_X_LOD(_GBuffer0, my_point_clamp_sampler, IN.NDCPosition.xy, 0);

로 처리할 수 있습니다.

결과

이렇게 받아온 g버퍼를 통해 쉐도우캐스터가 적용되지 않은 상태인 순수 알베도(혹은 디퓨즈) 값만을 받아올 수 있으며,

Gbuffer 프레임 디버거로 확인하는 G버퍼값

G버퍼값과Lit G버퍼컬러만 Lit에 넣은 결과

뎁스와 혼합해 아래처럼 만들 수 있습니다

뎁스혼합

NDC 좌표계에 대해 조금 혼란스럽다면 다음 글을 읽어보아도 좋습니다.

참조 포럼

This post is licensed under CC BY 4.0 by the author.
Contents