
Custom normal texture encoding function creation How to.
모바일 게임을 제작 하다 보면 여러가지 문제점을 만나게 된다.
그 중에서 하나 대표적인 것인 CPU 에서 빈번하게 발생 하게 되는 Draw-Call 이외에도 Texture fetching 이 빈번하게 발생 하는 부분이다.
최적화를 하다 보면 경력자 아티스트도 이 부분을 어느정도 들은 적이 있기 때문에 최대한 Sampler 를 많이 만들지 않으려고 노력 한다.
하지만 근본적인 원인은 잘 알고 있지 않다.
아무튼 하드웨어 적인 메카니즘에 대한 이야기를 하려는 것은 아니다.
이런 이야기를 이해 한다는 것은 내가 생각 할 때 굳이 내 글을 볼 필요가 없는 수준에 이미 도달 한 숙련자 일 것이기 때문이다.
어쨌든 Sampler 를 최대한 소량으로 선언 하는 것이 좋다는 것만 일단 알고 있자.
보통 Sampler2D 또는 tex2D 는 자주 봤을 것인데 이것은 CPU 에서 Texture Fetching 을 요구 한다.
결국 Draw-call 처럼 직렬 처리 되는 Texture Fetching 의 빈도가 높다는 것은 그렇게 기쁜 소식은 아닐 것이다.
피부 셰이더를 개발 하거나 기타 조금 복잡한 셰이더를 만들 경우에 한 장 이상의 노말맵을 사용 해야 하는 경우는 꼭 발생한다.
결국 두 장의 노말맵을 어떻게 한 장으로 묶어 줄 수 있는가에 대한 의문을 갖게 될 것이다.
수학적인 처리 방법은 그다지 관심이 없을 듯 하기 때문에 본론으로 바로 들어가서 어떻게 구현 할 수 있는지 함께 살펴보자.
또한 이 내요을 좀 더 쉽게 풀어가기 위해서 나는 Amplify shader Editor 를 사용 했다.
URP 에서 같은 내용을 다룰 것이지만 지금은 일단 이 내용을 충실하게 살펴보도록 하자.
노말맵핑이 뭔지 설마 모르지 않겠지만 그래도 여기에 더 자세한 정보가 담겨 있다.
굳이 노말맵이 더 궁금하다면 전직 렌더링 프로그래머였던 포프님의 유투브 체널을 시청 해 보자.
영상을 볼 때 노말맵의 Y 체널에 대한 이야기가 언급 되는데 이 부분은 좀 더 분명하게 이해 해야 할 필요가 있는 것 같다.
영상으로 포프님이 만들다 보니 설명이 조금 터프 한 느낌이다.
왼손 좌표계 오른손 좌표계 까지 이야기를 하는 것은 너무 복잡한 내용이 될 수 있기 떄문에 간단히 정리 해 보자.
다이렉트 엑스 렌더링과 오픈지엘 렌더링의 경우 UV 의 원점 위치가 일단 다르다.
결국 이게 범인이다.
다시 본론으로 돌아 가자.
Unity3D built-in Shader 코드를 참조 하자.
URP Core 의 ShaderLinrary/Packing.hlsl 을 참조 했다.
// Unpack from normal map
real3 UnpackNormalRGB(real4 packedNormal, real scale = 1.0)
{
real3 normal;
normal.xyz = packedNormal.rgb * 2.0 - 1.0;
normal.xy *= scale;
return normalize(normal);
}
위 두 함수를 참조 하여 코드를 목적에 부합 하도록 수정 하고 새로운 함수를 추가 하자.
추가 한 새로운 함수는 Amplify Shader Editor 의 Custom Expression 을 사용 하여 테스트 해 볼 것이다.
분명한 것은 이 새로운 함수의 목적이다.
1. 두 장의 노말맵을 한장의 노말맵으로 합쳐서 한번의 Set texture 만 발생 하도록 하자.
2. 모바일 게임용의 최적화 기본 방안을 생각하자. 예로 들어서 ALU 를 최소화 하기 위한 Approximation 에 대한 편차 있는 결과에 대해서 너그럽게 받아들이자.
두 개의 함수 형태로 재구성 했다.
JP_UnpackNormalRG_SafeNormal
inline float3 JP_UnpackNormalRG_SafeNormal( half2 normalXY )
{
half3 normal;
normal.xy = normalXY.xy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return normalize(normal);
}
JP_UnpackNormalRG_SafeNormal_Optimal
inline float3 JP_UnpackNormalRG_SafeNormal_Optimal( half2 normalXY )
{
return normalize(half3(normalXY.xy * 2 - 1 , 1));
}


두 함수를 시각적으로 Debug 하기 위해 TransformDirection 을 사용 하고 비교 했다.
위 두 식을 컴파일 하고 ALU 수량도 비교 해 보자.
Disassemble code 상에서 명령어 수량은 3개 차이가 있다.
JP_UnpackNormalRG_SafeNormal 의 Disassemble code block.
// SV_Target 0 xyzw 0 TARGET float xyzw
ps_4_0
dcl_constantbuffer CB0[5], immediateIndexed
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_input_ps linear v1.xy
dcl_output o0.xyzw
dcl_temps 1
0: mad r0.xy, v1.xyxx, cb0[4].xyxx, cb0[4].zwzz
1: sample r0.xyzw, r0.xyxx, t0.xyzw, s0
2: mad r0.xy, r0.xyxx, l(2.000000, 2.000000, 0.000000, 0.000000), l(-1.000000, -1.000000, 0.000000, 0.000000)
3: dp2 r0.w, r0.xyxx, r0.xyxx
4: min r0.w, r0.w, l(1.000000)
5: add r0.w, -r0.w, l(1.000000)
6: sqrt r0.z, r0.w
7: dp3 r0.w, r0.xyzx, r0.xyzx
8: rsq r0.w, r0.w
9: mad o0.xyz, r0.xyzx, r0.wwww, l(0.000010, 0.000010, 0.000010, 0.000000)
10: mov o0.w, l(1.000000)
11: ret
// Approximately 0 instruction slots used
JP_UnpackNormalRG_SafeNormal_Optimal 의 Disassemble code block.
// SV_Target 0 xyzw 0 TARGET float xyzw
ps_4_0
dcl_constantbuffer CB0[5], immediateIndexed
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_input_ps linear v1.xy
dcl_output o0.xyzw
dcl_temps 1
0: mad r0.xy, v1.xyxx, cb0[4].xyxx, cb0[4].zwzz
1: sample r0.xyzw, r0.xyxx, t0.xyzw, s0
2: mad r0.xy, r0.xyxx, l(2.000000, 2.000000, 0.000000, 0.000000), l(-1.000000, -1.000000, 0.000000, 0.000000)
3: mov r0.z, l(1.000000)
4: dp3 r0.w, r0.xyzx, r0.xyzx
5: rsq r0.w, r0.w
6: mad o0.xyz, r0.xyzx, r0.wwww, l(0.000010, 0.000010, 0.000010, 0.000000)
7: mov o0.w, l(1.000000)
8: ret
// Approximately 0 instruction slots used
또한 결론적으로 두 장의 노말맵을 한번에 모아서 처리 하고 있기 때문에 더 많은 명령어도 절약 된 결과가 되었다.
또는 여러 다른 텍스처 셋트가 요구 될 때 NormalMap 의 B 와 A 체널에 다른 텍스처 정보를 기록 할 수도 있다.
최적화 함수의 경우 z 에 대한 부분을 단순히 고정상수로 정의 했기 때문에 Pixel normal 의 각 pixel 당 계산도 최소화 한 것이 된다.
그리고 두 번의 Set texture 처리를 한 번의 Set texture 로 처리 했기 때문에 CPU 병목지점에 대한 최적화도 수행 한 결과가 된다.

카테고리:tutorials