핵심 요약
- 경찰과 도둑은 도시의 밤거리를 무대로 경찰팀과 도둑팀이 3대3으로 붙는 비대칭 술래잡기 PC 게임이다. Three.js와 Vite, TS로 만들고 있고 파쿠르와 온라인 멀티까지 붙어 있다.
- 캐릭터가 밤 배경에서 평면적인 스티커처럼 보이는 문제를, emissive 통짜 발광을 걷어내고 필라이트와 림라이트를 셰이더에 직접 주입하는 방식으로 해결했다.
- roughness는 0.85로 고정했다. 더 낮추면 머리 위 조명의 스페큘러가 정수리에 튀는 부작용이 있어서, 입체감과 부작용 사이에서 타협한 값이다.
- 픽사급 컨셉 이미지를 3D로 변환하는 v4 파이프라인으로 새 후보 모델을 만들었지만, 스타일이 확정되지 않아 결과물(GLB)은 로컬에만 두고 커밋하지 않았다.
밤에 캐릭터가 스티커처럼 보이는 이유
이 게임은 야간 도시가 배경이다. 어두운 거리에서 캐릭터가 안 보이면 곤란하니 예전부터 캐릭터에 emissive 0x4a4a4a를 통짜로 먹여서 자체 발광을 시켰다. 문제는 이게 너무 잘 작동했다는 점이다. 발광이 균일하게 퍼지면서 캐릭터 표면의 음영이 눌려버렸고, 결과적으로 입체가 아니라 색칠된 종이 오려낸 것처럼 보였다. 가독성은 확보했는데 그 대가로 캐릭터의 존재감을 잃은 셈이다.
three.js는 특정 오브젝트만 골라서 조명을 다르게 먹이는 기능을 지원하지 않는다. 그래서 조명 설정을 바꾸는 대신 프래그먼트 셰이더 안에 직접 두 개의 빛을 주입했다. 카메라 방향의 필라이트는 알베도와 램버트 값을 곱해서 만들고, 여기에 달빛을 흉내 낸 림라이트를 더했다. 이렇게 하면 전체를 밝게 뭉개는 게 아니라 캐릭터 윤곽을 따라 빛이 도는 것처럼 보이면서도 밤에 눈에 띄는 정도는 유지된다.
roughness 값을 0.85로 고정한 것도 같은 맥락이다. 이보다 낮추면 머리 위에 달아둔 playerLight의 스페큘러 하이라이트가 정수리에 그대로 맺혀서 캐릭터 머리에 반짝이는 점이 하나 생긴다. 입체감을 살리려다 이상한 곳에 하이라이트가 생기는 부작용을 만난 셈이라, 지금은 이 값에서 멈춰 있다. 로비 프리뷰용 3점 조명(키+림+바운스)도 이번에 같이 손봤고, charview에서는 클래스 목록이 다 로딩될 때까지 기다리도록 바꿨다.
결정하지 않은 것을 커밋에 남기지 않는 이유
이번 흐름에서 가장 큰 부분은 v4 A/B 파이프라인이다. 픽사급 컨셉 이미지를 Meshy의 image-to-3d로 넘겨서 PBR 재질이 입혀진 정적 메시를 뽑아내고, charview 화면에서 기존 로스터에 없는 police_v4 같은 후보 모델을 로드해서 비교해볼 수 있게 만들었다.
여기서 리깅은 아직 안 했다. 스타일이 확정되지 않은 모델에 뼈대를 입히고 애니메이션을 붙이는 건 시간이 드는 작업이라, 지금 단계에서는 정적 메시로 형태와 재질감만 먼저 비교하는 쪽을 택했다. 같은 이유로 v4 GLB 파일 자체도 저장소에 커밋하지 않고 로컬 public/models 폴더에만 두었다. 코드는 스타일을 비교하는 도구일 뿐이고, 그 도구가 만들어낸 결과물 중 무엇을 쓸지는 아직 정하지 않았다. 결정하지 않은 산출물을 저장소에 올리면 나중에 이게 실제로 쓰이는 모델인지 그냥 실험 흔적인지 구분하기 어려워진다. 그래서 코드는 커밋하되 데이터는 판단이 끝난 뒤에 올리는 쪽을 택했다.
이 둘을 이어놓고 보면 오늘 작업은 결국 같은 질문의 두 얼굴이었다. 캐릭터가 어떻게 보여야 하는가라는 질문에 셰이더 쪽에서는 답을 냈고, v4 쪽에서는 아직 답을 내지 않았다. 셰이딩 쪽은 확정된 캐릭터의 표현 방식을 다듬는 일이었고, v4 쪽은 그 캐릭터 자체를 바꿀지 말지 판단하는 일이라 성격이 다르다. 그래서 코드는 먼저 커밋하고, 판단이 필요한 자산은 판단이 끝날 때까지 저장소 밖에 둔 것이다.