일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- URP로 변경
- Specular
- Private Bytes
- 개인 바이트
- URP
- Rim Light
- C언어
- 프로그래밍 기초
- Virtual Byte
- Toon Shader
- Three(Two) Tone Shading
- 게임 수학
- 벡터
- 작업 집합
- ColorGradingLutPass
- Cell Shader
- VR
- ASW(Application SpaceWarp)
- 가상 바이트
- Cartoon Rendering
- Windows Build
- 3d
- working set
- OculusMotionVectorPass
- Cell Look
- 메모리 누수
- AppSW
- Today
- Total
WinCNT
Scale을 늘리면 Normal값이 계속 커지던 이슈 본문
발생한 이슈
커스텀 셰이더에서 Scale이 커지니 하이라이트가 지저분해지는 이슈가 발생했다
원인 분석을 위해 파라미터들을 하나씩 확인해보니 Scale이 커짐에 따라 Normal 값이 이상해진다는 사실을 알게 되었다
이슈 상세
// Normal Map 적용이 끝난 후의 Normal
return normal;
return normal * 0.5 + 0.5;
프래그먼트 셰이더에 위와 같은 return 값을 둬서 확인해봤다
그러자 다음과 같이 스케일이 커짐에 따라 노멀 값이 1을 넘어가서 Bloom이 되고 있었다!
발생 원인이 아니었던 것 - 역전치행렬 관련
처음에 떠오른 것은 비균등 스케일로 인해 법선이 무너지는 현상이었다
(노멀에는 월드 행렬의 역전치행렬을 곱해야 한다는 그거)
하지만 균등 스케일인 경우에도 해당 이슈는 발생했어서 후보에서 제외했다
발생 원인 - 단순한 정규화 안 함
그래서 소스 코드를 확인해봤다
o.bi_normal = normalize(cross(v.normal, v.tangent.xyz) * v.tangent.w * unity_WorldTransformParams.w);
o.normal = TransformObjectToWorldNormal(v.normal);
o.tangent = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0))).xyz;
o.bi_normal = mul(unity_ObjectToWorld, float4(o.bi_normal.xyz, 0)).xyz;
결론부터 말하자면 원인은 bi_normal(Bitangent)에 있었다
현재 코드는 Object Space의 노멀과 탄젠트로 bi_normal(Bitangent)를 구한 뒤, 거기에 월드 행렬을 곱해서 World Space에서의 Bitangent를 구하고 있다
왜 이렇게 했냐고…?
그야…재밌으니까필자가 코딩한 부분이 아니라 모르겠다!
아무튼 월드 행렬의 스케일에 대한 대비가 없었기 때문에 해당 이슈가 발생하고 있는 것이었다
최소한 Bitangent를 계산하고 나서 정규화(Normalize)를 해줬어야 했다
해결법
잠깐 상술했던 대로 최소한 SafeNormalize 등으로 정규화(Normalize)해주면 해당 이슈는 해소된다
TransformObjectToWorldNormal나 TransformObjectToWorldDir 등을 대신 써도 해소되는 것으로 보이긴 했다
하지만 모처럼 월드 공간에서의 노멀과 탄젠트를 계산했으니 그걸로 바이 탄젠트로 계산하면 되는 이야기이다
그것보다 더 좋은 것은 Unity에서 사용되는 함수를 갖다 쓰는 것이다
URP의 Lit.shader를 분석해보니 GetVertexNormalInputs란 함수를 쓰고 있길래, 커스텀 셰이더도 그걸로 수정했다
VertexNormalInputs normalInput = GetVertexNormalInputs(v.normal, v.tangent);
o.normal = normalInput.normalWS;
o.tangent = normalInput.tangentWS;
o.bi_normal = normalInput.bitangentWS;
참고로 GetVertexNormalInputs의 내부는 다음과 같았다
VertexNormalInputs GetVertexNormalInputs(float3 normalOS, float4 tangentOS)
{
VertexNormalInputs tbn;
real sign = real(tangentOS.w) * GetOddNegativeScale();
tbn.normalWS = TransformObjectToWorldNormal(normalOS);
tbn.tangentWS = real3(TransformObjectToWorldDir(tangentOS.xyz));
tbn.bitangentWS = real3(cross(tbn.normalWS, float3(tbn.tangentWS))) * sign;
return tbn;
}
TransformObjectToWorldNormal랑 TransformObjectToWorldDir 내부에서는 SafeNormalize로 정규화를 하고 있으니, 정규화된 노멀과 탄젠트로 계산한 바이 탄젠트도 정규화된 벡터!!
덤으로 Negative Scale에 대해서도 대응해주는 것 같아서 안심이다(이 부분은 자세히 확인 안 했지만ㅎㅎ;;)
아무튼 이걸로 해결!
마무리
시작은 하이라이트가 이상하다였으니 새삼 Normal의 중요성을 깨닫게 된 이슈였다
참고 자료
http://www.jp.square-enix.com/tech/library/pdf/RigAndTools_ScaleRig.pdf