normal map compression

반응형

보통 게임에서는 메모리 용량 때문에 텍스쳐를 압축해서 많이 사용하는데

pc에서는 특히 dds 파일을 많이 사용한다.

dds 파일 포멧을 많이 쓰는 이유는 "DXT 압축" 링크를 참조하면 된다.


그런데 문제는 DXT 압축 알고리즘이 노멀맵을 염두에 두고 설계한 것이 아니기 때문에 노멀맵에

DXT 압축을 사용하는것은 퀄리티에 심각한 결점을 부과해 준다는 것이다.


그래서 노멀맵은 어떻게 해야할까?

그렇다면 어떻게 해야 할까? dxt의 그 가성비를 포기해야 하는가?

다른 맵과는 다른 노멀맵의 특성을 이용하면 약간의 편법을 이용할 수 있다.

우선 노멀맵은 다른 텍스쳐들(diffuse, specular, emissive etc..)처럼 색의 정보를 저장하는게 아니라

기울기 정보, 즉 방향정보를 저장하고 있다.

이 기울기 정보를 저장하기 위해 기울어진 면에서 세개의 수직인 축을 저장하는데 이게 노멀맵이다.


여기에 DXT5 압축 알고리즘을 어떻게든 노멀맵에 적용시도를 해보자면...

dxt5의 경우 R5G6B5A8 픽셀포멧이며 unity에서 pc환경에서 노멀맵은 내부적 dxt5nm형식을 사용한다.

이 포맷은 dxt5와 형식이 같지만 압축하기 전에 red를 alpha로 옮기고 green은 그대로,

red와 blue는 단색으로 비우는데 이 작업을 보통 swizzling 라고 한다.

이는 데이터 손실을 최소화 하기 위함인데 dxt5 포맷의 바이트 배분이

5:6:5:8 (R:G:B:A) 이기 때문이다.

알파채널과 그린채널을 보면 Red나 Blue에 비해 비트수가 더 높은 것을 알 수 있다.

그래서 압축 전에 R값을 A로 옮겨서 데이터 손실을 최소화 하는 것이다.


UnityCG.cginc 파일을 보면 아래와 압축된 노멀맵을 압축해제하는 함수가 있다.

inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)

{

fixed3 normal;

normal.xy = packednormal.wy * 2 - 1;

normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));

return normal;

}


첫번째 연산을 보면 노멀의 w(a)와 y(g)를 0~1 범위를 -1~1범위로 바꾸는 작업을 한다.

w에는 r값을 넣었고 y에는 g값을 넣어서 압축했으므로 두 채널의 값만 압축해제하면 된다.


그 후 x와 y값으로 normalized 된 normal 벡터의 z값을 복원하면 된다.

수직벡터 구하는 방법에 대한 내용은 아래 링크를 참고하면 된다.


그런데 왜 R과 B채널을 쓰지 않는것 만으로 압축품질이 더 좋아지는 걸까?

이는 DXT 압축이 이미지의 각 4x4블록에 대해 두 개의 기본 색상만 선택하여 수행되기 때문이다.

만약 블럭의 모든 색상이 이 두 끝점 사이의 선을 따라 있으면 압축 품질이 좋다.

최악의 아티팩트는 단일 블럭에 RGB공간을 통해 분산된 색상이 포함되어 있을 때 발생하므로 

하나의 선을 통과 할 수 없다.

b채널을 버림으로써 3차원 RGB 색상 곤간을 2차원 R/G공간으로 축소한다.

이렇게 하면 블럭의 모든 색상에 적합한 단일 선의 확률이 높아져 압축 퀄리티가 좋아진다.

더 나은 품질을 위해 위에서 설명한 대로 비트수가 높은 알파채널과 G채널을 사용하면 더 좋다.

이렇게 하면 압축에 대한 걱정거리가 하나의 색상 차원으로 남게되어 모든 블록이 단일 라인을 따라

잘 맞을 것이다.

그리고 그냥 DXT1 텍스쳐에 RG를 쓰고 B를 0으로 밀어버리는 것 만으로 pixel 연산을 희생양 삼아

퀄리티가 좀 더 좋아 질 수도 있다.

이는 정밀도는 같지만 압축 알고리즘상 색상 매칭 확률을 높이니 조금 더 부드러워 보일 수 있다.

이에 대한 내용은 이곳을 참조 하면 된다.



언리얼의 V8U8과 DXTnm은 같은가?

자료가 많지 않아서 잘 모르겠지만 DXTnm은 기본적으로 DXT5의 포맷을 사용하고,

V8U8과 같이 수직축 계산한다는 건 동일하지만 DXT5는 5658로 기본적으로 4채널을 사용하고

높은 비트로 채널을 옮기며, 안쓰는 채널을 0으로 하여 압축시 매칭비율을 높이는 방식을 사용한다.

즉, 정밀도를 조금 늘리기 + 수직축 계산하기 + 압축하기 의 작업인듯 하다.

V8U8은 그냥 처음부터 높은 정밀도로 2채널만 사용하고 압축은 하지 않으며 밉맵을 하나 낮춘다.

즉, 정밀도 많이 올리기 + 수직축 계산하기 + 용량 줄이기 위해 밉맵 낮추기.

V8U8은 알파와 휘도를 저장하기 위한 A8L8의 signed 버전인듯 하다.


그렇다면....

노멀맵을 저용량에 고퀄로 쓰고싶다.

-> DXT1 압축을 해보자, 저용량이지만 퀄리티가 망이다.

-> DXTnm 압축을 해보자, 저용량은 아니지만 퀄리티는 좀 좋아진다.

-> V8U8 비압축을 해보자, 저용량에 고퀄이지만 저용량으로 하기위해 해상도를 확대하면 

    쨍한 부분에서 계단현상이 생기긴 한다.


목표는....

V8U8의 용량과 고퀄을 유지하고 해상도에 대한 아티펙트를 줄여보자!!

이에대한 아이디어는 나온게 꽤 많으니까 하나씩 적용해 보자!!



https://www.garagegames.com/community/blogs/view/22776



Reference Link

- Normal, Tangent, BiTagent Vector 그리고 Unity Normal Map

- dxt 압축

- 디더링

- 픽셀 셰이더 프로그래밍


- 유니티 엔진의 노말맵은 노랑색? 흑백? 파랑색?

DXT compression for normalmaps

- 수직벡터 구하는 방법

- unreal, V8U8

- Texture Compression Techniques and Tips

Real Time DXT Compression.pdf

ati NormalMapCompression.pdf

- nvidia, Real Time Normal Map DXT Compression

- unreal, 노말맵 제작기법

- normal editing tip


'Study > Graphics ' 카테고리의 다른 글

Blur 1  (0) 2016.10.12
UV Texture Coordinates and Texture Mapping - OpenGL / DirectX  (0) 2016.08.14
Semantics  (0) 2016.05.09
normal mapping artifacts  (0) 2014.12.10
tangent space, 접선 공간  (0) 2014.04.07
TAGS.

Comments