앨리스의 세컨더리 애니메이션 #3

4.16

회전 보간과 리버스키의 알고리즘을 고쳤다. 리버스키는 자식본들의 키를 오프셋할 때, 좀 더 자연스럽게 움직일 수 있도록 0프레임 이전에 역방향 회전을 넣어주는 기능이다. 그런데 이것이 쿼터니온에 대한 이해가 부족할 때 제작한 거라 엉망으로 작동되고 있었다.

며칠 째 버그가 계속 나와서 작업 진행이 더디다. 실무에서는 이것들이 아티스트와 TA간의 연계작업이 되는데, 이 툴을 아티스트에게 던져줬다고 생각하면 끔찍하다.

더 이상의 버그가 나오질 않기를 바라는 마음에서 글을 새로 적었다. 기도합니다. 나무아미타불. 아멘

기도는 기도에서 그쳤다. 또 기능을 수정하고 추가하고…

애드온을 만들었는데도 작업이 꽤 어렵다. 특히 앉기처럼 몸과 다리가 밀착되면 특히 더 어려워진다. 사실 이건 답이 없다. 천하의 마블러스도 몸이 겹치면 구겨지고 난리인데, 본 베이스의 작업은 오죽할까.

4.17

현재는 본과 본사이의 선형 보간을 할 때 euler값을 더해 평균을 구하고 있었다. 그런데 이것이 서 있을 땐 문제가 없는데, 뒤집혀 있을 땐 문제가 됐다. 그래서 오늘도 챗GPT에게 물어봤는데…

이 코드의 문제는 회전값을 오일러 각도로 선형 보간하는 데 있을 것 같아요. 오일러 각도는 주로 한 축을 기준으로 회전하는 경우에만 유효하고, 다중 축 회전에서는 문제가 발생할 수 있어요. 특히 180도 회전과 같은 경우에는 오일러 각도가 다중 해석이 가능하기 때문에 문제가 될 수 있어요.

이 문제를 해결하기 위해선 여러 가지 방법이 있지만, 가장 간단한 방법은 쿼터니언(quaternion)을 사용하는 것입니다. 쿼터니언은 회전을 나타내는 더욱 강력한 방법으로, 다중 축 회전에서도 유효하고 180도 회전과 같은 경우에도 제대로 작동합니다.

다음은 쿼터니언을 사용하여 회전값을 보간하는 코드의 예시입니다:

pythonCopy codeimport bpy
from mathutils import Quaternion

def interpolate(bone):        
    location = [bone.location.x, bone.location.y, bone.location.z]
    scale = [bone.scale.x, bone.scale.y, bone.scale.z]        

    rot1 = side_bones[0].matrix_channel.to_quaternion()
    rot2 = side_bones[1].matrix_channel.to_quaternion()

    # Interpolate quaternions
    lerp_quat = rot1.slerp(rot2, 0.5)

    # Convert quaternion to euler
    lerp_euler = lerp_quat.to_euler()

    bone.matrix = lerp_euler.to_matrix().to_4x4() @ bone.bone.matrix_local
            
    bone.location.x = location[0]
    bone.location.y = location[1]
    bone.location.z = location[2]
            
    bone.scale.x = scale[0]
    bone.scale.y = scale[1]
    bone.scale.z = scale[2]            
            
    bpy.context.view_layer.update()

위의 코드에서 rot1.slerp(rot2, 0.5) 부분은 쿼터니언의 선형 보간을 수행합니다. 이 방법을 사용하면 회전의 중간값을 구할 때 오일러 각도의 문제를 회피할 수 있습니다.

그리하여 오늘의 깨달음. 쿼터니온을 선형보간할 수 있구나! slerp를 사용하면 된다! 챗GPT는 대단해!그아아아(대흥분)

보간 코드는 여러번 말썽을 일으키는 코드였는데 쿼터니온 베이스라면 믿을 수 있을 것 같다. 앓던 이가 빠진 느낌이다.

1차 작업을 완료했다. 이제 엔진에 붙여보자.

엔진에 붙이기 위해 키를 정리해야 한다. 모든 모션은 프레임이 정해져 있기 때문에, 뒷프레임을 짤라낼 필요가 있다. 그렇지 않으면 유니티에서 일일이 숫자를 다시 기입해줘야 한다. 또한 프레임이 달라지면 유니티에서 설정해놓은 이벤트들이 어긋나기 때문에 기존의 프레임을 맞춰주는 편이 좋다. 그렇다면 작업공정이 안정된 지금은 세컨더리 애니메이션까지 한 번에 만들어주는 편이 좋을까…싶다가도 모션이 이상하면 어차피 수정해야 하기 때문에 지금 방식이 맞는 것 같다.

이 또한 기계적인 작업이기 때문에 스크립트에 기능을 추가했다.

짜르기는 현재 선택한 프레임을 기준으로 새 키를 찍고 이전 혹은 이후 프레임을 날려준다. 그런데 이 코드를 작성하며 깨달은 사실. 키를 순차적으로 지우면 리스트가 동적이라 결과가 변한다. 때문에 거꾸로 지워야 한다.

for fcurve in action.fcurves:
        if group_name in fcurve.data_path:
            for keyframe in reversed(fcurve.keyframe_points):
                if ((side == 'left' and keyframe.co.x < frame_current) or 
                    (side == 'right' and keyframe.co.x > frame_current)):
                    fcurve.keyframe_points.remove(keyframe)

for문을 돌 때 반드시 reversed()로 돌도록 하자.

하지만 이 방법도 문제가 있는게, 중간에 있는 키프레임을 지우면 같은 문제가 생긴다. 결국 키프레임을 별도의 리스트로 저장해서 해결해야 한다.

이제 엔진에 적용 중. 닐리의 발 잡기는 앨리스를 위해 만든 것이라고 해도 과언이 아닌 것!

고생한 것 치고는 퀄리티가 성에 차질 않는다. 모션사이가 생각보다 많이 끊겨 보이는데…이것만 보간할 수 있는 방법이 없을까?..

4.17

유니티의 애니메이터는 레이어라는 개념이 있다. 주로 FPS등에서 상하체를 분리해 애니메이션을 조합하는 용도로 사용된다. 이것을 이용하면 되지 않을까? 속성으로 레이어 마스크를 배우고, 블렌딩을 적용해본 결과… 블렌딩 자체가 스테이트가 투명하게 관리되지 않아 오래전에 발생한 문제가 발생했다. FSM을 직접 만들기로 결심했었던 바로 그 문제. 블렌딩이 2-3개 겹치면 모션이 엉망이 된다.

이를 해결하려면 스테이트가 겹치지 않게 하면 된다. 그런데 잠깐…애니메이션은 대체로 격렬한 움직임을 보인다. 애니메이션이 튀는 구간은 거의 모두 Idle로 돌아오는 구간이다. CrossFade()를 Idle에만 적용한다면 어떨까.

성공적! 거의 튀지 않는다.

….인줄 알았는데, 블렌딩이 빠르게 될 때의 시간이 보간될 때 여전히 문제를 일으킨다. 으.어…

결국 CrossFade()를 포기. 조사해 본 바로는 블렌딩을 바로 취소할 수 있는 방법은 없다. 결국 다시 Play()로 돌리고 끊김을 완화하기 위해선 다시 끝프레임을 Idle로 맞춰주는 방법을 택할 수 밖에 없다.

문제가 하나 더 있는데, 잡기에서 Idle로 돌아올 때 방향이 바뀌는 문제이다. 이건 어차피 예전에도 한번 바꾸고 싶긴 했다. 각오하고 해보자.

이 기능은 예전에 한 번 실패했던 오퍼레이터이다. 몸통은 어렵지 않은데 양쪽 팔이 굉장히 번잡시러워서 포기했던 기억이 난다. 구르고 굴러 돌아오니 그냥 데이터 패스를 맞교환해주면 되는 일이었다. 하지만 여전히 손가락이나 IK등은 번잡시럽기 때문에, 큰 줄기만 뒤집어놓고 세부수정을 하는 편이 좋겠다.

이런 저런 삽질 끝에 정한 방향성

  • 애니메이션은 CrossFade()대신 Play()를 사용.
  • 대기로 이어지는 애니메이션의 경우, 맨끝프레임을 Idle의 첫프레임으로 보간
  • 방향이 달라지는 잡기의 반전

데이터가 커지면 작은 변화 하나도 부담되는 작업량이 된다. 차근차근 진행해보자.

블렌더 특성 상 ChildOf컴포넌트가 2개이상 붙은 본은 익스포트 시 프레임 지연이 일어난다. 게다가 IK가 정상작동하지 않는 문제도 있다. 이렇게 되면 결국 파일을 분리하는 것이 속편하다. 잡기용 파일은 별도로 분리해두자.

4.19

세컨더리 작업이 어려울 거라 생각했지만 예상보다 파장이 컸다. 이대로 작업을 진척시키기는 어렵다. 규칙을 확실하게 정립해 두어야 한다.

  • 순차적 키밀기가 마냥 좋지만은 않다. 과유불급. 사용 시 주의하자.
  • 첫 본이 가급적 90도를 넘지 않게 애니메이션 하자.
  • 두번째 본이 충돌에 오작동을 일으키는 경우가 있다. 팔의 컬리전은 사용하지 말 것
  • 짧은 길이는 단순 블렌딩한다. 그걸로 충분하다.
  • 추가로 표정을 애니메이션 하자.

앨리스의 세컨더리 애니메이션 #2

4.12

애드온 보완작업이 끝났으니 이제 실전으로 들어가보자. 역시 들어가자마자 에러가 난다.[…] 고치고.. 또 고치고

이제 에러가 없기를.(그런데 재생속도가 왤케 빠르지…?)

4.13

대기자세에서 앞머리를 휘날릴 때 순차키를 주려고 시도했는데, 본 갯수가 많다보니 1프레임 단위가 너무 크다. 때문에 더 세분화할 수 있는 기능이 필요했다. 결국은 또 애드온을 수정.

에이…스샷으로 봐선 1도 티가 안난다[…] 여튼 작업 완료. 루핑키 적용부분에 버그가 있어서 이것도 같이 수정

여튼 이 기능은 이처럼 최종폴리싱에 쓸 예정

뒤로 걷기를 작업하던 도중, 중복해서 키가 생기는 버그가 있어 코드를 확인해보니, 이럴 수가!! 계산을 제곱으로 하고 있었다! 이 버그를 수정하고 나니 10초 가량 걸리던 작업이 2~3초면 끝난다. 야호!

웹에 올리면 속도가 2배는 빨라진다. 이유는 모르겠다.

작업하다보니 터무니없는 코드가 돌아가고 있었다. 잘도 이런 게 문제없이 돌아가고 있었어..?!

버그잡다가 끝났다.. 오늘은 여기까지

4.14

검질한 후에 치마가 다리를 뚫지만… 뭐 중요한 건 그게 아니지?!!

대부분의 모션은 다시 Idle로 연결된다. 빠른 격투게임의 템포상 본의 움직임이 잘 보이지는 않지만, Idle로 갔을 때 어색한 건 역시 거슬린다. 이 경우 세컨더리 본의 키를 어찌하는 것이 좋을까?

  • Idle의 0프레임으로 연결
  • 궤적에 따른 잔여 움직임으로 내비둠
  • 공용으로 쓰는 무난한 키프레임으로 연결

원래 2번으로 연결하려 했다가, 3번으로 연결하려 했다가, 1번도 해봤다가…결국 다시 2번이 가장 괜찮아 보인다.

역시 중요한 것은 궤적이다. 이렇게 되면 애니메이션을 짜르기 위해 중간에 키를 넣어야 한다. 어차피 그룹내 모든 본에 넣게 되니 이를 위한 스크립트를 추가하자.

필요에 의해 만든 기능은 모두 쓴다. 하지만 ‘이건 쓰겠지…’싶어서 만든 기능은 안쓰는 게 꽤 있다.

오늘의 깨달음. ARP에선 숨김처리 한 것은 익스포트 하지 않는다. 바디 컬라이더를 익스포트할 때마다 지워줘야겠네… 싶어 귀찮겠다 싶었는데. 오오..!

세컨더리 애니메이션이 들어가니 비로소 캐릭터가 완성되어 보인다.

4.15

본 회전 보간 시 오류가 나는 경우는 드물지만, 꽤 성가시다. 생각보다 잦은 빈도로 나타나길래 옵션을 만들었다.

오류가 날 땐 보간만 하고 충돌을 사용하지는 말자.

앞머리의 본을 아낌없이 사용한 선택은 옳았다. 세컨더리 애니메이션을 만나니 정말 다양한 실루엣을 만들어준다.

점점 옵션이 많아진다. 초기값 보간도 옵션이 적용되도록 수정. 이 기능은 alt+r과 같은데, 좀 덜 적용되도록 해서 회전값을 원래 형태에 가깝게 만드는 용도로 사용한다.

타임라인에서 현재 프레임이 회전키에 얼마나 가까운가를 확인하기 위해 추가. 사실 이건 굳이…? 라며 긴가민가 했는데… 결국 추가하게 되는구나.

찰랑찰랑. 고생한 보람은 있다.

키는 대체로 월드기준으로 복사해 주는 편이 좋은 결과를 보여준다. 허나 회전이 들어가면 원심력이 작용하기 때문에 로컬이 어울린다. 애니메이션이 짧을 때는 괜찮았던 것들이 길어지니 부족한 부분이 생긴다. 이를 위해 또 기능을 추가해야 한다.

앨리스의 세컨더리 애니메이션

4.10

세컨더리 애니메이션이란 본체의 애니메이션에 따라 부차적으로 필요한 애니메이션을 뜻한다. 캐릭터가 몸을 움직였을 때 따라오는 망토의 애니메이션이 대표적인 세컨더리 애니메이션이다. 애니메이터들이 매우 싫어하는 작업이다.

이를 위해 프로젝트 작업 초반에 세컨더리 애니메이션 전용 애드온을 만들었었는데, 역시 고장났다(…)

다행히 큰 고장은 아니었다.

경직은 충돌을 수동으로 처리하기 위해 만든 기능이다. 치마가 저렇게 뚫리는 걸 해결하기 위해 만들어둔 기능이었지만, 전혀 도움이 되지 않는다. 역시 이론과 실전은 다르다.

어떻게 할 지 고민하던 끝에 이런걸 찾았다.

메쉬에 오브젝트를 스냅해주는 것이다. 이걸 다리에 스냅하고, 본이 포인트를 보게 하면…잘 쓰면 뭔가 될 것 같은데..

설계 중… 완벽하진 않지만, 어쨌거나 본을 ‘밖으로 끄집어 내는 일’은 가능해 보인다.

하지만 롤(Roll)이 고려되지 않는다는 단점이 있다.

그른데 트랙 투를 이렇게 하면 어찌 될 듯…?

구상은 끝났다. 이제 만들어보자. 작업을 하다보니 그룹본 보간 기능이 좌/우 한 쪽에만 적용되는 옵션이 있으면 좋겠다.

그럭저럭 해결은 됐지만, 몇가지 기능이 더 필요하다.

  • 몸에 본을 붙일 때의 이격치
  • 경직구간 기능 제거

4.11

러프한 구현. 오퍼레이터를 사용하기 때문에 속도가 꽤 느리다! 실전은 역시 이론과 다른 법이지만, 본이 루트에 가까울 수록 더 괜찮은 결과를 보여줬다. 일단 계산을 최소화하기 위해 최적화 코드를 넣고, 보간법을 기존과 다르게 해주어야 할 것 같다.

계산 시 불필요한 걸 걸러줬더니 60초 -> 15초로 단축됐다! 상황에 따라 다르긴 하지만, 충분히 사용할 수 있을 것 같다.

최근 블렌더 사용 시에 오토세이브가 작동하지 않는 문제가 있었다. 처음엔 대수롭지 않게 생각하다가 몇 번 작업을 날리고 나니 생각이 바뀌었다. 대체 왜 그래…

몇몇 포럼을 뒤적여보니 모달연산자를 실행할 경우 오토세이브가 작동하지 않는다고 되어 있다.

https://docs.blender.org/api/current/bpy.types.Operator.html#modal-execution

뭔 소린지 하나도 모르겠지만, 애드온이 계속해서 이벤트를 체크하면 문제를 일으키는 것 같다. 시간을 리셋하는 걸까… 여튼 이같은 애드온을 2개 알고 있다. Auto Reload와 KitBash3D이다. 범인은 KitBash3D였다. 외부프로그램과 계속 통신을 하느라 모달연산자를 실행하는 모양이다.

이제는 사용하지도 않는 애드온 때문에 골머리를 썩었다. 여튼 이제 잘 작동한다. 마음의 안정이 찾아왔다.

그룹본 보간하기는 공식을 바꿔야 한다. 컬리전을 계산하며 본이 벌어지는 경우, 이를 보완해주기 위한 기능이다. 보기엔 쉬운데 저게 참 어려웠다. 화려한 것은 모두 잡기술이다. 정말 중요한 것은 보기엔 단순해도 구현난이도가 무척 높을 때가 많다.

기본은 이것이다. 이제 가운데 본을 보간하면

그 중간으로 움직인다.

예정했던 기능은 완료. 기능은 이상이 없는데, 칼주름이라 뾰족한 부분이 파묻히는 문제가 있다. 이를 수동으로 끄집어내어주는 기능이 좀 더 필요할 것 같다.

기능은 거의 완성됐지만, 몇가지 편의기능이 더 있으면 좋겠다. 오늘은 타임오버.

4.12

보간 도중 본이 안쪽으로 파고드는 버그가 있었다.

이 버그는 충돌알고리즘으로 사용했던 shrink wrap제약조건이 문제가 된다. 시스템이 갑옷 아랫쪽에 튀어나온 단면을 충돌영역으로 잡음으로서 원치 않는 오류를 내고 있었다. 컬리전은 맨몸 그 자체여야 할 듯…?

애드온은 막바지 테스트 단계. 여기에 적지는 않았지만 버그가 무척이나 많았다. 이제 없겠지. 없어야 해…!

애드온은 이제 정말 길어졌다. 많이도 만들었어.

애드온 완성!…일까?

공용 이펙트의 제작 #2

4.9

먼지를 만들어 보자.

먼지는 생각보다 이미지가 잘 안나온다.

계속 시도 중.

메쉬 기반을 포기하고 Streched Billboard 로 교체. 이 쪽이 더 좋아보인다.

됐다. 기본형은 완성이다. 지금은 이걸 코드로 처리해놨는데, 스크립트로 옮겨야 한다.

블렌드 모드를 열거형으로 지정할 수 있는 방법이 있다. 셰이더를 처음 배울 때 공부한 적이 있는데, 정작 쓰려고 보니 자료를 찾기 힘들다. 이렇게 하면 된다.

Properties
    {    
        [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("SrcBlend", Float) = 1
        [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("DstBlend", Float) = 0
    }

    SubShader
    {        
        ...
        Pass
        {            
            Blend [_SrcBlend] [_DstBlend]  

그런데 또 정작 쓰려고 보니 내가 필요한 건 Additive와 Opaque를 한 셰이더에서 처리하는 것인지라 셰이더를 분리할 수밖에는 없다. 일단 알아는 두자.

공용 이펙트 시스템의 구축. EffectManager:CreateCommonEffect()로 호출한다. 방향성을 타는 이펙트는 Forward와 Back을 붙여 구분해야 한다. 파티클의 운동량은 단순히 스케일 마이너스로는 해결이 되지 않기 때문에, 이렇게 따로 만들어 주어야 한다.

이제 정말로 미뤄왔던 작업 – 세컨더리 애니메이션을 제작해야 할 시간이 됐다. 두렵다…

공용 이펙트의 제작

4.7

제작할 것은 크게 2가지

  • 타격 이펙트
  • 먼지 이펙트

먼지는 배경에 따라 색이 달라져야 한다. 비오는 날엔 먼지 대신 물이 튀겨야 하지만, 느낌이 잘 안사니까 그냥 색만 맞추자. 무엇보다 귀찮다.

타격 효과부터 만들어 보자. 파티클은 써도 써도 헛갈린다.

오늘의 깨달음

파티클을 뉘이고 싶다면 렌더정렬을 Local로 해주면 된다.

하지만 방향성을 타는 파티클의 경우, 회전값을 주면 짐벌락 현상때문에 회전값이 뒤틀린다. 이것 때문에 이것저것 옵션을 찾아보았건만, 마땅한 해결책을 찾지는 못했다.

아무래도 블렌더에서커스텀 메쉬를 별도로 만들어 처리해 주어야 할 것 같다.

엥…그른데 반밖에 출력이 안된다.

회전계산을 할 때 부호가 바뀌는 걸 고려하지 않는 모양이다… 이래서 공식기능으로 회전이 없는건가.

일단은 셰이더에서 양면을 모두 찍게해서 해결하기로 하자. 이게 정답이 아닐 수도 있지만, 지금으로선 이게 최선이다.

앞으로 방향성을 타는 2D식 이펙트는 모두 오른쪽을 보게 만들어야 한다는 규칙이 생겼다.

테스트를 위해 이펙트를 붙인다. 이걸 붙이느라 또 리팩토링 한가득.

헤비 이펙트. 잘 작동한다.

풀관리도 잘되고 있다. 암만 때려도 이펙트가 5개가 넘어가진 못한다.

이제 꾸며보자.

이펙트의 마스크 이미지는 싱글채널을 사용한다. R과 A중 고를 수가 있다. 기본값이 A인걸로 보아 A를 추천하는 모양인데, 셰이더에서 정보를 받을 때 4채널로 받은 후 1개만 쓰는 구조가 마음에 들지 않았다. 그래서 지금까지는 번거로워도 R채널을 쓰고 있었는데… 샘플링하면 무조건 4채널로 받는 모양이다. (에러가 나는 건 아니지만, 경고가 뜬다.) 이래나 저래나 똑같다면 그냥 A를 쓰는 게 낫겠지만…

한가지 더 의문이 든다. 싱글채널은 작업은 편하지만 최적화에 도움이 될까?

샘플링이 무조건 4채널이라면 어쨌거나 비디오 메모리를 먹는다는 이야기가 된다. 비슷한 고민을 한 친구가 있다.

https://forum.unity.com/threads/sample-one-texture-channel-only-instead-of-four-optimization.526185

그런데 채널을 R로 바꾸면 메모리 절감효과가 있다.

무려 절반이! 줄어든다. 하지만 그렇게까지 훌륭하지는 않은 것 같기도… 그냥 Default로 하고 채널별로 분리하는 게 좋을까… 선인들이 그렇게 한데는 이유가 있겠지…

일단은 타임오버 내일 다시 고민해보기로 하자.

4.8

텍스처 압축에 대해 좀 더 조사를 해 보자. 유니티의 텍스처 관련 문서에서 자세한 설명을 찾을 수 있었다.

https://docs.unity3d.com/kr/2021.2/Manual/class-TextureImporterOverride.html

비교하려는 것은 BC3와 BC4다. BC3는 DXT5를 뜻한다. 우리가 흔히 사용하는 바로 그 포맷이다.

텍스처 압축 포맷설명채널Quality픽셀당 비트1024×1024 텍스처 크기(MB)
RGBA Crunched DXT5압축된 RGBA, 추가 디스크 크런치 압축 포함RGBA낮음-중간가변가변
R Compressed BC4압축된 단일 채널(R)R높음40.5

눈치껏 판단하건데 BC뒤에 붙는 숫자가 높을 수록 더 높은 품질을 보여주는 것 같다. 적어도 DXT5와 BC4를 놓고 보면 그렇다. 답은 나왔다. 마스크 채널은 BC4를 사용해야 한다.

파티클의 타격 이미지가 자연스럽게 사라지게 하기 위해 SDF를 사용할 예정이다. 블렌딩시키고 싶은 2개의 이미지가 있을 때 이것이 자연스레 합성이 되는지 테스트가 필요하다.

타격이미지는 요렇게 작아져야 한다.

는 실패. SDF는 마법이 아니다.

그냥 SDF를 쓰지 않은 프레임 커팅이 나아보인다.

에이..별로네…

그냥 SDF가 가장 낫다. SDF는 마법이다!(…)

파티클의 커스텀 데이터를 다룰 때 사용하는 모드는 ‘보기‘이다! 컬러 시맨틱을 커스텀 데이터로 사용한다는 뜻이 아니다! 이걸 사용하려면 커스텀 데이터를 TEXCOORD1에 할당해서 사용하여야 한다.

이 시스템을 이해하느라 작업시간을 다 썼다. 아휴. 타임오버.

오래 전 개발했던 캐릭터 히트 라이트가 이제야 그림이 맞게 됐다.

이펙트가 캐릭터를 감싸도록 찍히는 게 마음에 든다. 의도했던 대로

배경 작업 정리

4.5

비를 만들 땐 유니티에서 기본 제공되는 이미지를 사용했다. 그래도 별 탈은 없지만 동그라미는 여기저기 많이 쓰니까 수학 계산으로 기본형을 만들어두는 게 편하다. 챗GPT에게 물어보니 length를 사용하면 된단다.

half4 frag (Varyings IN) : SV_Target
{            
    float2 center = float2(0.5, 0.5);
    float circle = pow(saturate(1-length(IN.uv - center)-0.5)*2, 2.2);

    return circle * _Color;
}

길이를 구한 후 그걸 토대로 필요한 계산들을 거쳐준다. 2.2제곱은 감마 스페이스를 인간의 눈에 예쁘게 보이도록 바꿔주는 것이다. 필요한 계산들은 그 전에 하는 편이 정확하다.

셰이더가 많아졌으므로 이펙트용 폴더를 분리한다. 이전에 망한 프로젝트의 경험을 떠올리면 툰렌더의 이펙트는 의외로 Additive가 적다. 두가지 이유가 있는데, 하나는 만화적인 표현을 위한 컷아웃을 주로 사용한다는 점, 다른 하나는 툰렌더의 특성상 분위기가 밝기 때문에 애디티브 셰이더가 범용적으로 어울리지 않는다는 점이다. 따라서 Additive는 별도의 식별자를 붙이는 편이 좋겠다.

따라서 이렇게 분리된다.

카메라에 맺히는 물방울을 정리하다가 대마왕님의 블로그에서 무려 1채널로! 돋보기 효과를 내는 포스트를 찾았다.

https://chulin28ho.tistory.com/623

하지만 내용을 보니 단색 이미지를 노말로 변환하는 과정이 들어간다. 그렇다면 내용이 똑같으므로 패스해도 되겠다.

작업하다가 엉망진창인 물방울 노드도

깔끔하게 정리해 주고

머티리얼도 정리해준다.

이제 셰이더 폴더도 복잡해졌다.

카메라에 맺히는 물방울은 렌더피처에서 동적으로 끄고 켤 수 있어야 한다. 다음과 같은 방법으로 접근할 수 있다.

[SerializeField] UniversalRendererData rendererData;
...
ScriptableRendererFeature rendererFeature = rendererData.rendererFeatures[0];
rendererFeature.SetActive(true);

이것은 추후 배경의 데이터를 담을 스크립트에 사용할 것이다. 작동하는 걸 확인했으니 일단은 빼두자.

대강 정리가 끝난 것 같다.

다음 작업은… 원래 세컨더리 애니메이션을 할까 했는데, 셰이더를 만지다 보니 이펙트가 하고 싶어졌다. 공용이펙트인 타격/먼지 이펙트를 제작해 보자.

흐린 마을 #2

4.3

조명은 씬에 귀속된다. 그림자의 색깔도 마찬가지다. 환경에 의해 전체적인 색조가 탁해야 하므로 셰이더 드라이버의 Start()함수에 색 조절 함수를 추가해주어야 한다. 이를 위해 아무렇게나 지어놨던 셰이더의 변수명을 통일했다. 캐릭터가 많았다면 한 고생했을 것 같은 작업이다.

비가 떨어지는 효과를 위해 이펙트를 좀 배워야 한다. 파티클의 기본 기능이야 카노를 제작할 때 지겹도록 써봤으니 대충은 알고 있지만, SDF텍스처를 라이프타임에 맞게 컷아웃하는 기능을 모른다. 언리얼의 머티리얼엔 다이나믹 파라메터라는 것이 있는데, 유니티에선 이를 어떻게 쓸까? rito15님의 블로그에 그 해답이 있다.

https://rito15.github.io/posts/unity-particle-system-custom-data

개념이 조금 복잡해 보이지만, 아마 익숙해지면 편하게 사용할 수 있을 것으로 보인다.

그렇게 꼴랑 만든 것이 이것[…] 별 거 아닌 것 같은데 생각보다 많은 걸 공부해야 했다. 이걸 어따 쓰냐면…

바닥을 축축하게 보이고 싶을 때 쓴다. 이제 화면도 축축해 보이고 싶다. 이를 위해 필요한 것이 렌더타깃인데… 이걸 또 어찌하는 지 모른다[…] 내일 공부해 보자.

렌더텍스처에 대해서 공부. URP에선 카메라의 메뉴가 조금 변했기 때문에 렌더 텍스처를 넣는 위치가 튜토리얼과 다를 수 있다. 기존엔 Rendering탭에 있었지만, URP(혹은 최신버전)에선 Output탭에 있으니 주의하자.

렌더 텍스처는 확보했다. 이제 이것을 포스트프로세스와 연결해야 한다. 언젠가는 해야 할 공부였으니 지금 해보자.

자료를 몇 개 찾아본 결과, 유니티의 포스트프로세싱 방법은 많이 변했다. 어차피 셰이더는 그대로일텐데, 볼륨을 적용하는 방법이 많이 변한 것 같다. 이렇게 되면 배우기가 참 힘들다. 튜토리얼이 맞지 않기 때문.

https://darkcatgame.tistory.com/121

이 블로그에 자세히 설명되어 있는 것 같다. 긴 여정이 될 것 같으니 각잡고 공부해보자.

…였는데 이것조차 최신 버전과는 맞지 않았다. (2년밖에 안된 글인데…!)

해서 자료를 좀 더 찾아보니 셰이더 그래프가 눈에 띈다. 안본 사이 셰이더그래프도 많이 변했다. 그리고 포스트프로세싱용인 풀스크린 머티리얼도 추가된 것 같다!

노드의 장점은 버전호환성이 좋다는 것이다. 단점은 그만큼 기능이 적다는 것이다. 하지만 포스트프로세싱엔 고급기능을 사용하지 않아 굳이 HLSL머티리얼을 고집할 필요가 없다. 이걸 쓰자.

하지만 뭔가 단단히 잘못된 것만은 알겠다….

4.4

카메라에 파티클이 주르륵 내리는 현상의 주원인은 렌더러로 밝혀졌다. 현재 사용중인 렌더러는 포스트프로세싱을 거치는데, 이 프로세싱이 적용된 상태로 렌더텍스처에 중첩되어 그려진다. ( 그렇다고 해도 드로우 절차상 그래선 안될 것 같은데… 어쩐지 그러고 있다.) 때문에 메인 카메라의 렌더러와 보조카메라의 렌더러를 다르게 사용해야 한다.

하지만 그럼에도 불구하고 피가 주르르 흐르는 현상이 여전하다.

원인은 배경. 뚫린 배경이 문제이기 때문에 검은색 Solid로 채워주면 문제가 해결된다. 이걸 알아내느라 반나절이 걸렸다.. 아휴.

이제야 원하는 결과를 얻었다. 눈을 내리려는 건 아니다.

이제 이 파티클들로 카메라에 맺힌 물방을처럼 굴절을 일으키고 싶다. 씬컬러(셰이더 그래프에선 BlitSource라고 부른다.) 의 Screen Position에 UV왜곡치를 더해 볼록하게 표현하면 될 것이다. 3가지 정보가 필요하다. X, Y, 그리고 마스크. 공교롭게도 노말맵의 정보와 비슷하다. 물방울을 모델링해서 노말을 뽑자.

노말맵이 곡률을 계산하고 있어서 얼추 볼만하게는 나올 줄 알았는데 생각보다 밋밋한 결과를 보여줬다. 좀 더 정확한 구현을 위해선 사인함수를 추가로 사용해 곡률을 더해주어야 했다.

그런데 이렇게 쪼그맣게 쓸거라, 사실 저렇게까진 안해도 된다. 그냥 한 번 구현해 보고 싶었다. 실무라면 일정에 쫓겨서 안했을 듯.

마지막은 리플렉션이다. 렌더텍스처가 하나가 더 필요하다.

일단 배경이 비칠 물웅덩이를 모델링하고

용암을 붓…이게 아닌데…?! 스크린 UV를 구하는 방법을 모르겠다.

https://blog.naver.com/mnpshino/221481588595

하지만 마둠파님께서 친절하게 설명해 놓으셨다.

렌더텍스처를 걍 뒤집어 쓰면 될 줄 알았는데, 생각보다 잘 안맞는다.

…는 렌더텍스처의 비율문제였다. 렌더텍스처를 2의 배수로 맞췄더니 이런 문제가 생긴다.

마참내! 구현은 완료했다. 예상만큼 어려웠고, 배운 것도 많다.

이제 정리작업이 남겨두고 있다. 이건 내일 하자…

흐린 마을

3.31

스케치한다. 우중충한 마을이 꼭 부셔져있을 필요는 없지만, 그냥 부셔졌다고 하자.

원경은 이번에도 AI가 수고해주었다. 부셔지진 않았지만, 뒷 공간을 드문드문 채우는 용도이니 그리 중요하지는 않아 보인다.

4.1

스케치가 끝났으니 디테일 작업에 들어가면 된다. 하지만 이번엔 예정된 작업이 만만치 않다. 정리를 하고 가는 편이 좋겠다.

  • 배경 모델과 텍스처 PBR > NPR정리 작업
  • 셰이더에 Rainy항목을 추가
    • 이 항목은 바닥으로 갈 수록 하얀 포그를 추가하고, 캐릭터일 경우 색채도를 낮춘다. 적용했을 때 결과물이 별로라면 필요없을 수도 있다.
  • 빗줄기, 물방울 튀기기 이펙트를 추가.
  • 카메라 물방울 포스트 프로세싱
    • 파티클로 물방울 이펙트를 만든 후, 렌더타겟으로 이를 찍어 표현해야 한다. 언리얼에서는 해봤는데 유니티에서는 안해봤다. 포스트 프로세싱 자체가 처음이므로 공부해야 하기에 사실상 이것이 가장 큰 작업이 될 듯.
  • 물웅덩이에 SS반사
    • 이거 어떻게 하는 건지 모르는데, 일단 적어두자.

차근차근 작업해보자.

일단 텍스처를 정리했는데… 음. 메쉬정리가 심히 귀찮다…그래도 해야겠지….

영차영차. 오늘은 여기까지

오늘의 깨달음

엉망진창인 면을 선택할 땐 스무싱 그룹을 오토로 나누고 L을 눌러 샤프기준으로 선택하면 한결 수월하다.

베이킹을 했지만 예상치 못한 문제가 발생.조각이 너무 많아 픽셀로 감당이 안된다.

결국 수동으로 다시 펴주는 중…

4.2

UV를 잘 펴던 중, 희안한 현상을 발견했다. 저렇게 얇은 사다리꼴이 나오고 모든 폴리곤이 보이지 않는 현상이다. 위의 문장 옆에 (Clipped)라고 써져 있는 것이 수상한데, 이 기능이 대체 뭘까… 챗GPT에게 물어보자.

알겠습니다. Blender에서 뷰포트 정보 옆에 괄호로 **“(Clipped)”**라고 표시되는 현상은 다음과 같이 해결할 수 있습니다:

  1. 클리핑된 뷰포트 해제:
    • 이 현상은 특정 영역만 폴리곤이 나오고 나머지 영역이 클리핑되는 상태를 나타냅니다.
    • 이것은 **원근 투영(Perspective Projection)**으로 선택한 영역 외부를 클리핑하는 것입니다.
    • 이를 해결하려면 다음과 같이 하세요:
      • Alt + B를 눌러 클리핑된 영역을 해제합니다.
      • 다시 Alt + B를 눌러 해제된 상태로 돌아갑니다.

이렇게 하면 뷰포트의 클리핑 현상이 해소됩니다. Blender 작업을 즐겁게 이어나가세요! 🎨🔍

Alt+B를 누르면 해결은 된다. 그런데 이게..음… 뭔 기능인지 모르겠네.

이번 베이킹은 폴리곤이 많아서 고생 좀 했다. 아휴… 이제 내일은 질감을 넣어보자.

일단은 유니티로 넘기고 색감을 맞추자.

배경 기본은 완료 캐릭터도 우중충한 색감에 맞춰주어야 한다.

어떻게 표현을 할지 테스트 중

음. 결국 그냥 명암색을 바꿔주는 것만으로 충분해 보인다.

지면에 깔리는 안개를 표현하고 싶었는데… 이건 안넣어도 될 듯.

비도 내리고(이렇게 많이는 아니고…)

이제 어려운 것들을 해보자.

성벽 위

3.30

이번 배경은 밤이다. 일단 스케치부터.

하늘이 있어야 분위기가 살겠다. 하늘부터 깔아보자.

오… 나쁘지 않은데 달이 너무 크다. 애초에 웹툰용 소스라 하늘이 넓게 표시되지는 않기 때문이다. 일단 밑져야 본전이니 AI를 돌려보자.

그게 무슨 밤이야…

못쓰겠다.

스케치는 완료. 이제 UV를 펼 차례. 하지만 타임오버이므로 오늘은 여기까지.

3.31

물건이 얼마 없어서 쉽게 구웠다. 유니티로 넘겨보자.

데이터가 가벼워서 큰 어려움은 없었다. 완성이 쉽게 돼서… 좀 성의없게 느껴지는 감은 있는데, 꼭 고생해야 정성가득한 작업은 아니라고 생각하기로 하고…(직업병이다.)

다음 배경의 주제는 ‘비오는 날’이었는데, 이것이 고민이다. 물에 젖은 캐릭터를 표현하려면 지금보다 많은 것이 필요하다. 옷도 젖어야 하고, 스펙큘러도 좀 더 표현되어야 하고, 바닥에 물반사, 카메라의 물방울 굴절, 번개가 치는 찰나의 흑백음영 셰이딩 등… 많은 것이 필요하다. 나열된 요소 중 몇몇개는 이미 불가능한 것들이다. 게다가 이것이 게임하기에 좋은가?… 에 대한 근본적인 고민이 있다.

하지만 우중충한 배경을 꼭 해보고 싶긴 하다. 비가 약간씩만 내리는 것은 어떨까. 그럼 캐릭터는 안젖어도 될테니까… 우선 그렇게 방향을 잡고 진행해 보기로 하자.

노을지는 거리

3.27

킷배시의 몇몇 모델들을 다운로드하지 못하는 문제가 있다. 귀찮지만 꼭 해결해야 할 문제라서 회사에 메일을 보냈다. 답변을 기다려는 동안 수동임포트를 시도해보았는데, USD라는 것은 어떤 확장자일까… 결국 알아내지 못했다.

답변이 왔지만 원론적인 이야기다. 메모리가 부족하다는 이야기인데, 말도 안된다. 에휴. 수동으로라도 임포트 할 수 있게 해놓으면 좋으련만… 별 수 없이 유니티로 불러 우회하는 방법을 사용해야 한다.

이걸 위해서 유니티에 킷배시를 연결하고, fbx exporter를 깔고, 가져오고… 복잡한 과정이 있었지만 결국 가져왔다.

조명은 바닥을 비춘다. 그렇다면 바닥은 저렇게 어두우면 안된다. 이유가 뭘까…

아하. 노말맵이 메쉬의 노말을 잘못된 방향으로 인도하고 있었다.

어차피 노말맵은 안쓸 것이니 과감히 짤라주면 제대로 나온다.

스케치한다. 노을은 빛이 질감을 잡아먹기 때문에 PBR을 그대로 써도 썩 어울릴 것 같긴 하다. 그래도 통일성을 위해 질감을 지워주자.

프로젝션 준비를 위한 모델링 작업. 그래도 2번 작업을 거친 후라 한결 수월하게 진행 중

3.28

언랩이 끝났는데… 스마트 UV는 뒀다 뭐해…바보같이 하나하나 폈다. 흑흑

총 폴리곤은 19630개. 캐릭터 하나의 1/5도 안된다. 뷰가 고정되면 여러 모로 잇점이 많다.

베이킹은 내일 하자.

3.29

베이킹을 하기 전에 스타일을 툰형태로 맞춘다. PBR을 보다가 카툰렌더를 적용하면 아무래도 좀 심심해 보인다.

기본 베이킹 완료. 디테일은 내일

바닥이 왜 뚫릴까… 싶어서 살펴보니 거리를 잘못 쟀다..

노을지는 거리의 배경 완료.

이번 배경은 지금까지 정립한 프로세스를 확인하는 작업이었으나, 여전히 삽질이 많았다.

바닥의 경우 2cm정도의 높낮이를 주었지만, 이것이 생각보다 더 캐릭터를 떠 보이게 하는 결과가 있었다. 무조건 평면이 낫드라.

이 쯤에서 오랫만에 빌드. Cargo의 플러그인이 문제를 일으킨다.

플러그인에 필요한 dll파일이 빌드에 들어가려 해서 오류가 난다. 이것을 에디터에서만 사용하도록 바꿔주어야 한다.

실무에서 일일 빌드의 중요성은 늘 강조된다. 버그는 그 때 그 때 찾아 없애야지, 방치하면 큰 병이 된다. 하지만 역시 자동 일일 빌드 시스템이 구축되지 않은 상황에서 이것은 역시 꽤 귀찮은 일이다..

다음 배경은 멀리 마법사의 탑이 보이는 성벽으로 하고 싶다. 적절한 모델이 있으면 좋겠다.