[그래픽스] 1. 서론

Jaeyoung Seon·2022년 1월 26일
2

컴퓨터그래픽스

목록 보기
2/6
post-thumbnail
  1. 서론
    1.1. 그래픽스 영역
    1.2. 주요 어플리케이션
    1.3. 그래픽스 API
    1.4. 그래픽스 파이프라인
    1.5. 수치상의 문제
    1.6. 효율
    1.7. 그래픽스 프로그램 설계 & 코딩하기

1. 서론

컴퓨터 그래픽스 (computer graphics): 컴퓨터가 이미지를 생성하고 조작하는 모든 방법을 통칭

책 전반에 걸쳐 다양한 이미지 (사실적인 시각 효과, 유용한 기술적 삽화, 아름다운 컴퓨터 애니메이션 등)을 만들 수 있는 알고리즘적이고 수학적인 도구를 소개할 예정임.
그래픽스는 2차원, 3차원을 주로 다루며, 이미지들을 합성하거나 기존 이미지를 조작하여 새로운 이미지를 만들어낼 수 있음. 이 책은 3차원 물체나 장면에서 합성 이미지를 만들어내는 데 사용되는 핵심적인 알고리즘과 수학에 관한 책임.

컴퓨터 그래픽스에 뛰어들기 전에 특정 하드웨어, 파일 포맷, 그래픽스 API (Application Program Interface) 정도는 필수적으로 알아야 함. 다만 컴퓨터 그래픽스 영역이 급속도로 변하는 분야이기 때문에 앞서 말한 지식의 세부적인 부분은 계속 바뀔 것임. 그렇기 때문에 이 책에서는 최대한 특정적인 하드웨어나 API에 대한 설명을 피할 것이며, 하드웨어나 소프트웨어 환경에 대한 documentation을 통해 필요한 부분을 보충하길 바람. 다행히도 컴퓨터 그래픽스에 대한 용어나 개념이 어느 정도 표준화되어있어서 책에서 설명하는 내용이 대부분의 환경에서 잘 맞아떨어질 것.

이 장에서는 기본 용어를 정의하고, 역사적 배경이나 관련된 정보를 소개함.

1.1. 그래픽스 영역

어떤 영역이라도 카테고리를 나누는 것은 다소 위험한 일이지만, 대부분의 실무자들이 동의하는 영역이 존재함.

  • 모델링 (Modeling)은 표면 (shapte)과 겉모습에 대한 수학적 설명을 컴퓨터에 저장될 수 있는 방식으로 다룸.
  • 렌더링 (Rendering)은 미술 (art)로부터 물려받은 용어이며, 3D 컴퓨터 모델로부터 음영 처리된 이미지를 생성하는 과정을 다룸.
  • 애니메이션 (Animation)이미지를 여러 개 보여주어 움직이는 것처럼 보이게 하는 기술이며, 모델링과 렌더링을 사용하지만 이들이 다루지 않는 시간 경과에 따른 움직임이라는 개념이 추가되어있음.

이 외에도 컴퓨터 그래픽스를 발전시킨 다른 영역이 많고, 이들을 그래픽스의 핵심 영역으로 볼 것인지는 의견에 따라 다름.

  • 사용자 상호작용 (User Interaction)은 마우스나 태블릿과 같은 입력 장치 간의 인터페이스, 어플리케이션, 이미지에서의 사용자 피드백, 기타 감각 피드백 등을 다룸. 역사적으로 그래픽스를 연구하는 사람들이 지금은 어디서나 볼 수 있는 입출력 장치를 가장 먼저 다뤘기 때문에 이 영역은 그래픽스와 관련이 깊음.
  • 가상현실 (Virtual Reality): 사용자가 3D 가상 세계에 몰입할 수 있도록 만드는 기술. 최소 스테레오 그래픽과 헤드 모션에 대한 반응이 요구됨. 더 실제같은 가상현실을 만들기 위해서는 소리 및 힘에 대한 피드백이 잘 이뤄져야 함. 이 영역이 고급 3D 그래픽과 디스플레이 기술을 요구하기 때문에 그래픽스와 관련이 깊은 영역임.
  • 시각화 (Visualization): 시각 디스플레이를 통해 사용자에게 복잡한 정보에 대한 통찰력을 제공함. 종종 시각화 문제에서 다루어야 하는 그래픽 이슈가 존재하기도 함.
  • 이미지 처리 (Image Processing): 2D 이미지에 대한 조작을 다루며, 그래픽스 및 비전 분야에서 모두 사용되는 기술임.
  • 3D 스캐닝 (3D Scanning): 측정된 3D 모델을 생성하기 위해 범위 탐색 기술을 사용함. 이 모델들은 풍부한 시각적 이미지 생성에 유용하며, 모델 처리에는 대부분 그래픽스 알고리즘이 사용됨.
  • 컴퓨팅적 촬영법 (Computational Photography): 물체나 장면, 환경 등을 사진으로 포착하는 새로운 방법을 가능하게 하기 위해 컴퓨터 그래픽스, 컴퓨터 비전, 이미지 처리 방식을 사용함.

1.2. 주요 어플리케이션

모든 분야에서 컴퓨터 그래픽스가 활용될 수 있지만, 이를 주로 활용하는 몇 가지 산업 분야가 있음.

  • 비디오 게임: 점점 정교한 3D 모델과 렌더링 알고리즘을 사용하고 있음.
  • 만화: 3D 모델로부터 직접 렌더링되는 경우도 있음. 전통적인 2D 만화의 경우 많은 시간을 들여 그림을 그리지 않아도 연속적으로 움직이는 시점이 적용된 3D 모델로부터 배경을 렌더링할 수 있음.
  • 시각 효과: 컴퓨터 그래픽스의 거의 모든 기술을 사용함. 대부분의 현대 영화에서는 촬영된 여러 배경을 덧씌우기 위해 디지털 합성을 사용함. 많은 영화는 합성된 환경이나 물체, 심지어 실제와 비슷한 캐릭터를 만들기 위해 3D 모델링과 애니메이션을 활용함.
  • 애니메이션 영화: 시각 효과에서 쓰는 기술의 대부분을 사용하지만 반드시 실제와 비슷할 필요는 없음.
  • CAD/CAM: 컴퓨터 지원 설계 (computer-aided design)컴퓨터 지원 제조 (computer-aided design)을 대표하는 기술. 이 분야에서는 컴퓨터 기술을 사용하여 컴퓨터의 부품 및 제품을 설계하고, 가상 설계를 활용하여 제조 과정에 대한 안내를 제공함. 예를 들어, 많은 기계 부품이 3D 컴퓨터 모델링으로 설계되고 컴퓨터가 제어하는 밀링 장치 (computer-controlled milling device)가 자동으로 생산함.
  • 시뮬레이션: 정교한 비디오 게임처럼 느껴질 수 있는 영역. 예를 들어, 비행 시뮬레이터는 정교한 3D 그래픽을 사용하여 비행기 조종 경험을 시뮬레이션함. 시뮬레이션을 통해 운전과 같이 안전이 중요한 분야에 대한 초기 훈련을 수행할 수 있고, 비용이 많이 들거나 위험해서 실제로 해볼 수 없는 화재 진압과 같은 상황에 대한 시나리오를 훈련할 수 있음.
  • 의학 촬영: 환자 데이터를 스캔하여 의미 있는 이미지를 생성함. 예를 들어, 컴퓨터 단층 촬영 (Computed Tomography. CT)의 데이터 집합은 밀도 값의 거대한 3D 사각형 배열로 구성되어 있음. 컴퓨터 그래픽스를 사용하여 음영 이미지 (shaded image)를 생성하면 의사는 데이터로부터 핵심 정보를 추출함.
  • 정보 시각화: "자연스러운" 시각 묘사가 필요하지 않은 데이터에 대한 이미지를 생성함. 예를 들어, 10개의 주식에 대한 일시적인 주가 변동은 뚜렷한 시각 묘사가 나타나지는 않지만, 똑똑한 그래핑 기술을 사용하면 이러한 데이터 속에서 특정 패턴을 찾아낼 수 있음.

1.3. 그래픽스 API

그래픽스 라이브러리를 사용하기 위해서는 그래픽스 API를 다루는 것이 중요함.

API (Application Program Interface): 관련된 연산들을 수행하기 위한 함수들의 모음
그래픽스 API: 이미지 및 3D 표면을 스크린 상의 창에 그리는 등의 기본적인 연산을 수행하기 위한 함수들의 모음

모든 그래픽 프로그램들은 시각적인 출력을 위한 그래픽스 API사용자로부터 입력을 받기 위한 사용자 인터페이스 API를 사용해야 함. 현재 두 가지 API에 대한 지배적인 패러다임이 존재함.
첫 번째는 통합 접근법으로, Java로 대표되는 이 패러다임은 완전히 표준화되고 언어에 의해 지원되는 통합 및 휴대용 패키지로서 그래픽스 및 UI 툴킷을 제공함.
두 번째는 Direct3D & OpenGL인데, 그리기 명령은 C++ 같은 언어와 연결된 소프트웨어 라이브러리의 일부이며, UI 소프트웨어는 시스템마다 다른 독립 엔티티 (entity)임.

두 번째 접근법의 경우 간단한 프로그램을 위해 휴대용 라이브러리 계층을 사용하여 시스템별 UI 코드를 캡슐화 (encapsulate)할 수 있지만 휴대용 코드를 작성하기에는 문제가 있음.
어떤 API를 선택하든 기본적인 호출은 거의 비슷하며, 이 책에서 설명할 개념이 적용될 것임.

1.4. 그래픽스 파이프라인

오늘날 모든 데스크탑 컴퓨터는 강력한 3D 그래픽스 파이프라인을 가지고 있음.

그래픽스 파이프라인 (graphics pipeline): 원근법에 따라 효율적으로 3D 요소를 그리기 위한 소프트웨어/하드웨어 서브시스템.

보통 이러한 시스템들은 공유 정점 (shared vertex)을 포함하는 3D 삼각형을 처리하기 위해 최적화되어있는 경우가 많음. 파이프라인의 기초 연산을 사용하여 3D 정점 위치 (location)를 2D 스크린 위치 (position)에 매핑하며, 삼각형에 음영을 처리하여 실제처럼 보이게 하고, back to front 순서대로 나타나게 할 수 있음.

삼각형을 유효한 back to front 순서로 그리는 것이 컴퓨터 그래픽스에서 가장 중요한 연구 이슈이지만, z 버퍼 (z-buffer)를 사용하여 이제는 대부분 해결된 이슈임.

z 버퍼: 어떤 문제를 브루트 포스 (brute-force) 방식으로 해결하기 위한 특별 메모리 버퍼

그래픽스 파이프라인에서 사용되는 기하학적 조작은 3가지 전통적인 기하학적 좌표와 원근법을 지원하는 4번째 동차 좌표 (homogeneous coordinate)로 구성된 4D 좌표 공간에서 전적으로 수행될 수 있음. 이러한 4D 좌표는 4x4 행렬과 4차원 벡터를 사용하여 조작될 수 있음. 따라서 그래픽스 파이프라인은 효율적인 처리와 행렬 및 벡터 구성을 위한 기계를 포함함.
4D 좌표계는 컴퓨터 과학에서 사용되는 가장 교묘하고 아름다운 구조 중 하나이며, 컴퓨터 그래픽스를 배울 때의 가장 큰 지적 허들과 같음. 모든 그래픽스 책의 첫 장에서는 이러한 좌표계를 다룸.

이미지의 생성 속도는 그려야 하는 삼각형의 개수에 따라 크게 달라짐. 많은 어플리케이션에서 시각적인 퀄리티보다 상호작용성 (interactivity)이 중요하기 때문에, 모델 표현에 사용되는 삼각형의 개수를 줄이는 것이 중요함. 만약 모델을 멀리서 바라보는 경우 가까이서 볼 때보다 삼각형 개수를 더 줄일 수 있음. 따라서 다양한 Level of Detail (LOD)을 사용하여 모델을 표현하는 것이 좋음.

1.5. 수치상의 문제

그래픽스 프로그램의 대부분은 3차원 숫자 코드 (3D numerical code)임. 이때 발생하는 수치상의 문제 (numerical issue)는 어떤 프로그램에서는 중요한 문제일 수도 있음.
과거에는 기계마다 내부적으로 숫자를 표현하는 방식이 달랐음. 심지어 서로 다른 방식으로 예외가 처리되었기 때문에 강력하고 휴대가 쉬운 방식으로 이러한 문제를 해결하는 것이 매우 어려웠음.
다행히도 현대 컴퓨터들은 IEEE 표준화 기구에서 제정한 부동소수점 표준을 준수하고 있음. 이를 통해 특정 숫자 조건이 처리될 방법에 대해 개발자는 편리한 추정을 만들 수 있음.

숫자 알고리즘 코딩에 유용한 여러 기능이 IEEE 부동소수점에 포함되어 있음에도 불구하고 그래픽스에서 접할 수 있는 대부분의 상황에서 알아야 할 중요 기능은 몇 가지에 불과함. 그 중 IEEE 부동소수점에서 제공하는 다음 세 가지 실수에 대한 "특수" 값을 이해하는 것이 가장 중요함.

  1. 무한 (∞): 어떤 다른 유효숫자 (valid number)들보다 더 큰 유효숫자
  2. 마이너스 무한 (-∞): 어떤 다른 유효숫자들보다 더 작은 유효숫자
  3. Not a Number (NaN): "0 나누기 0"과 같이 결과가 명확하지 않은 연산에서 발생하는 비유효숫자 (invalid number)

IEEE 부동소수점의 설계자들은 프로그래머가 편하게 사용할 수 있도록 몇 가지 결정을 내렸음. 대부분은 division by zero와 같은 예외 처리에 있어 위에서 설명한 3가지 특수 값과 관련되어 있음. 이 경우 예외는 기록되지만 대부분 프로그래머는 이를 무시할 수 있음. 구체적으로, 어떤 양의 실수 aa에 대해 다음 법칙이 division by infinite values에 적용됨.

+a/(+)=+0,+a/(+\infin)=+0,
a/(+)=0,-a/(+\infin)=-0,
+a/()=0,+a/(-\infin)=-0,
a/()=+0-a/(-\infin)=+0


무한을 포함하는 다른 연산은 예상대로 동작함. 다시 양수 aa에 대해, 동작은 다음과 같음.
+=+,\infin+\infin=+\infin,
=\infin-\infin=NaN,
×=,\infin\times\infin=\infin,
/=\infin/\infin=NaN,
/a=,\infin/a=\infin,
0/0=0/0=NaN


무한을 포함하는 boolean 표현식은 역시 예상대로임.
1. 모든 유한한 유효숫자는 +∞보다 작다.
2. 모든 유한한 유효숫자는 -∞보다 크다.
3. -∞ < +∞


NaN이 포함된 규칙은 더 간단함.
1. NaN을 포함하는 모든 산술식의 결과는 NaN이다.
2. NaN을 포함하는 모든 boolean 표현식의 결과는 false이다.


IEEE 부동소수점의 가장 유용한 점은 divide by zero가 처리되는 방식일 것. 실수 aa에 대해 division by zero가 포함된 규칙은 다음과 같음.
+a/+0=+,+a/+0=+\infin,
a/+0=-a/+0=-\infin


프로그래머가 IEEE 규칙을 이용하면 더욱 간단하게 해결할 수 있는 수치 계산이 존재함.

a=11b+1ca=\frac{1}{\frac{1}{b}+\frac{1}{c}}

이러한 표현식은 저항기 (resistor)렌즈(lense)에서 나타남. 만약 divide by zero가 발생하여 프로그램 충돌 (program crash)이 발생했다면, b와 c가 0보다 작거나 같은지 확인하기 위해 2개의 if문이 필요함. 대신 IEEE 부동소수점에서는 b 또는 c가 0이면 문제 없이 a의 값은 0이 될 것임. 이러한 체크를 피하기 위한 다른 기술은 NaN에 대한 boolean 특성을 이용하는 것임.

a = f(x)
if (a > 0) then
	do something

여기서 함수 f는 ∞나 NaN과 같은 "보기 싫은" (ugly) 값을 반환하지만 if문은 여전히 잘 정의되어 있음. a = NaN이나 a = -∞에 대해 if문은 false임. 어떤 값이 반환되는지 주의깊게 보면 if문은 특별 체크 없이 옳은 선택을 할 수 있을 것. 이러한 설계는 프로그램을 더 작고, 강력하고, 효율적으로 만듦.

1.6. 효율

코드를 더 효율적으로 짜는 것에 있어 만능 규칙은 없음. 효율은 세심한 균형 (careful tradeoff)에 의해 결정되며, 이는 아키텍처에 따라 다름. 가까운 미래에 좋은 휴리스틱 (heuristic)은 프로그래머가 연산의 개수보다 메모리 접근 패턴에 더 신경쓰도록 하는 것임. 이는 20년 전의 베스트 휴리스틱과는 정확히 반대임. 이러한 변화는 메모리의 속도가 프로세서의 속도를 따라가지 못하기 때문에 발생하였음. 이러한 추세가 계속되고 있기 때문에, 최적화를 위한 제한적이고 일관적인 메모리 접근이 증가하고 있음.

코드를 빠르게 하는 적절한 접근법은 다음 순서를 따르는 것.

  1. 코드를 가능한 복잡하지 않게 (straightforward) 작성하라. 중간 결과를 저장하지 않고 필요에 따라 바로바로 계산하라.
  2. 최적화된 환경 속에서 컴파일을 진행하라.
  3. 프로파일링 도구를 활용하여 심각한 병목 현상 (bottleneck)을 찾을 수도 있다.
  4. 지역성 (locality)을 향상시키기 위해 적절한 자료구조를 찾아라. 가능하면 데이터 단위의 크기를 대상이 되는 아키텍처의 캐시/페이지 크기에 맞도록 조절할 수 있다.
  5. 수치 계산 과정에서 프로파일링 도구가 병목 현상을 발견했다면, 놓친 효율을 챙기기 위해 컴파일러가 생성한 어셈블리 코드를 확인하라. 발견한 모든 문제를 해결하기 위해 소스코드를 수정하라.

5단계 중 가장 중요한 단계는 1단계임. 대부분의 최적화 작업은 속도 향상은 커녕 코드를 더 읽기 어렵게 만듦. 게다가 최적화 작업 전에 소요된 시간은 버그 수정이나 기능 추가에 사용된 시간임. 실수 대신 정수를 사용하는 등의 고전적인 트릭은 현대 CPU가 정수 연산만큼 부동소수점 연산 또한 빠르게 수행할 수 있기 때문에 더이상 속도 향상을 위한 트릭이 아님. 모든 상황에서 프로파일링은 특정 기계와 컴파일러 최적화의 장점을 보장하기 위해 꼭 필요한 작업임.

1.7. 그래픽스 프로그램 설계 & 코딩하기

그래픽스 프로그래밍에서 특정 전략은 유용함. 이 장에서는 책에서 배운 방법들을 직접 구현하면서 도움이 될만한 조언을 제공할 것임.

1.7.1. 클래스 설계

그래픽스 프로그램의 핵심은 벡터나 행렬 같은 기하학적인 요소뿐만 아니라 RGP 백이나 이미지 같은 그래픽적인 요소를 구현하기 위한 클래스와 루틴임. 루틴은 가능한 깔끔하고 효율적으로 작성되어야 함. 위치와 변위 (displacement)가 서로 다른 연산을 가지기 때문에 보편적으로 설계 시 고민해야 할 것은 두 요소가 분리된 클래스여야 하는지임. 예를 들어, 위치의 절반 (1/2)은 기하학적 의미가 없는 반면 변위의 절반은 기하학적 의미가 존재함. 이 고민은 그래픽스 전문가들 사이에서 열띤 토론이 일어날 수 있을 정도로 의견이 일치하지 않지만, 예를 들어 두 개를 구별하지 않는다고 가정함.

이는 몇 가지 기초 클래스가 다음을 포함해야 한다는 것을 의미함.

  • vector2: x, y 요소를 저장하는 2D 벡터 클래스. 각 요소를 길이 2 배열에 저장하여 인덱스 연산자 []를 지원할 수 있음. 또한 벡터 더하기, 빼기, 내적, 외적, 스칼라곱, 스칼라 나누기 등의 연산도 포함함.
  • vector3: vector2와 유사한 3D 벡터 클래스.
  • hvector: 4개의 요소를 갖는 동종 (homogeneous) 벡터.
  • rgb: 3개의 요소를 저장하는 RGB 컬러. RGB 더하기, 빼기, 곱, 스칼라곱, 스칼라 나누기 등의 연산을 지원함.
  • transform: 변환을 위한 4x4 행렬. 위치, 방향, 표면 법선 벡터를 적용하기 위한 행렬 곱셈 및 멤버 함수를 지원함.
  • image: 출력 연산을 포함하는 RGB 픽셀의 2차원 배열.

추가적으로 간격, 직교 기저, 좌표계와 같은 클래스를 추가할 수 있음.

1.7.2. Float vs Double

현대 아키텍처에서는 메모리 사용량을 줄이고 메모리 접근을 일관되게 유지하는 것이 효율성 향상의 핵심임. 이를 위해 단일 정밀도 (single precision)를 사용함. 다만 수치 문제를 피하기 위해서는 이중 정밀도 산술 (double precision arithmetic)을 사용함. 이는 프로그램에 의존하는 경향이 있지만 클래스 정의에 기본으로 갖고 있는 것이 좋음.

1.7.3. 그래픽스 프로그램 디버깅

주변에 물어보면 프로그래머들이 경험이 많아질수록 전통적인 디버거를 더 적게 사용한다는 사실을 알 수 있음. 그 이유 중 하나는 이러한 디버거를 사용하는 것이 복잡한 프로그램에서는 더 어색하기 때문임. 다른 이유는 가장 해결하기 어려운 에러가 잘못된 것이 구현되는 개념적 오류이며, 이를 탐지하지 않고 변수 값을 처리하는 데 시간을 낭비하기 쉽기 때문임. 그래픽스에서 유용하게 사용할 수 있는 디버깅 전략이 몇 가지 존재함.


과학적인 방법
그래픽스 프로그램에는 전통적인 디버깅에 대한 대안이 존재하는데, 이는 매우 유용함. 다만 단점은 프로그래머가 커리어 초반에 "이건 절대 하지 마세요" 하는 것과 비슷하다는 점. 그러면 문제의 원인이 무엇인지 확인하고 테스트할 수 있는 가설을 개발할 수 있음. 예를 들어, 레이트레이싱 프로그램에서 다소 무작위하게 보이는 어두운 픽셀을 많이 볼 수 있음. 이는 레이트레이싱을 개발하고 실행할 때 자주 볼 수 있는 "그림자 여드름" 문제임.

그림자 여드름.

이때 전통적인 디버깅은 도움이 되지 않음. 대신 그늘이 지면서 그림자 광선이 표면에 내리쬐고 있다는 사실을 알아야 함. 어두운 부분이 주변 색상 (ambient color)임을 알 수 있으므로 직접 조명 (direct lighting)은 잃어버린 부분임. 직접 조명은 그림자 속에서 보이지 않을 수 있으므로 이 점들은 그림자가 아닌 것처럼 태그가 잘못 지정되고 있다고 가정할 수 있음. 가설을 테스트하기 위해 그림자 체크를 끄고 재컴파일 할 수 있음. 이것을 가짜 그림자 테스트 (false shadow test)라고 부름. 이 방법이 좋은 연습이 될 수 있는 이유는 가짜 값 (false value)을 찾을 필요 없이 개념적인 오류를 확인할 수 있기 때문임. 대신 개념적 오류를 실험적으로 좁힐 수 있음. 보통 몇 번의 실험을 거치면 찾을 수 있고, 이러한 디버깅은 "즐거움".


코드화된 디버깅 출력으로써의 이미지
그래픽스 프로그램에서 디버깅 정보를 얻을 수 있는 가장 쉬운 채널은 출력 이미지 그 자체임. 각 픽셀마다 실행되는 일부 연산의 변수 값을 알고 싶다면 출력 이미지에 직접 값을 복사하고, 평범하게 할 수 있는 나머지 계산을 건너뛰어 일시적으로 프로그램을 수정할 수 있음.
예를 들어, 만약 표면 법선에 대한 문제가 셰이딩에 대한 문제를 야기한다면, 법선 벡터를 직접 이미지에 복사 (x -> red, y -> green, z -> blue)하여 벡터의 색상으로 코딩된 삽화 (color-coded illustration)를 연산에 활용함.
혹은, 만약 특정 값이 유효 범위를 벗어나지는 않을지 걱정된다면 프로그램에 밝은 빨간색 픽셀을 사용함. 다른 트릭은 뒷 표면을 명확한 색상으로 그리는 것이며, (이 표면은 보이지 않음) 물체의 ID 번호로 이미지를 색칠하거나 계산에 소요되는 작업량에 따라 픽셀을 색칠하는 방법임.


디버거 사용하기
과학적인 방법이 모순을 초래한 것 같거나 정확히 무슨 일이 일어나고 있는지 관찰하는 것 말고는 다른 방법이 없는 경우가 있음.
문제는 그래픽스 프로그램들이 같은 코드를 여러 번 실행하기 때문에 (픽셀당, 혹은 삼각형 당) 처음부터 디버거를 거치는 것은 비현실적인 일임. 또한 해결하기 어려운 버그들은 보통 복잡한 입력 때문에 발생함.

유용한 접근법은 버그에 대해 "트랩을 설치하는 것"임. 먼저 프로그램을 결정론적 (deterministic)으로 만들어서 단일 스레드에서 실행하고 모든 난수가 고정 시드에서 계산될 수 있도록 해야 함. 그런 다음 어떤 픽셀이나 삼각형이 버그를 발생시키는지 알아내고 버그 발생이 의심되는 코드가 올바르지 않은 경우에만 실행되는 구문을 앞에 추가함. 예를 들어, (126, 247) 픽셀에서 버그가 발생하면, 아래 구문을 추가할 수 있음.

if x == 126 and y == 247 then
	print "blarg!"

출력 구문에 중단점 (breakpoint)을 지정하면, 해당 픽셀이 연산되기 전에 디버거에 걸리게 됨. 어떤 디버거는 조건부 중단점 (conditional breakpoint) 기능이 존재하여 코드를 수정하지 않고 같은 작업을 수행할 수 있음.

프로그램이 고장날 때를 대비해서, 전통적인 디버거는 충돌 지점을 정확히 찾아낼 때 유용함. 그런 다음 assert나 재컴파일을 통해 프로그램을 역추적 (backtracking)하여 어느 지점에서 잘못되었는지 찾아낼 수 있음. assert는 앞으로 발생할 지 모를 버그 때문에 프로그램에 남아있게 될 것. 프로그램에 가치 있는 assert를 추가하지 않기 때문에 이는 전통적인 단계별 프로세스를 피한다는 것을 의미함.

디버깅을 위한 데이터 시각화
프로그램이 잘못되기 전에 많은 중간 결과를 연산하기 때문에, 프로그램이 무엇을 하는지 이해하기 어려운 경우가 많음. 이러한 상황은 많은 데이터를 측정하는 과학 실험과 비슷하며, 한 해결책은 동일함. "플롯 (plot)이나 그림을 잘 그려서 데이터가 무엇을 의미하는지 이해할 수 있도록 하는 것"
예를 들어, 레이트레이싱 프로그램에서 광선 트리 (ray tree)를 시각화하기 위한 코드를 작성하여 어떤 경로가 픽셀에 기여하는지, 혹은 이미지 리샘플링 루틴에서 입력으로부터 샘플을 추출하는 모든 지점 (point)을 보여주는 플롯을 그릴 수 있음. 프로그램의 내부 상태를 시각화하기 위한 코드를 작성하는데 시간을 쓰면 최적화 시 동작을 더 잘 이해할 수 있음.

profile
재밌는 인생을 살자!

1개의 댓글