셰이더 디버깅, 화면이 검게 깨지는 원인부터 잡는다

profile_image
작성자 그래픽스개발자 시온
댓글 0건 조회 6회

머티리얼을 연결한 순간 캐릭터가 새까맣게 변하거나, 특정 그래픽카드에서만 표면이 번쩍인다면 셰이더 코드를 무작정 다시 쓰기 쉽습니다. 하지만 이런 현상은 복잡한 조명 공식보다 잘못된 입력값, 좌표계 불일치, 정밀도 문제에서 시작되는 경우가 훨씬 많습니다.

셰이더 오류는 일반 코드처럼 중단점과 로그만으로 추적하기 어렵습니다. 따라서 화면에 값을 직접 출력하고 렌더링 단계를 하나씩 격리하는 방식으로 접근해야 원인을 빠르게 좁힐 수 있습니다.

검은 화면이 나오면 입력 데이터부터 분리합니다

컴파일 성공은 정상 렌더링을 보장하지 않습니다

셰이더가 컴파일되었다고 해서 계산 결과까지 정상이라는 뜻은 아닙니다. 버텍스 위치가 클립 공간 밖으로 밀렸거나 텍스처가 바인딩되지 않았거나, 법선 벡터가 반대 방향을 바라보면 문법 오류 없이도 화면은 검게 나옵니다. 가장 먼저 해야 할 일은 조명과 텍스처 계산을 제거하고 고정 색상만 반환하는 셰이더로 교체하는 것입니다.

고정 색상이 보이면 래스터라이저와 렌더 타깃까지는 정상이라고 판단할 수 있습니다. 그다음 버텍스 색상, UV, 법선, 조명 순서로 기능을 한 단계씩 복원합니다. 반대로 고정 색상조차 보이지 않는다면 프래그먼트 계산보다 카메라 행렬, 컬링 모드, 깊이 테스트, 드로 호출 여부를 먼저 확인해야 합니다.

  1. 픽셀 셰이더가 빨강 같은 고정 색상을 반환하도록 변경합니다.
  2. 깊이 테스트를 잠시 끄고 지오메트리가 가려졌는지 확인합니다.
  3. 백페이스 컬링을 해제해 삼각형 정점 순서 문제를 찾습니다.
  4. 월드·뷰·투영 행렬을 각각 단위행렬로 바꾸며 문제가 생기는 단계를 구분합니다.
  5. 텍스처 대신 UV를 RGB 색상으로 출력해 보간 결과를 검사합니다.

값을 색으로 바꾸면 GPU 내부가 보입니다

GPU 내부 값은 로그보다 색상으로 시각화하는 편이 빠릅니다. UV는 float4(uv.x, uv.y, 0, 1), 월드 법선은 음수 범위를 보정한 normal * 0.5 + 0.5 형태로 출력할 수 있습니다. 화면에 갑작스러운 경계나 단색 영역이 나타난다면 값의 범위, 보간 또는 정규화 과정에 문제가 있다는 신호입니다.

  • 전체가 한 가지 색: 상수 버퍼나 버텍스 속성이 갱신되지 않았을 가능성이 큽니다.
  • 삼각형마다 색이 끊김: 보간 지정자 또는 정점 중복 상태를 확인합니다.
  • 카메라 이동 때 색이 요동침: 서로 다른 좌표계의 값을 섞었는지 살펴봅니다.
  • 일부 픽셀만 흰색: 값이 1을 크게 넘거나 NaN이 전파되었을 수 있습니다.
한 번에 여러 기능을 고치지 마세요. 고정 색상에서 시작해 입력을 하나씩 되살리면 최초로 화면이 깨지는 지점이 곧 조사 대상입니다.

좌표계와 법선이 맞지 않을 때 생기는 흔적을 읽습니다

월드 공간과 뷰 공간을 섞지 않았는지 확인합니다

조명이 카메라를 따라 움직이거나 모델을 회전했을 때 밝은 면이 엉뚱한 곳에 남는다면 좌표계 불일치를 의심해야 합니다. 예를 들어 법선은 월드 공간인데 광원 방향은 뷰 공간이라면 내적 자체는 계산되지만 물리적으로 의미 없는 결과가 나옵니다. 변수 이름에 WS, VS, TS처럼 공간을 표시하면 이런 실수를 코드 단계에서 줄일 수 있습니다.

행렬 곱셈 순서도 엔진과 셰이딩 언어에 따라 달라질 수 있습니다. 행 우선과 열 우선 저장 방식, CPU에서 전치했는지 여부, 왼손·오른손 좌표계를 확인해야 합니다. 특히 직접 만든 게임 수학 라이브러리를 사용한다면 단위 벡터를 넣었을 때 예상 축으로 변환되는지 작은 테스트를 먼저 작성하는 편이 안전합니다.

화면 증상가능성이 큰 원인빠른 확인법
카메라 회전에 따라 조명이 회전함월드·뷰 공간 혼용법선과 광원 방향을 같은 색상 규칙으로 출력
모델 크기를 바꾸면 명암이 찌그러짐비균일 스케일의 법선 변환 오류역전치 행렬 적용 전후 비교
메시 일부가 투명하게 사라짐정점 감김 방향 또는 컬링 설정양면 렌더링으로 임시 전환
노멀맵 방향이 뒤집힘탄젠트 기준 또는 Y축 규약 불일치평면 노멀맵과 축별 테스트 텍스처 사용

비균일 스케일에는 역전치 행렬이 필요합니다

위치에 사용한 월드 행렬을 법선에도 그대로 적용하면 균일 스케일에서는 멀쩡해 보여도 납작하거나 길게 늘린 모델에서 조명이 틀어집니다. 법선은 표면의 접선과 수직 관계를 유지해야 하므로 월드 행렬 상단 3×3의 역행렬을 전치한 행렬로 변환해야 합니다. 변환 뒤에는 다시 정규화해야 보간 과정에서 달라진 길이까지 보정됩니다.

  • 법선 벡터의 길이가 0에 가까운 정점이 없는지 검사합니다.
  • 노멀맵을 끈 기본 조명부터 정상인지 확인합니다.
  • 탄젠트의 부호 값으로 비탄젠트 방향을 복원하는 규약을 통일합니다.
  • DirectX와 OpenGL 계열 텍스처의 노멀맵 Y축 차이를 확인합니다.
  • 미러링된 UV에서 탄젠트 공간이 뒤집히는 구간을 별도로 살펴봅니다.

개발 행사에서 공개되는 그래픽스 세션은 실제 렌더링 문제를 단계별로 분해하는 사례를 접하기 좋습니다. 행사 성격은 GDC 관련 지식백과 설명을 참고하면 프로젝트 기술 공유가 왜 중요한지도 함께 이해할 수 있습니다.

텍스처와 수치 오류는 재현 조건을 고정해 추적합니다

샘플러 하나가 플랫폼별 결과를 바꿀 수 있습니다

에디터에서는 정상인데 모바일 기기나 특정 GPU에서만 표면이 깨진다면 텍스처 포맷, 샘플러 상태, 정밀도 지정자를 확인해야 합니다. 존재하지 않는 밉 레벨을 읽거나 압축 텍스처의 채널을 잘못 해석하면 검은색 블록과 색 번짐이 나타날 수 있습니다. sRGB 텍스처를 선형 데이터처럼 사용하거나 그 반대로 처리하면 치명적인 오류는 없어도 전체 명암이 부자연스러워집니다.

색상 텍스처는 일반적으로 sRGB 변환 대상이지만 법선, 거칠기, 금속성 같은 데이터 텍스처는 선형 값으로 읽어야 합니다. 한 장에 여러 속성을 묶은 채널 패킹 텍스처라면 제작 도구와 런타임의 채널 정의가 같은지도 확인하세요. 기획 문서에 단순히 ‘재질이 어둡다’라고 적는 것보다 재현 카메라, 광원, 품질 옵션을 함께 남겨야 개발자와 콘텐츠 담당자가 같은 장면을 볼 수 있습니다. 역할 간 요구사항 전달의 맥락은 기획자에 관한 용어 설명에서도 확장해 볼 수 있습니다.

  1. 재현 장면 고정: 카메라 위치, 광원, 해상도, 품질 옵션을 저장합니다.
  2. 원본 텍스처 대체: 흰색·회색·평면 노멀 같은 검증용 텍스처를 연결합니다.
  3. 채널별 출력: R, G, B, A 채널을 각각 회색조로 표시합니다.
  4. 밉맵 확인: 밉 레벨마다 다른 색을 넣어 예상 레벨을 읽는지 봅니다.
  5. 플랫폼 비교: 동일한 장면을 API와 기기별로 캡처해 최초 차이가 발생한 패스를 찾습니다.

NaN과 무한대는 발생 직후 차단합니다

번쩍이는 점, 화면 전체를 가르는 긴 삼각형, 프레임마다 달라지는 노이즈는 NaN이나 무한대가 계산 체인에 들어간 흔적일 수 있습니다. 길이가 0인 벡터의 정규화, 0으로 나누기, 음수의 제곱근, 범위를 벗어난 역삼각함수가 대표적인 원인입니다. 값이 프레임 버퍼에 기록될 때는 이미 여러 픽셀로 퍼졌으므로 위험한 연산 바로 앞에서 입력 범위를 제한해야 합니다.

normalize(v)를 무조건 호출하기보다 길이 제곱이 작은 경우 기본 방향을 반환하고, 분모에는 문제를 숨기지 않는 범위에서 최소값을 적용합니다. acos 입력은 부동소수점 오차로 1을 살짝 넘을 수 있으므로 -1과 1 사이로 제한합니다. 다만 모든 값에 무차별적으로 saturate를 적용하면 원인이 가려지므로 디버그 빌드에서는 비정상 값을 눈에 띄는 자홍색으로 표시하는 분기를 두는 편이 낫습니다.

  • 정규화 전 벡터 길이 제곱을 검사합니다.
  • 나눗셈 전 분모가 허용 오차보다 큰지 확인합니다.
  • pow의 밑과 지수가 유효한 조합인지 검증합니다.
  • 중간 렌더 타깃의 HDR 범위와 포맷 정밀도를 확인합니다.
  • 모바일에서는 half와 float를 바꾸어 정밀도 차이를 비교합니다.
오류 값을 0으로 조용히 바꾸면 장면은 덜 깨져 보여도 버그는 오래 남습니다. 개발 중에는 비정상 값을 강렬한 색으로 드러내고, 출시 빌드에서만 안전한 대체값을 적용하는 방식이 유리합니다.

증상을 가리는 세 가지 습관이 수정 시간을 늘립니다

무작정 옵션을 끄기 전에 최초 불일치 지점을 남깁니다

첫 번째 실수는 문제가 사라질 때까지 그림자, 후처리, 노멀맵을 한꺼번에 끄는 것입니다. 원인이 제거된 것은 알 수 있어도 어느 입력과 패스가 충돌했는지는 남지 않습니다. 기능을 하나씩 끄고 각 단계의 결과를 캡처하며, 사용한 셰이더 변형 키워드와 렌더링 API도 함께 기록해야 재발을 막을 수 있습니다.

두 번째 실수는 셰이더 소스만 보고 CPU 측 바인딩 코드를 확인하지 않는 것입니다. 상수 버퍼의 구조체 정렬, 오래된 디스크립터, 잘못된 텍스처 슬롯은 GPU 코드가 완벽해도 오류를 만듭니다. 구조체 필드 크기와 오프셋을 출력해 셰이더 선언과 대조하고, 프레임 캡처 도구에서 실제로 바인딩된 리소스를 확인하세요.

  • 변형 폭증: 키워드 조합을 줄인 최소 셰이더로 같은 증상이 나는지 검사합니다.
  • 상수 버퍼 오염: CPU와 GPU 구조체의 정렬 및 패딩을 필드별로 대조합니다.
  • 상태 누수: 이전 패스의 블렌드·깊이·컬링 상태가 남았는지 확인합니다.
  • 캐시 착시: 셰이더 캐시를 지우기 전후의 빌드 결과와 해시를 비교합니다.

예외를 덧대기보다 최소 재현 장면을 보관합니다

세 번째 실수는 특정 모델이나 기기 이름을 조건문에 넣어 증상만 피하는 것입니다. 임시 분기는 다음 에셋이나 드라이버에서 다시 깨질 가능성이 높고, 포트폴리오 프로젝트의 코드 품질도 떨어뜨립니다. 문제 메시 하나, 재질 하나, 광원 하나만 남긴 최소 재현 장면을 만들면 수정 전후를 자동 캡처하거나 픽셀 차이로 비교할 수 있습니다.

최소 장면에는 정상 사례와 실패 사례를 나란히 배치하는 것이 좋습니다. 비균일 스케일 모델, 미러 UV 모델, 투명 재질처럼 경계 조건을 작은 테스트 묶음으로 보관하면 새 셰이더 기능을 추가할 때 회귀 오류를 일찍 발견할 수 있습니다. 화면이 정상으로 돌아온 뒤에도 원인을 일으킨 입력을 삭제하지 말고 테스트 에셋으로 남겨야 같은 고장을 다시 추적하는 비용을 줄일 수 있습니다.

  1. 문제가 발생하는 드로 호출 하나만 남깁니다.
  2. 카메라와 광원 값을 고정하고 자동 노출을 끕니다.
  3. 정상·실패 이미지를 동일 해상도로 저장합니다.
  4. 허용 오차를 둔 픽셀 비교 테스트를 실행합니다.
  5. 수정 코드와 함께 재현 장면을 버전 관리에 포함합니다.

특정 기기 예외를 먼저 추가하거나, 모든 중간값을 강제로 제한하거나, 캐시 삭제만 반복하는 습관은 원인을 숨깁니다. 입력 시각화, 렌더 패스 격리, 최소 재현 장면 보존을 순서대로 적용하면 검은 화면뿐 아니라 플랫폼별 깜빡임과 재질 왜곡까지 재현 가능한 개발 문제로 바꿀 수 있습니다.

셰이더 디버깅, 화면이 검게 깨지는 원인부터 잡는다

댓글목록

등록된 댓글이 없습니다.