머리
학교 프로젝트가 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버퍼를 통해 쉐도우캐스터가 적용되지 않은 상태인 순수 알베도(혹은 디퓨즈) 값만을 받아올 수 있으며,
프레임 디버거로 확인하는 G버퍼값
G버퍼컬러만 Lit에 넣은 결과
뎁스와 혼합해 아래처럼 만들 수 있습니다
NDC 좌표계에 대해 조금 혼란스럽다면 다음 글을 읽어보아도 좋습니다.