| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- forward
- texture
- deferred
- scalar
- scattering
- hzb
- Wavefront
- atmospheric
- GPU
- Shadow
- shader
- VGPR
- ue4
- Nanite
- Graphics
- GPU Driven Rendering
- ShadowMap
- DX12
- optimization
- Study
- DirectX12
- SIMD
- UE5
- RayTracing
- unrealengine
- vulkan
- wave
- SGPR
- 번역
- rendering
- Today
- Total
RenderLog
RayTraced Ambient Occlusion(RTAO) 본문
RayTraced Ambient Occlusion(RTAO)
최초 작성 : 2024-01-31
마지막 수정 : 2024-01-31
최재호
목차
1. 목표
2. 내용
2.1. DispatchRay 코드 작성
2.2. Desnosing
2.2.1. RayPerPixel 개수를 늘린 경우
2.2.2. Temporal
2.2.2.1. RayAccumulation
2.2.2.2. Reprojection
2.2.3. Spatial
2.2.3.1. Jittering
2.2.3.2. Filtering
2.2.3.2.1. GaussianBlur(Separable)
2.2.3.2.2. BilateralFilter
3. 구현 결과
4. 실제 구현 코드
5. 레퍼런스
1. 목표
RayTracing 을 사용한 Ambient Occlusion 을 구현해 보며 RayTracing 에 익숙해져 봅시다.
리얼타임 렌더링을 위해서 픽셀 당 사용 가능 한 Ray 수는 그렇게 많지 않습니다. 이런 부분을 개선할 수 있는 방식에 대해서 알아봅시다.
사전지식
- DXR or VkRaytracing 에 대한 이해
이후 글에서는 RayTranced Ambient Occlusion 을 RTAO 라고 부르겠습니다.
구현에 사용한 그래픽카드는 RTX3070 입니다.

2. 내용
RayTracing 을 시작하면서 마주치는 첫 번째 이슈는 렌더타겟의 픽셀 당 발사할 수 있는 Ray 가 굉장히 제한적이라는 것입니다. 렌더타겟의 픽셀 당 2개 이상의 레이를 발사 할 수도 있지만 발사하는 Ray 가 늘어날수록 성능이 급격하게 떨어지는 것을 확인할 수 있을 것입니다. 그림2 봐주세요.

그림2 의 좌측에서부터 각각 픽셀 당 1 개, 3 개, 10개 의 Ray 를 사용한 경우 GPU 에서의 처리시간 차이를 보여줍니다.
이런 이유로 이 글의 RTAO 구현에서는 렌더타겟의 각 픽셀 당 1개의 Ray 를 발사하는 것을 목표로 합니다. 또한 기본적인 RayTracing 방식은 Camera 의 위치에서 각 렌더타겟의 샘플로 Ray를 발사하지만 그 단계를 줄이기 위해서 Hybrid RayTracing 방식을 사용합니다. GBuffer 생성까지는 기존의 디퍼드렌더링 패스를 따라가고 이후에 Raytracing 을 시작하는 시점에는 GBuffer 의 Position, Normal 정보를 기반으로 Ray 를 발사합니다. 이번 RTAO 예제에도 그런 방식을 따르고 있습니다.
2.1. DispatchRay 코드 작성
RTAO 의 Raytracing 코드는 자체는 아주 간단합니다. AO 의 차폐 여부만 비교하면 되기 때문입니다. 아래 코드를 봐주세요. RTAO 에서 사용하는 Shader 종류는 아래와 같습니다.
- 처음 Ray 를 생성해 주는 RayGeneration Shader
- Hit 처리를 담당하는 AnyHit , ClosestHit (Closest 는 Hit 한 것 중 가장 가까운 것) Shader
- 아무것도 Hit 하지 못했을 때 호출되는 Miss Shader 입니다. 이 경우 보통은 이 경우 환경맵 샘플링 할 것입니다.
[shader("raygeneration")]
void MyRaygenShader()
{
float2 UV = DispatchRaysIndex().xy / g_sceneCB.ViewRect.zw;
UV += g_sceneCB.HaltonJitter.xy; // Apply Jittering from Halton Sequence
// Get WorldPos, WorldNormal from G-Buffer
float3 WorldPos = GBuffer0_Pos.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
float3 WorldNormal = GBuffer1_Normal.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
...
// Shoot rays 'RayPerPixel' times
for(int i=0;i<g_sceneCB.RayPerPixel;++i)
{
RayDesc ray;
ray.Origin = WorldPos;
ray.Direction = random_in_hemisphere(WorldNormal, WorldPos);
ray.TMin = 0.001; // Small epsilon to avoid self intersection.
ray.TMax = g_sceneCB.AORadius;
RayPayload payload = { float4(1.0f, 1.0f, 1.0f, 1.0f) };
TraceRay(Scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES, ~0, 0, 0, 0, ray, payload);
++AccumulateCount;
// Incremental Average : https://blog.demofox.org/2016/08/23/incremental-averaging/
FinalAO = lerp(FinalAO.xyz, payload.color.xyz, 1.0 / AccumulateCount);
}
RenderTarget[DispatchRaysIndex().xy] = float4(FinalAO, AccumulateCount);
}
[shader("anyhit")]
void MyAnyHitShader(inout RayPayload payload, in MyAttributes attr)
{
payload.color = float4(0, 0, 0, 1);
}
[shader("closesthit")]
void MyClosestHitShader(inout RayPayload payload, in MyAttributes attr)
{
payload.color = float4(0, 0, 0, 1);
}
[shader("miss")]
void MyMissShader(inout RayPayload payload)
{
payload.color = float4(1, 1, 1, 1);
}
먼저 간단한 Hit Shader 시리즈를 확인해 봅시다. AO 는 차폐되는 경우 검은 그림자가 드리웁니다. 그래서 Hit 되는 경우는 암부로 표현해줘야 할 것입니다. 그래서 Hit, Miss Shader 를 보면 Hit 한 경우 Color 를 검은색, Miss 인 경우 흰색으로 설정합니다.
다음으로 RayGeneration Shader 입니다. 이 글에서는 Hybrid RayTracing 을 사용하기 때문에 RayGeneration 을 GBuffer 기준으로 합니다. GBuffer 에서 얻어온 WorldPos 와 WorldNormal 을 사용하여 TraceRay 함수를 호출합니다. WorldPos 는 그대로 사용하면 됩니다. 하지만 Ray 방향의 경우 매 WorldNormal 을 중심으로 한 반구 범위 내에서 랜덤 방향으로 Ray 를 발사합니다. AO 는 현재 위치와 인접한 위치로 인해서 얼마나 차폐 되는지 표현하는 것이 목표입니다. 주변 환경이 얼마나 차폐되어 있는지 알 수 있는 방법은 현재 위치에서 여러 방향으로 Ray 를 발사하여 차폐 결과를 누적하는 것 입니다. 차폐 여부를 판단하는 방식만 보더라도 픽셀 한개의 정확한 AO 를 처리 하는데 아주 많은 수의 Ray 가 필요하다는 것을 알 수 있습니다. 실제로 Ray 를 수 없이 발사하는 것으로 결정할 수도 있지만 Ray 발사 비용이 무한정 올라갈 것입니다.
우리는 이 글의 시작에서 Ray 발사 비용이 아주 비싸기 때문에 픽셀 당 1개의 Ray 를 발사하는 것을 목표로 하자고 했었습니다. 그럼 이제 어떻게 이 문제를 해결할지 차근차근 알아봅시다.

2.2. Desnosing
Ray 를 여러번 발사 할 수 없다면 Ray 를 여러번 발사한 것 처럼 추가 데이터를 활용해야 합니다. 이런 데이터를 Temporal, Spatial 방식을 통해서 얻어올 수 있을 것입니다. Temporal 의 경우 이전 프레임의 데이터를 재활용하는 것이며, Spatial 은 필터링과 같이 인접 위치의 정보를 활용하여 Noise 를 완화하는 것입니다.
본격적으로 들어가기 전에 Ray 를 개수를 단순히 늘려보는 결과도 확인해 봅시다. 얼마나 많은 Ray 를 발사해야 안정적인 AO 가 나오는지 퍼포먼스는 얼마나 더 사용하게 되는지 확인해봅시다.
2.2.1. RayPerPixel 개수를 늘린 경우
그림4 에서 보는 것과 같이 픽셀 당 30 개의 Ray 를 발사해서 샘플을 추가해 줬습니다. 결과물은 더 나아졌지만 RayTracing 비용이 기존 대비 7ms 이상 증가했습니다. 60FPS 를 유지하기 위해서 16ms 내에 모든 렌더링 과정을 마쳐야 되는 게임과 같은 상황에서는 사용할 수 없는 수준입니다.

2.2.2. Temporal
2.2.2.1. RayAccumulation
한 프레임에 발사할 수 있는 Ray 의 개수가 제한적이라면 Temporal 데이터를 추가로 활용해 볼 수 있을 것입니다. 간단한 방법으로는 발사한 Ray 를 계속해서 AO 렌더타겟에 계속 누적하는 것입니다. 그림5 를 보면 픽셀 당 1개의 Ray 를 발사하는데 비해서 Noise 가 상당히 줄어든 것을 볼 수 있습니다.

아래 코드를 통해서 AO 를 Accumulate 시키며, 최대 500개의 픽셀까지만 누적시킵니다.
[shader("raygeneration")]
void MyRaygenShader()
{
float2 UV = DispatchRaysIndex().xy / g_sceneCB.ViewRect.zw;
UV += g_sceneCB.HaltonJitter.xy; // Apply Jittering from Halton Sequence
float3 WorldPos = GBuffer0_Pos.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
float3 WorldNormal = GBuffer1_Normal.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
float3 FinalAO = 0;
float AccumulateCount = 0.0f;
if (!g_sceneCB.Clear) // Clear Accumulated AO when moving camera or option changed
{
FinalAO = RenderTarget[DispatchRaysIndex().xy].xyz;
AccumulateCount = RenderTarget[DispatchRaysIndex().xy].w;
}
// Max accumulateCount is 500
if (AccumulateCount > 500)
return;
...
}
이 방식은 사용 중인 렌더타겟에 AO 정보를 계속 누적하는 방식이기 때문에 제약사항이 있습니다. 예를 들면 화면을 이동 중인 물체가 있거나 카메라가 이동하는 경우 기존의 누적한 AO 를 전부 버리고 새로 누적해야 하는 단점이 있습니다. 이런 부분을 개선해 주는 것이 바로 Reprojection 입니다. 계속해서 해당 내용을 확인해 봅시다.
2.2.2.2. Reprojection
Reprojection 은 VelocityBuffer 를 사용하여 현재 쉐이딩 중인 픽셀이 이전 프레임에는 어느 위치에서 그려지고 있었는지 정보를 얻습니다. 그리고 이전 프레임의 위치를 얻을 수 있었다면, HistoryBuffer 에서 이전 프레임에 기록한 데이터를 가져와서 재활용합니다. 여기서 HistoryBuffer 는 이전 프레임에 렌더링 된 AO 정보를 담고 있는 렌더타겟 입니다. 이 부분은 TAA(Temporal Antialiasing) 에서 사용되는 기법이며 레퍼런스7 를 참고하였습니다.
아래 코드는 Reprojection 을 수행하는 코드입니다. VelocityBuffer 를 통해서 이전 프레임에 렌더링 된 위치의 AO 를 얻어옵니다. 이 값을 HistoryColor 로 둡니다. 그런 뒤 HistoryColor 와 이번 프레임에 Ray 를 발사하여 만든 CurrentColor 정보를 혼합해줍니다. 여기서는 HistoryColor 를 90% 사용하도록 하였습니다. 그림6 를 보면 Reprojection 후 Ray 의 Noise 가 많이 감소한 것을 확인할 수 있습니다.

float4 AOReprojectionPS(VSOutput input) : SV_TARGET
{
// Get previous frame's UV
float4 Velocity = VelocityBuffer.Sample(TextureSampler, input.TexCoord);
float2 OldUV = input.TexCoord - (Velocity.xy / float2(ComputeCommon.Width * ComputeCommon.InvScaleToOriginBuffer, ComputeCommon.Height * ComputeCommon.InvScaleToOriginBuffer));
// Fetch current and previous frame's AO
float3 currentColor = CurrentTexture.Sample(TextureSampler, input.TexCoord).xyz;
float3 historyColor = HistoryBuffer.Sample(TextureSampler, OldUV).xyz;
float ReprojectionWeight = 0.9;
#if USE_DISCONTINUITY_WEIGHT
// Use Reprojection Data depending on Discontinuity of depth.
float DiscontinuityWeight = abs(DepthBuffer.Sample(TextureSampler, input.TexCoord).x - HistoryDepthBuffer.Sample(TextureSampler, input.TexCoord).x) < 0.01;
ReprojectionWeight *= DiscontinuityWeight;
#endif // USE_DISCONTINUITY_WEIGHT
return float4(lerp(currentColor, historyColor, ReprojectionWeight), 1.0);
}
하지만 이 Reprojection 을 수행하는 경우 그림7 와 같이 Ghosting 현상이 발생합니다. 이전 위치의 정보를 재활용하기 때문에 렌더링 하는 오브젝트가 움직이거나 카메라가 움직이는 경우 이전 픽셀을 정보가 남을 것입니다.

이 부분을 완화하기 위해서 화면상의 Discontinuity 정보를 활용할 수 있을 것입니다. 예를 들면, Color Intensity, Depth, Normal Direction 정보 같은 부분들이 좋은 후보가 될 수 있을 것입니다. 이 구현에서는 간단한 Depth 정보를 사용하여 Ghosting 현상을 줄였습니다. 만약 Discontinuity 가 발견된 지점이라면 Reprojection 을 포기하고 현재 픽셀을 사용합니다. 그림8 를 보면 Ghosting 이슈가 많이 완화된 것을 볼 수 있습니다만 현재 프레임에 생성한 AO 를 그대로 사용했기 때문에 해당 부분이 Noise 가 강조됩니다. 하지만 이 부분은 뒤에서 볼 Spatial Filter 를 적용하고 나면 상당 부분 완화되며, 오브젝트의 테두리가 더 선명하게 유지되는 점도 확인 할 수 있습니다. 그림8 의 우측 이미지를 참고해 주세요.

지금까지는 Temporal 정보를 사용하여 Noise 를 감소 시켰습니다. 이제부터는 Spatial 정보를 사용하여 Noise 를 감소시키는 과정을 보겠습니다.
2.2.3. Spatial
2.2.3.1. Jittering
Jittering 은 Ray 발사에 무작위성을 추가로 더 부여합니다. Halton Sequence 를 사용하여 매 프레임 Ray 가 발사되는 WorldPos 에 약간의 Offset 을 더해줍니다. Halton Sequence 는 레퍼런스7 의 TAA 에서 제시 된 16개의 데이터를 약간 수정하여 그대로 사용했습니다.
아래 코드는 Halton Sequence 를 G-Buffer 를 Fetch 할 UV 에 적용하는 것을 보여줍니다. 또한 C++ 코드에서 생성하는 Halton sequence 를 보여줍니다.
// Raygen shader
[shader("raygeneration")]
void MyRaygenShader()
{
float2 UV = DispatchRaysIndex().xy / g_sceneCB.ViewRect.zw;
UV += g_sceneCB.HaltonJitter.xy; // Apply Jittering from Halton Sequence
float3 WorldPos = GBuffer0_Pos.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
float3 WorldNormal = GBuffer1_Normal.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
...
}
// C++ 코드 의 Halton sequence 생성 코드
static Vector2 HaltonJitter[]={
Vector2(0.0f, -0.333334f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.5f, 0.333334f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(0.5f, -0.777778f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.75f, -0.111112f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(0.25f, 0.555556f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.25f, -0.555556f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(0.75f, 0.111112f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.875f, 0.777778f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(0.125f, -0.925926f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.375f, -0.259260f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(0.625f, 0.407408f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.625f, -0.703704f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(0.375f, -0.037038f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.125f, 0.629630f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(0.875f, -0.481482f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
Vector2(-0.9375f, 0.185186f) / Vector2((float)RayRTWidth, (float)RayRTHeight)
};
그림9 의 상단은 Reprojection 만 적용한 상태입니다. 이 경우 아치형 표면에서 Aliasing 이 발생합니다. 그림 9 의 하단은 Jittering 을 켠 후 결과입니다. 이 경우 Aliasing 이 제거된 것을 확인할 수 있습니다.

2.2.3.2. Filtering
다음으로는 이미지 필터링 방식을 봅시다. 이번에는 간단히 Gaussian 과 Bilateral Filter 를 사용하여 Noise 를 줄여봅니다.
2.2.3.2.1. GaussianBlur(Separable)
GaussianBlur 의 경우 좋은 설명이 레퍼런스3 에 있습니다. 그래서 추가로 해당 내용에 대한 설명은 하지 않을 것입니다. 바로 결과 차이를 확인해 봅시다.
그림10 를 보면 GaussianBlur 를 통해서 이미지의 High frequency noise 가 많이 감소한 것을 확인할 수 있습니다.

2.2.3.2.2. BilateralFilter
BilateralFiltering 의 경우도 좋은 설명이 레퍼런스4 에 있습니다. GaussianBlur 와 비교하고 차이점을 간단히 설명하면 다음과 같습니다.
GaussianBlur 는 이미지의 모든 영역에 대한 High frequency 를 동일하게 감소시키지만 BilateralFilter 의 경우는 선택적으로 high frequency 정보를 유지할 수 있습니다. 이 글에서는 Depth 정보를 기반으로 AO 에서 오브젝트의 테두리 정보를 보존하도록 했습니다. 그림11 를 보면 기존 GaussianBlur 보다 Edge 정보가 더 잘 보존 되는 것을 볼 수 있습니다. 이것을 통해서 앞쪽에 있는 오브젝트의 AO 가 뒤쪽에 있는 오브젝트의 AO 에 묻어나는 것을 피할 수 있을 것입니다.

아래 코드의 ‘// Bilateral with depth’ 주석 하단 코드가 Bilateral Filter 를 적용하는 부분입니다. 현재 구현 중인 RTAO 의 경우 Linear Depth 를 사용하지 않기 때문에 Depth 의 Range 가 0.9~1.0 사이에 거의 몰려있습니다. 그래서 Bilateral 에 사용하는 sigma(표준편차) 를 거기에 맞게 작은 값으로 유지해줘야 합니다.
[numthreads(16, 16, 1)]
void Bilateral(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
if (GlobalInvocationID.x >= ComputeCommon.Width || GlobalInvocationID.y >= ComputeCommon.Height)
return;
int kernelSize = ComputeCommon.KernalSize;
int center = kernelSize / 2;
int2 PixelPos = int2(GlobalInvocationID.xy);
float2 CenterUV = PixelPos / float2(ComputeCommon.Width, ComputeCommon.Height);
float CenterDepth = DepthBuffer.SampleLevel(DepthSampler, CenterUV, 0).x;
float3 Color = float3(0, 0, 0);
float TotalWeight = 0;
for (int i = 0; i < kernelSize; ++i)
{
for (int j = 0; j < kernelSize; ++j)
{
int x = (i - center);
int y = (j - center);
float Gs = GetGaussian2DKernel(i, j);
float2 CurPixelPos = clamp(PixelPos + float2(x, y), float2(0, 0), float2(ComputeCommon.Width, ComputeCommon.Height));
float3 CurrentPixel = inputImage[CurPixelPos].xyz;
// Bilateral with depth
float2 CurUV = CurPixelPos / float2(ComputeCommon.Width, ComputeCommon.Height);
float DepthDifference = abs(DepthBuffer.SampleLevel(DepthSampler, CurUV, 0).x - CenterDepth);
float Gi = Gaussian1D(DepthDifference, ComputeCommon.SigmaForBilateral);
#if USE_GAUSSIAN_INSTEAD
Color += Gs * CurrentPixel;
TotalWeight += Gs;
#else
Color += Gs * Gi * CurrentPixel;
TotalWeight += Gs * Gi;
#endif
}
}
Color /= TotalWeight;
resultImage[int2(GlobalInvocationID.xy)] = float4(Color, 1.0);
}
아쉬운 점은 Bilateral Filtering 의 경우 Separable Filter 가 아닙니다. 그래서 이 RTAO 에서는 필터 크기가 커질 수록 Bilateral 이 Gaussian 에 비해 연산양이 기하급수 적으로 늘어난다는 점입니다. 이 부분을 개선한 Bilateral Filtering 이 있다면 적용해보면 더 좋을 것 같습니다.
RTAO 에 적용한 내용은 여기까지입니다. 구현하면서 느낀 점은 “발사되는 Ray 의 수를 줄이기 위해서 좋은 Denoiser 가 중요하다.” 입니다. 추후에 Spatiotemporal Variance-Guided Filtering 에 대해서도 다뤄보면 좋을 것 같습니다. 혹시 해당 내용을 다루게 된다면 여기에 링크를 달아두겠습니다.
3. 구현 결과
RTAO 를 위한 렌더패스를 정리하면 아래와 같습니다.
DispatchRay → ReprojectionAO → CopyDepthBuffer → BilateralFilter → CopyHistoryBuffer → ApplyAO
CopyDepth 와 CopyHistory Buffer 는 이전 프레임의 RenderTarget 을 재활용하여 복사를 제거할 수도 있을 것 같습니다. 하지만 여기서는 명시적으로 이전 렌더타겟 정보를 보존해야 함을 보기 위해서 그대로 남겨뒀습니다.
그림12 는 RTAO 의 Denosing 과정별 결과 차이를 보여줍니다.

그림13 은 RTAO 를 Sponza Scene 에 적용하기 전과 후의 비주얼 차이를 보여줍니다.

아래 동영상은 옵션을 변경하면서 AO 적용 결과를 확인해 본 것입니다. Denosing 이 모두 적용된 경우 AO 렌더타겟을 50%(가로세로를 절반으로 줄여서 1/4 크기)로 한 경우도 Noise 가 크게 눈에 안 띄는 것을 볼 수 있습니다.
그림14. RTAO 영상 (출처 : 직접구현)
2560x1369 (대략 2K) 에서 AO Resolution 을 변경해 보면서 성능을 측정해 보면 아래 그림15와 같습니다. 여기서 표시하는 비율은 가로, 세로 각각에 곱해지는 비율입니다. 즉, 50% 면 가로세로 2배 줄어들기 때문에 최종 해상도는 1/4 배로 줄어듭니다.

4. 실제 구현 코드
아래 경로에서 실제 구현코드를 받아 실행해 볼 수 있습니다. DirectX12, Vulkan 중 선택하여 실행 가능합니다.
https://github.com/scahp/jEngine/tree/RTAO
5. 레퍼런스
1. https://cwyman.org/code/dxrTutors/tutors/Tutor5/tutorial05.md.html
2. https://simonstechblog.blogspot.com/2019/09/dxr-ao.html
3. https://www.youtube.com/watch?v=-LD9MxBUFQo
4. https://www.youtube.com/watch?v=7FP7ndMEfsc
5. https://developer.nvidia.com/downloads/ray-tracing-games-nvidia-rtx-pdf
6. https://youtu.be/yag6e2Npw4M
7. https://scahp.tistory.com/77 (원문 : https://sugulee.wordpress.com/2021/06/21/temporal-anti-aliasingtaa-tutorial/)
'Graphics > Graphics' 카테고리의 다른 글
| PathTracing (1/2) (1) | 2024.03.01 |
|---|---|
| AsyncCompute - DX12, Vulkan (0) | 2024.02.13 |
| Bindless Resource - DX12, Vulkan (0) | 2024.01.17 |
| DX12 Shader Visible Descriptor Heap (0) | 2023.08.05 |
| BCn Texture Compression Formats 정리 (0) | 2023.04.21 |