애니메이션 작업준비 #2

12.24

메리크리스마스! 작업하자.

셰이더 컨버팅 중 문제가 생겼다. 앤젤링의 강도는 엔진마다 차이가 날 수는 있으나, 위치가 다른 것은 문제다. 왜 이런 현상이 일어나는지 조사해야 한다.

가장 먼저 의심된 것은 UV의 컨버팅 여부인데, 이것은 정상이었다. 컬러그리드맵을 씌워본 결과 모든 UV는 정상이다.

앤젤링의 연산에 sin함수를 사용하고 있는데, 이것이 블렌더와 다를 수 있다. 해서 살펴봤지만 이것도 정상이다.

원인을 알아냈다. v의 문제였다. 앤젤링은 뷰에 따라 uv를 offset해주는 해주는 방식을 사용하고 있는데, 엔진의 uv와 블렌더의 uv가 서로 다른 문제가 있다. 블렌더는 아래가 0, 엔진은 위가 0이다. 따라서 v를 움직이려면 부호를 바꿔주어야 정상작동한다. 이건 언리얼도 마찬가지인데, 입이나 눈처럼 uv를 오프셋해주어야 하는 부분에서 특히 까다롭다.

앤젤링의 컬러는 블렌더에선 헤어컬러를 좇게 했다가 엔진에선 커스텀으로 지정할 수 있게 하였다…가 다시 헤어컬러를 따라가도록 변경했다. 하지만 이번엔 엔젤링의 강도가 문제였다. 이를 찾기 위해 셰이더코드를 여기저기 뒤진 끝에, 이건 그냥 텍스처의 문제란 것을 발견했다.

앤젤링은 이런 바코드 텍스처(아휴 작아)를 사용하고 있는데 유니티에선 이것을 Single-Channel로 지정해 사용한다. 채널이 하나라면 자동으로 Linear컬러로 계산되고, 리니어 컬러는 감마처리를 하지 않았기 때문에 사람의 눈으로 볼 땐 좀 더 밝게 보인다. 이를 위해 블렌더에서도 sRGB와 Linear를 정의할 수 있는 프로퍼티를 제공하는데, 이것을 sRGB로 둔 것이 원인이었다.

컬러스페이스를 리니어로 바꾸면 이제 유니티와 비슷해진다.

문제는 sRGB 버전이 더 마음에 든다는 것이다! 이를 위해 포토샵에서 이미지를 어둡게 조절해 봤지만 결과가 약간 다르다.

전보다는, 좀 더 부드러운 느낌이 든다.

이제 얼굴 셰이더를 만들자. 일다나 스킨셰이더를 복사해서 넣어보았다. 아이고 무셔…

얼굴은 예외적인 셰이더 투성이다. 눈, 눈썹, 입, 얼굴 셰이더가 모두 다르고 하나하나 다 복잡하다.그 중에서 가장 쉬운 얼굴 셰이더부터 옮겨보자.

얼굴 제작 중 임시로 마스크를 벗겨주려고 했지만 작동이 되지 않는다.

URP에서 알파블렌드말고 알파테스트는 어떻게 가동시키는걸까. 이런저런 자료를 찾아봤는데 HLSL에선 2가지 방법이 가능했다.

clip(-1);                

if (a<0) discard;

그냥 셰이더 모델 바꿔주고 알파값만 지정해주면 될 줄 알았는데, 그게 아니었다. 레거시 셰이더에선 AlphaTest커맨드로 가능하다고 하지만, 내가 사용하는 것은 HLSL이기 때문에 작동하지 않았다. 이걸 알아내느라 몇 시간을 쓴걸까… clip()함수 사용법을 몰라서 한참을 뒤졌다. 0보다 크거나 같으면 출력, 작으면 폐기된다.

어쩐지 렌더타입과 큐와도 관계가 없다. 알파테스트가 아니라 오파쿠여도 작동된다. 뭐 내부가 어떻게 돌아가는 건지 하나도 모르겠어. 이런 점들이 셰이더 공부에 난이도를 더한다. 정보가 매우 파편화되어 있어 검색해도 그게 답이 아닐 때가 많다.

타임오버. 얼굴이 아직 끝난 게 아니라는 것이 문제다.

12.25

얼굴은 마무리를 했다. 눈썹이 빨갛게 나오는 문제가 있는데 이건 데이터 문제이니 나중에 손보면 될 것이다.

입셰이더를 컨버팅을 했는데 한 번에 잘 작동할 리는 없다… 타임오버이니 여기서 끊고 커밋을 해두자.

12.26

입 셰이더를 짜려고 유니티를 열었는데, 블렌더에 비해 홍조가 너무 붉게 나오는 것이 눈에 띄었다. 그러고보니 눈썹부분도 너무 붉게 나오는 것이 수상하다.

홍조는 버텍스 컬러의 R채널의 정보를 받는다. 눈으로 봐도 꽤나 차이가 난다. 왜 이런 차이가 날까? 처음엔 float와 half의 정밀도 차이인가 싶었는데, 아차! 컬러스페이스!

블렌더의 버텍스컬러의 노드를 받아오는 부분은 감마가 자동으로 계산되는 것 같다. 리니어 컬러스페이스는 인간의 눈으로 볼 때 흰색이 좀 더 부각되어 보인다. 이를 맞추기 위해선 2.2를 제곱해야 한다. 따라서 버텍스 컬러를 받아오는 이 코드는

half4 colorMask = IN.color;

이렇게 수정되어야 한다.

half4 colorMask = pow(IN.color, 2.2);

이제야 제대로 된 색이 출력된다. 참고로 다시 리니어 스페이스로 돌리는 건 0.45제곱을 하면 된다.

하지만 컬러는 어차피 버텍스 단위로 계산되니, 프래그먼트보단 버텍스에서 하는 게 조금이라도 더 좋을 것 같다. 따라서 코드를 버텍스 셰이더쪽으로 옮긴다.

OUT.color = pow(IN.color, 2.2);

하지만 이 코드는 블렌더와 완벽히 호환되지 않는다. a는 언제나 리니어 컬러 스페이스로 계산되어야 한다. 따라서

OUT.color.rgb = pow(IN.color.rgb, 2.2);

OUT.color.a = IN.color.a;

이렇게 따로 변환해주어야 한다.

이제 완전하게 변환된다. (하지만 전혀 티가 안난다….)

하지만 컴파일창에 경고가 떠있다. pow함수는 음수가 지원되지 않는다고 한다. 물론 color는 음수를 사용할 수 없지만, 에러를 막기 위해 절대값변환을 해줄 필요가 있다.

OUT.color.rgb = pow(abs(IN.color.rgb), 2.2);

이제 문제없을 것이다. (아마도?)

입셰이더를 컨버팅하다가 알게 된 충격적인 사실. 유니티의 UV.v의 원점 위치도 블레더와 같이 아래에 있다. 언리얼만 위에 있는 것인가.

입의 셰이더도 완료. 다음은 눈.

1차 구현. 곤충이 되어버렸어!

곤충눈 현상은 텍스처 설정이 Repeat이기 때문에 나타나는 현상이었다. Clamp로 바꾸면 해결된다. 코드는 죄가 없다.

눈까지 완료. 하지만 블렌더와는 다르게 유니티에선 한가지 셰이더를 더 만들어야 한다. 바로 눈썹셰이더이다.

미루어놓았던 문제는 반드시 말썽을 일으킨다. 눈썹이 그렇다. 눈썹의 외곽선을 어떻게 처리할 지 고민을 하다가 부피가 크지 않아서 미루어 놓았었는데 결국 역풍을 맞았다.

그래도 다행인 것은 블렌더의 셰이프키 시스템이 어지간해선 말썽을 부리지 않는다는 것이다.

윤곽선을 표현하기 위해 이렇게 눈썹의 토폴로지를 다르게 변경해도, 별다른 말썽을 일으키지 않는다. 조금만 수틀려도 모든 데이터를 일그러뜨리는 맥스의 모핑시스템은…아휴, 말을 말자.

하나 더 다행인 것은 얼굴데이터 이전 스크립트를 미리 만들어 놓았었다는 것이다. 이 스크립트는 얼굴데이터에 연결된 머티리얼과 셰이더에 연결된 드라이버를 원래 캐릭터의 정보로 바꿔준다. 수동으로 하자면 하루는 족히 걸릴 작업을 버튼 한번으로 끝낼 수 있다. 고마워. 과거의 나!

12.27

얼굴 노말을 위한 버텍스 그룹을 지정할 때 WeightTool을 사용하면 다른 그룹을 건든다. 애드온의 버그인 것 같아 보인다.

아이라인 화장을 위한 마스킹을 버텍스 그룹으로 지정해두었는데, 이것이 깨지는 현상이 있었다. 편집하는 그룹 외에 다른 그룹은 꼭 잠가둘 것.

얼굴의 명암이 지저분하게 나오는 현상이 있어서 이를 조사했다. 원인은 커스텀 노말 계산부분의 노말라이징.

부드러운 얼굴 셰이딩을 위해 구형 노말과 원래 노말을 섞어서 쓰고 있는데, 구형노말을 계산할 때 노말라이즈를 빼고 있었다. 이는 의도적인 것이었는데, 어차피 픽셀셰이더로 넘긴 후엔 노말라이즈 해줄테니 버텍스셰이더에서 해줄 필요 있을까? 싶었지만 이것이 잘못된 생각이었다.

원인은 일찍 찾았는데 이걸 위해서 또 얼굴 데이터를 한바탕 청소했다. 좋아. 하는 김에 회색지대에 있던 작업들을 끝내버리는 편이 어떨까.

이것 또한 미루어왔던 작업이다. 얼굴은 데이터의 복잡도로 인해 틀이 잡히기 전까진 외곽선을 그리지 않고 있었다. 턱선을 그어주는 것과 그렇지 않은 것은 분명한 차이가 있어 보인다.

윤곽선은 여러개의 셰이프키는 필요하지 않지만, 그래도 고작 얼굴 실루엣 잡아주는데 이 정도 데이터를 쓰는 건 좀 낭비같다는 생각은 든다. 하지만 초심을 유지하기로 하자. 퀄리티를 낼 수 있다면, 어떤 비효율도 감수하기로.

다시 눈썹으로 넘어가고 싶은데 타임오버. 눈썹은 최종적으로 머리카락 위에 그려져야 한다. 큐를 어찌어찌하면 된다고 들었는데… 지금은 방법을 모르겠다. 스샷은 느낌을 보기 위해 ZTest를 건너뛰어 출력한 것이다. 그 어떤 오브젝트가 가리든 출력하기 때문에 사용할 수 있는 방법은 아니다.

…자러 가려고 했는데 문득 생각이 나서 테스트. 오.. 됐다. 렌더큐는 그리는 순서를 말하는 것인데, 이걸 아무리 정렬해봤자 작동하지 않는 게 당연하지[…] 일단 헤어의 렌더큐를 2낮춘다. 그 다음 눈썹의 ZTest를 Always로 그린다. 물론 다른 오브젝트위에 출력되어야 하니 ZWrite는 켜두어야 한다.그리고 헤어보다 높은 큐를 할당하면 된다. 즉 렌더순서는 헤어 -> 눈썹 -> 기타 셰이더가 된다.

그런데.. 이거 낮춰도 되나…높이는 건 많이 봤는데 낮추는 건 못봤는데… 일단 되니까 넘어가기로 하자.

애니메이션 작업준비 #2”의 4개의 생각

  1. In Blender, EEVEE does not correctly display the transparency using “alpha blend”. It usually causes a z buffer fight, and sometimes it will display the transparency, and other times it will invert the transparency with the object behind. How did you solve this issue in Blender with the mouth setup?

    Also, in all the replies I’ve attached a link, your blog automatically deletes my question post. Please check to “allow links in comments under review” so I can share the links for the youtube videos showing specific casxes, bug reports from blender and blog links related to the subject. Thank you!

    1. 실시간 렌더링에서 알파블렌드의 정렬문제를 해결하는 사람은 노벨상을 주어야 합니다! 어서 구원자가 나타나면 좋겠어요.
      전 대부분의 머티리얼에 Opaque를 사용하고 있어요. 잘 아시다시피, AlphaBlend끼리만 겹치지 않으면 큰 문제가 없습니다. 캐릭터 머티리얼 중 AlphaBlend를 사용하는 경우는 입 부분에 사용하는 머티리얼뿐이예요. 나머지는 모두 Opaque입니다.

      댓글이 지워지는 것은 몰랐네요. 미안해요. 블로그의 세부설정엔 크게 신경쓰지 않았거든요. (이것도 기본 스킨 중 하나일 뿐이죠) 일단 링크는 4개까지 포함할 수 있도록 설정을 바꿨습니다. 하지만 스팸란을 보니, 링크가 없는 글들도 스팸처리되어 있네요. 아마 댓글 시간 간격이 너무 빨랐던 것 같아요. 워드프레스의 댓글시스템은 정말 빡빡하네요.

이즈님에게 덧글 달기 응답 취소