티스토리 뷰
Chap 7. G-Buffer 의 B. GBuffer는 어떻게 생성되나요? - 1
New shading models and changing the GBuffer | Community tutorial
Implementing a Celshading model directly into UE5.1 source. This celshading use a linear color curve atlas to drive all the values. Learn how to set you...
dev.epicgames.com
한가위 전에 공부한 내용인데 복습할겸~ 해서 전에 공부한 내용을 짧지만 포스팅 하기로 했다.
먼저 해당 방법을 이용해서 GBuffer 렌더 타겟을 추가하면 다음을 사용할 수 없다.
- 레이 트레이싱 그림자
- 레이 트레이싱 채광창
- 모바일 지연 렌더링
모바일의 경우는 이는 하드웨어 제한 사항이 된다. 더 이상의 GBuffer 렌더 타겟을 추가할 수 없기 때문. GBuffer는 대역폭을 절약하기 위해 모바일용 메모리에 저장되지 않고 Tiled GPU 메모리에 직접 저장된다. 따라서 CustomData 버퍼 사용을 권장. 한다고 한다.
Gbuffer 논리 및 베이스패스
베이스 패스가 어떻게 작동하는지 알아 봐야 한다.
UE는 모든 셰이딩 모델, 버텍스 팩토리 및 블렌드 모드에 대해 하나의 베이스 패스만 사용한다. 아마 BasePassPixelShader.usf 인듯 하다
파일을 열면 엄창난 양의 #if / #endif 전처리기가 있다.
참고: UE는 HLSL 에 .usf 및 .ush ( Unreal Shader File 및 Unreal Shader Header ) 를 사용합니다. 일반 HLSL과 동일하지만 추가 매크로가 있습니다.
BasePassPixelShader.usf 파일에는 FPixelShaderInOut_MainPS()라는 큰 함수가 있다.
// is called in MainPS() from PixelShaderOutputCommon.usf
void FPixelShaderInOut_MainPS(
FVertexFactoryInterpolantsVSToPS Interpolants,
FBasePassInterpolantsVSToPS BasePassInterpolants,
in FPixelShaderIn In,
inout FPixelShaderOut Out)
{
#if INSTANCED_STEREO
const uint EyeIndex = Interpolants.EyeIndex;
ResolvedView = ResolveView(EyeIndex);
#else
const uint EyeIndex = 0;
ResolvedView = ResolveView();
#endif
FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, In.SvPosition);
FPixelMaterialInputs PixelMaterialInputs;
[...]
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS && !IS_NANITE_PASS
{
float4 ScreenPosition = SvPositionToResolvedScreenPosition(In.SvPosition);
float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(In.SvPosition);
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, In.SvPosition, ScreenPosition, In.bIsFrontFace, TranslatedWorldPosition, BasePassInterpolants.PixelPositionExcludingWPO);
}
#elif IS_NANITE_PASS
{
// TODO: PROG_RASTER - USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float3 TranslatedWorldPosition = MaterialParameters.WorldPosition_CamRelative;
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, In.SvPosition, MaterialParameters.ScreenPosition, In.bIsFrontFace, TranslatedWorldPosition, TranslatedWorldPosition);
}
#else
{
CalcMaterialParameters(MaterialParameters, PixelMaterialInputs, In.SvPosition, In.bIsFrontFace);
}
#endif
내부가 진짜 거대한 if/endif 문임…
가만히 보다보면 LIGHTMAP_VT_ENABLED, USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS, USE_EDITOR_COMPOSITING, EARLY_Z_PASS_ONLY_MATERIAL_MASKING, MATERIALBLENDING_MASKED_USING_COVERAGE
등과 같이 어떤 기능들을 사용할 것이냐에 대한 if/endif 문이 작성되어 있다.. 라이트맵, 마테리얼 블랜딩 등등... 그리고 사용할 기능에 따라 달라질 함수들에 대한 호출이 전부 서술되어 있다. ㅎㄷㄷ
베이스패스 시작 시. 장면 뷰(VR의 경우 스테레오)를 얻은 다음 모든 정점/라이트맵/픽셀 정보를 얻습니다.
몇 가지 계산(픽셀 오프셋 등)을 수행합니다. 그런 다음 FGBufferData 구조체를 만듭니다 .
이 구조체는 다른 패스(조명/음영/포스트 프로세스)에서 Gbuffer의 렌더 대상을 샘플링한 후 모든 데이터를 저장한다.
여기서는 해당 데이터를 GBuffer의 렌더 타겟으로 인코딩하는 데 사용한다고 함.
FGBufferData GBuffer = (FGBufferData)0;
// [...] FPixelShaderInOut_MainPS
// File: DeferredShadingCommon.ush
// all values that are output by the forward rendering pass
struct FGBufferData
{
// normalized
half3 WorldNormal;
// normalized, only valid if HAS_ANISOTROPY_MASK in SelectiveOutputMask
half3 WorldTangent;
// 0..1 (derived from BaseColor, Metalness, Specular)
half3 DiffuseColor;
// 0..1 (derived from BaseColor, Metalness, Specular)
half3 SpecularColor;
// 0..1, white for SHADINGMODELID_SUBSURFACE_PROFILE and SHADINGMODELID_EYE (apply BaseColor after scattering is more correct and less blurry)
half3 BaseColor;
// [...]
half StoredSpecular;
// 0..1, only needed by SHADINGMODELID_EYE which encodes Iris Distance inside Metallic
half StoredMetallic;
// Curvature for mobile subsurface profile
half Curvature;
}
보면 여러가지 WorldNormal , WorldTangent , DiffuseColor 등의 데이터들이 잔뜩 들어있음. 여러 패스들을 통해 렌더 할 대상을 샘플링하여 이 구조체를 채우고 이 구조체의 데이터를 통해 렌더 타겟으로 인코딩 하는 과정 인것같다.
FGBufferData 는 DeferredShadingCommon.ush 파일 안에 있음.
그렇게 만든 GBuffer는 초기화 아래에서 SetGBufferForShadingModel() 함수를 사용하여 데이터를 입력받는다. (사용된 셰이딩 모델을 기반으로 머티리얼 정보를 제공한다. 라고 함..) 아래 함수의 위치는 BasePassPixelShader.usf
// [...]
FGBufferData GBuffer = (FGBufferData)0;
GBuffer.GBufferAO = MaterialAO;
GBuffer.PerObjectGBufferData = GetPrimitive_PerObjectGBufferData(MaterialParameters.PrimitiveId);
GBuffer.Depth = MaterialParameters.ScreenPosition.w;
GBuffer.PrecomputedShadowFactors = GetPrecomputedShadowMasks(
LightmapVTPageTableResult,
Interpolants,
MaterialParameters,
VolumetricLightmapBrickTextureUVs);
#if !STRATA_ENABLED || STRATA_INLINE_SINGLELAYERWATER
SetGBufferForShadingModel(
GBuffer,
MaterialParameters,
Opacity,
BaseColor,
Metallic,
Specular,
Roughness,
Anisotropy,
SubsurfaceColor,
SubsurfaceProfile,
Dither,
ShadingModel
);
#endif // !STRATA_ENABLED
// [...]
그리고 SetGBufferForShadingModel 함수 내부.
void SetGBufferForShadingModel(
in out FGBufferData GBuffer,
in out FMaterialPixelParameters MaterialParameters,
const float Opacity,
const half3 BaseColor,
const half Metallic,
const half Specular,
const float Roughness,
const float Anisotropy,
const float3 SubsurfaceColor,
const float SubsurfaceProfile,
const float Dither,
const uint ShadingModel)
{
GBuffer.WorldNormal = MaterialParameters.WorldNormal;
GBuffer.WorldTangent = MaterialParameters.WorldTangent;
GBuffer.BaseColor = BaseColor;
GBuffer.Metallic = Metallic;
GBuffer.Specular = Specular;
GBuffer.Roughness = Roughness;
GBuffer.Anisotropy = Anisotropy;
GBuffer.ShadingModelID = ShadingModel;
// Calculate and set custom data for the shading models that need it
// Dummy initial if statement to play nicely with the #ifdef logic
if (false)
{
}
#if MATERIAL_SHADINGMODEL_SUBSURFACE
else if (ShadingModel == SHADINGMODELID_SUBSURFACE)
{
GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor);
GBuffer.CustomData.a = Opacity;
}
#endif
...
}
이러한 과정으로 만들어진 데이터들은 마지막에 MRT 텍스처로 인코딩 된다. MRT 텍스처는 C++ 셰이더 구조체의 GBufferXTextures에 해당하는 HLSL이다. EncodeGBufferToMRT() 함수는 다음과 같다.
// [...]
// this is the new encode, the older encode is the #else, keeping it around briefly until the new version is confirmed stable.
#if 1
{
// change this so that we can pack everything into the gbuffer, but leave this for now
#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
GBuffer.GenericAO = float(GBuffer.DiffuseIndirectSampleOcclusion) * rcp(255) + (0.5 / 255.0);
#elif ALLOW_STATIC_LIGHTING
// No space for AO. Multiply IndirectIrradiance by AO instead of storing.
GBuffer.GenericAO = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0); // Stationary sky light path
#else
GBuffer.GenericAO = GBuffer.GBufferAO; // Movable sky light path
#endif
EncodeGBufferToMRT(Out, GBuffer, QuantizationBias);
if (GBuffer.ShadingModelID == SHADINGMODELID_UNLIT && !STRATA_ENABLED) // Do not touch what strata outputs
{
Out.MRT[1] = 0;
SetGBufferForUnlit(Out.MRT[2]);
Out.MRT[3] = 0;
Out.MRT[GBUFFER_HAS_VELOCITY ? 5 : 4] = 0;
Out.MRT[GBUFFER_HAS_VELOCITY ? 6 : 5] = 0;
}
#if SINGLE_LAYER_WATER_SEPARATED_MAIN_LIGHT
// In deferred, we always output the directional light in a separated buffer.
// This is used to apply distance field shadows or light function to the main directional light.
// Strata also writes it through MRT because this is faster than through UAV.
#if STRATA_ENABLED && STRATA_INLINE_SINGLELAYERWATER
Out.MRT[(GBUFFER_HAS_VELOCITY ? 2 : 1) + (GBUFFER_HAS_PRECSHADOWFACTOR ? 1 : 0)] = float4(SeparatedWaterMainDirLightLuminance * View.PreExposure, 1.0f);
#else
if (GBuffer.ShadingModelID == SHADINGMODELID_SINGLELAYERWATER)
{
Out.MRT[(GBUFFER_HAS_VELOCITY ? 6 : 5) + (GBUFFER_HAS_PRECSHADOWFACTOR ? 1 : 0)] = float4(SeparatedWaterMainDirLightLuminance * View.PreExposure, 1.0f);
}
#endif
#endif
}
else
{
// Old legacy Gbuffer generation
// [...]
EncodingGBufferToMRT() 함수는 신기하게도.. C++ 함수에서 만들어지는듯.
static FString CreateGBufferEncodeFunction(const FGBufferInfo& BufferInfo)
{
FString FullStr;
FullStr += TEXT("void EncodeGBufferToMRT(inout FPixelShaderOut Out, FGBufferData GBuffer, float QuantizationBias)\\n");
FullStr += TEXT("{\\n");
int32 TargetChanNum[FGBufferInfo::MaxTargets] = {};
// for each values, 0 means float, 1 means int, and -1 means unused
int32 PackedStatus[FGBufferInfo::MaxTargets][4];
for (int32 I = 0; I < FGBufferInfo::MaxTargets; I++)
{
for (int32 J = 0; J < 4; J++)
{
PackedStatus[I][J] = -1;
}
}
bool bNeedsConversion[GBS_Num] = {};
...
}
이렇게 만들어진다!
이 함수는 DecodeGBufferDataDirect() 및 DecodeGBufferDataUV() 와 같이 픽셀을 샘플링하는 다른 함수와 마찬가지로 C++에서 생성됩니다.
YourProjectName \Intermediate\ShaderAutogen\PCD3D_SM6\AutogenShaderHeaders.ush 파일에서 생성된 코드를 찾을 수 있습니다.
라고 한다.
무슨 말이냐면 C++ 내의 함수안에서 새로운 함수를 만든다는 뜻이다. 그래서 경로에 가보면 AutogenShaderHeaders.ush 이라고 하는 자동 생성된 ush 파일을 볼 수 있다. 내부에 있는 함수들을 보면
EncodeGBufferToMRT
DecodeGBufferDataDirect
DecodeGBufferDataUV
DecodeGBufferDataUint
DecodeGBufferDataSceneTextures
DecodeGBufferDataSceneTexturesLoad
함수들이 있다는걸 알 수 있다. 위의 설명처럼 이 함수들은 FGBufferData 에 있는 데이터들을 각 MRT로 옮기거나 (Encode) MRT에서 FGBufferData 로 데이터를 옮기는 (Decode) 함수들이 될 것이다. 줄여서 픽셀을 샘플링하게 된다고 말할 수 있겠다.
이어서 위의 CreateGBufferEncodeFunction 함수가 인자로 받는 FGBufferInfo 에 들어가면
GBufferInfo.h 내부에 있는
struct FGBufferInfo
{
static const int MaxTargets = 8;
int32 NumTargets;
FGBufferTarget Targets[MaxTargets];
FGBufferItem Slots[GBS_Num];
};
를 보여준다. 꽤 신기하다!ㅋㅋ
직접적으로 타겟의 개수에 영향을 주고, 타겟을 쥐고 관리하는 구조체인듯 하다.
BasePassPixelShader.usf 의 파일 제일 아래에 보면 include가 되어 있따.
HLSL에서는 함수 순서가 중요하므로 MainPS() 전에 basepass 함수를 선언해야 합니다 . 이것이 바로 끝에 포함된 이유입니다.
라고 설명되어 있다. 실제로 파일을 열어보면 파일 제일 아래에 include가 있는 모습을 볼 수 있다.
/ [...]
} // End of FPixelShaderInOut_MainPS()
// [...]
// all PIXELSHADEROUTPUT_ and "void FPixelShaderInOut_MainPS()" need to be setup before this include
// this includes generates the wrapper code to call MainPS(inout FPixelShaderOutput PixelShaderOutput)
#if COMPUTE_SHADED
#include "ComputeShaderOutputCommon.ush"
#else
#include "PixelShaderOutputCommon.ush"
#endif
include를 하면 파일이 포함되니까 이 부분까지도 제일 아래로 내려버린듯. 신기한 접근방법.
요약
UE는 처음에 공통된 베이스 패스를 사용. BasePassPixelShader.usf
베이스 패스를 지나면서 계산을 수행. FGBufferData 구조체를 만듦.
FGBufferData 구조체는 MRT를 그리기 위한 정보가 담겨있는 구조체.
SetGBufferForShadingModel() 함수를 통해 FGBufferData 에 데이터를 담는다.
완성된 FGBufferData 구조체는 마지막에 MRT 텍스처로 인코딩된다.
인코딩에 필요한 함수들은 신기하게도 C++ 함수를 통해 텍스처 파일로 만들어짐.
'Unreal > New Shading Model' 카테고리의 다른 글
New Shading Model 개발 근황 (0) | 2023.10.11 |
---|---|
[UE 5.1] New Shading Model - GBuffer의 변경 C++ (0) | 2023.10.06 |
[UE 5.1] New Shading Model - GBuffer의 생성 - 2 (1) | 2023.10.04 |
[UE 5.1] New Shading Model Beginning (0) | 2023.09.26 |
- Total
- Today
- Yesterday
- UE5
- sort
- level3
- 누적합
- 디자인 패턴
- 프로그래머스
- Heap
- 채팅서버
- 스택/큐
- C++
- 완전탐색
- DFS
- 재귀
- 고득점 Kit
- greedy
- 해시
- LV2
- IMGUI
- BFS
- 탐욕법
- LV3
- FPS
- 너비우선탐색
- Ue
- 힙
- 개인공부
- 데디케이티드
- 고득점kit
- 정렬
- Unreal 5.1
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |