본문 바로가기

Unreal Engine

Unreal에서의 NaN / Inf 대응

Unity Manual의 내용을 참고해서, Unreal에서도 렌더링 과정에서 Nan 또는 Inf 값이 생성되는 지 확인할 수 있게 구현합니다.

https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@10.1/manual/Post-Processing-Propagating-NaNs.html

 

Post-processing: propagating Not a Number or Infinite values | High Definition RP | 10.1.0

Post-processing: propagating Not a Number or Infinite values Not a Number (NaN) and Infinite (Inf) values occur when shader operations produce an undefined result. Visually, they manifest as purely black or white pixels. Example operations that can lead to

docs.unity3d.com

 

NaN(Not a Number) 또는 InF(Infinite) 값이 생겼을 때 undefiend result 가 나타날 수 있다. (검정이나 흰색으로 표현된다)

이를테면 아래와 같은 상황

  • log/ log2의 square root를 실행했을 때
  • modulo 연산을 수행했을 때(A%B) : A는 무한대이고, B는 0일 때 Nan이 발생
  • 어떤 수를 0으로 나누었을 때 (예를 들어, 벡터를 길이 0으로 normalize 했을 때)

셰이더 연산 뿐만 아니라 초기화되지 않은 메모리도 Nan이나 Inf를 생성, 포함하고 있을 수 있다. 어떤 플랫폼에서는 렌더타겟이 새로 생 성될 때, 값이 0으로 초기화하지 않는다 - 렌더타겟을 새로 생성하고나서 write 하거나, clear 하지 않은 경우 NaN이나 InF 값을 생성할 수 있다는 것.

→ 일반적으로 렌더 타겟 쓸 대는 항상 clear 해야 함. 또는, 렌더 타겟을 읽기 전에 미리 원하는 값을 렌더타겟에 write 해야 한다.

 

Nan / InF 값의 확산

NaN 또는 InF를 피연산자로 하는 모든 연산은 NaN 또는 INF를 결과로 생성할 수 있다. 이 값이 Bloom 으로 확산되면 더 많은 픽셀로 확산될 수 있다. (bloom 이 scene color를 downsample 한 다음, 다시 UpSample 하는데, DownSample 하는 과정에서 화면 전체로 이 값이 퍼져나간다)

Screen Space Reflection을 수행하거나, Screen Space Refraction, Distortion을 수행할 때 컬러 피라미드를 생성하는 과정에서도 같은 이슈가 생긴다. 해당 효과들이 NaN / InF값을 생성하는것이 아니고, 이미 생성된 값을 넓은 영역으로 퍼트리는 것이다.

 

해결법

NaN / InF 값의 원인을 찾아 고쳐야 한다.

유니티의 HDRP Camera는 NaN와 InF 값들을 검은 픽셀로 교체해주는 기능이 있디 (Stop NaN).

그런데 이것은 꽤 리소스를 잡아먹는 기능이다. 또는 전용 Debug 모드로 시각화할 수 있다.

또는 RenderDoc과 같은 툴을 활용해서 문제가 되는 부분을 찾아낼 수도 있음.

 

 

렌더독으로 확인하는 법

프레임을 캡쳐한 다음, 텍스처 뷰어에서 Overlay 드롭다운 메뉴에서 NaN/Inf/-ve Display 선택.

 

Unity의 NanTracker Debug Mode

HDRP Project에서 Window > Analysis > Rendering Debugger 의 Rendering 탭에서, Rendering 탭의 FullScreen Debug Mode 를 보면 이런 저런 풀스크린 디버깅 모드를 선택할 수 있다.

 

이 디버그 모드의 포스트프로세스 셰이더 내용은 아래 파일에 포함되어 있다.

 

프로젝트 경로\Library\PackageCache\com.unity.render-pipelines.highdefinition@14.0.4\Runtime\Debug\DebugFullScreen.shader

 

파일의 내용을 살펴보면, fragment shader의 내용은 다음과 같다.

 

if (_FullScreenDebugMode == FULLSCREENDEBUGMODE_NAN_TRACKER)
 {
 	float4 color = SAMPLE_TEXTURE2D_X(_DebugFullScreenTexture, s_point_clamp_sampler, input.texc
 	if (AnyIsNaN(color) || AnyIsInf(color))
 	{
 		color = float4(1.0, 0.0, 0.0, 1.0);
 	}
 	else
	{
 		color.rgb = Luminance(color.rgb).xxx;
	}
	return color;
 }

Scene Color중 Nan이나 InF값이 있는지 AnyIsNaN() 함수와 AnyIsInF() 함수로 알아보고 있다.

만약 둘중 어느 하나라도 1을 반환한다면, 해당 픽셀은 빨간색 (1.0, 0.0, 1.0) 으로 표시된다.

그렇지 않다면, 화면 색의 Luminance값으로 칠한다. 즉 이미지를 흑백 처리한다.

 

 

AnyIsNan(), AnyIsInF()

Project\\Library\\PackageCache\\com.unity.render-pipelines.core@14.0.4\\ShaderLibrary 의 Common.hlsl에 포함된 함수이다.

 

 

bool IsNaN(float x)
{
     return (asuint(x) & 0x7FFFFFFF) > 0x7F800000;
}

bool AnyIsNaN(float2 v)
{
     return (IsNaN(v.x) || IsNaN(v.y));
}

bool AnyIsNaN(float3 v)
{
     return (IsNaN(v.x) || IsNaN(v.y) || IsNaN(v.z));
}

bool AnyIsNaN(float4 v)
{
     return (IsNaN(v.x) || IsNaN(v.y) || IsNaN(v.z) || IsNaN(v.w));
}

bool IsInf(float x)
{
     return (asuint(x) & 0x7FFFFFFF) == 0x7F800000;
}

bool AnyIsInf(float2 v)
{
	return (IsInf(v.x) || IsInf(v.y));
}

bool AnyIsInf(float3 v)
{
     return (IsInf(v.x) || IsInf(v.y) || IsInf(v.z));
}

bool AnyIsInf(float4 v)
{
     return (IsInf(v.x) || IsInf(v.y) || IsInf(v.z) || IsInf(v.w));
}

화면의 렌더링 된 결과값을 uint 값으로 변환한 뒤, 비트마스크로 절대값으로 변환한다. (abs(x)와 동일)

0x7F800000 은 single precision floating point값에서의 NaN 또는 Infinity를 의미한다. 이 값보다 클 경우, 그 값은 NaN 또는 Infinity 일 수 있 다.

절대값으로 변환한 결과값이 이 값보다 크다면 1을, 작다면 0을 반환할 것이므로, 최종 결과가 1일 경우 이 값은 NaN 이라는 의미이다.

IsInf() 함수에서는 0x7F800000 값과 동일한 지 체크한다. 동일할 경우, Inf라는 의미로 1을 반환하고 그렇지 않으면 0이 된다.

 

Unreal에서의 구현

Post Process Material 노드로 구현해 보았다.

여기에서 나온 결과가 1보다 크거나 같다면, NaN 또는 Inf 값이므로 (1, 0, 0) 빨간색을, 1보다 작다면 현재 SceneColor의 Luminance를 표시 해 준다.