2011년 12월 26일 월요일

[포프의 쉐이더 입문강좌] 04. 기초적인 조명쉐이더 Part 1

이전편 보기


샘플파일받기

제4장 기초적인 조명쉐이더

이 장에서 새로 배우는 HLSL

  • NORMAL: 정점의 법선정보를 불러올 때 사용하는 시맨틱
  • normalize(): 벡터 정규화 함수
  • dot(): 내적 함수
  • saturate(): 0 ~ 1을 넘어서는 값의 범위를 짤라 냄.
  • reflect(): 벡터반사 함수
  • pow(): 거듭제곱 함수


이 장에서 새로 사용하는 수학
  • 내적: 코사인 함수를 재빨리 계산할 때 사용할 수 있음
  • 정규화: 벡터를 단위벡터(길이가 1인 벡터)로 만듬.


빛이 존재하지 않는다면 물체를 볼 수 없습니다. 매우 당연한 이치인데도 이걸 까먹고 지내는 분들이 많은 것 같습니다. (저도 종종 까먹습니다.) 예를 들어, 창문이 하나도 없는 방에 들어가서 문을 닫아버리면 아무것도 볼 수가 없지요? 어디서 새어 들어오는 빛이 있지 않는 한 아무리 어둠 속에서 오래 있어도 아무것도 보이지 않습니다. 이 당연한 사실을 자꾸 까먹는 이유는 실생활에서 완전히 칠흑 같은 어둠을 찾기가 쉽지 않기 때문입니다. 왜일까요? 바로 끝없이 반사하는 빛의 성질 때문입니다. 딱히 눈에 뜨이는 광원이 없더라도 대기중의 미세입자에 반사되어 들어오는 빛까지 있으니까요. 이렇게 다른 물체에 반사돼서 들어오는 빛을 간접광이라 합니다. 반대로 직접광은 광원으로부터 직접 받는 빛입니다. 그림 4.1에 예를 들어보겠습니다.

그림 4.1 직접광과 간접광의 예

직접광과 간접광 중에서 어떤 것을 더 쉽게 계산할 수 있을까요? 위 그림만 봐도 딱 답이 나오죠? 직접광입니다. 간접광은 수없이 반사의 반사를 거치므로 당연히 직접광보다 계산하기 어렵습니다. 간접광을 계산하는 방법 중 하나로 광선추적(ray-tracing)이라는 기법이 있습니다. 아마 3D 그래픽에 관심이 많으신 분들이라면 최근 들어 광선추적에 대해 논하는 많은 자료를 보셨을 겁니다. 하지만, 아직도 실시간 광선추적기법이 게임에서 널리 사용되지 않는 이유는 하드웨어 사양이 따라주지 않기 때문이라죠. (특히 콘솔 하드웨어의 하드웨어 사양이 더 큰 문제입니다.) 그렇기 때문에 아직도 컴퓨터 게임 등을 비롯한 실시간 3D 프로그램에서는 주로 직접광만을 제대로 계산하고 간접광은 흉내내기 정도로 그치는 게 보통입니다. 따라서 이 장에서도 직접광만을 다루도록 하겠습니다. (간접광까지도 다루는 조명모델을 전역조명모델(global illumination model)이라고 합니다. 반대로 직접광만을 다루는 조명모델을 지역조명모델(local illumination model)이라 합니다.)  참고로 이 장에서 배우는 조명 쉐이더는 아직까지도 대부분의 게임에서 표준으로 사용하는 기법이므로 잘 숙지해 두세요.

빛을 구성하는 요소는 크게 난 반사광(diffuse light)과 정 반사광(specular light)이 있습니다. 이 둘을 따로 살펴보도록 하겠습니다.

난 반사광
배경
대부분의 물체는 스스로 빛을 발하지 않습니다. 그럼에도 저희가 이 물체들을 지각할 수 있는 이유는 다른 물체(예, 태양)가 발산하는 빛이 이 물체의 표면에서 반사되기 때문입니다. 이 때, 여러 방향으로 고르게 반사되는 빛이 있는데 이것을 난 반사광(diffuse light)이라고 합니다. (diffuse 광은 아직도 용어정립이  안되고 있습니다따라서  용어를 사용할  때마다 종종 영문 표기를 같이 하도록 하겠습니다다른 용어로는 산란광확산광 등이 있는데  반사광이 가장 적합한  같습니다.) 어느 방향에서 바라봐도 물체의 명암이나 색조가 크게 변하지 않는 이유를 아시나요? 여러 방향으로 고르게 퍼지는 난 반사광 덕분입니다. 만약 빛이 한 방향으로만 반사된다면(이것이 뒤에서 살펴볼 정 반사광입니다.) 그 방향에서만 물체를 지각할 수 있겠지요.

참고로 물체의 표면이 거칠수록 난반사가 심해지는 것이 보통입니다. (표면이 완전히 매끈하더라도 난반사가 완전히 사라지는 경우는 극히 드뭅니다. 표면을 뚫고 들어간 뒤, 물체 내부에서 반사되는 빛도 있기 때문입니다.)

일단 난 반사광을 그림으로 그려 보겠습니다.

그림 4.2. 난 반사광

그림 4.2에서 아직 보여 드리지 않은 것이 조금 후에 배워 볼 정 반사광입니다. 정 반사광이 무엇인지는 나중에 알려 드릴 테니 일단은 입사광 중의 일부는 난 반사광이 되고 다른 일부는 정 반사광이 된다고만 기억해 두세요.

자, 그렇다면 수학적으로 난 반사광을 어떻게 계산할까요? 당연히 수학자마다 다른 주장을 하지만 그 중에서 게임에서 주로 사용하는 람베르트(lambert) 모델을 살펴봅시다. 요한 람베르트라는 수학자가 창시한 람베르트 모델은 표면법선(법선(normal)이란 표면의 방위(orientation)를 나타내는 벡터입니다. 따라서 그림 4.2에서처럼 좌우로 평평한 평면의 법선은 위쪽으로 수직인 선이 됩니다.)과 입사광이 이루는 각의 코사인 값을 구하면 그게 바로 난 반사광의 양이라고 합니다. 그렇다면 일단 코사인 함수의 그래프를 볼까요?


그림 4.3. y = cos(x) 그래프

위 그래프를 보시면 입사광과 표면 법선의 각도가 0일 때, 결과(y축의 값)가 1인 거 보이시죠? 그리고 각도가 늘어날수록 결과가 점점 작아지다가 90도가 되니 0이 돼버립니다. 여기서 더 나아가면 그 후로는 아예 음수 값이 돼버리네요? 그러면 실제 세계에서 빛의 각도에 따라 결과가 어떻게 바뀌는지 살펴 볼까요?

그림 4.4. 입사광과 법선이 이루는 다양한 각도

위의 그림에서 평면이 가장 밝게 빛나는 때가 언제일까요? 당연히 해가 중천에 떠있을 때겠죠? (그림 a) 그리고 해가 저물어감에 따라 점점 표면도 어두워지겠네요. (그림 b) 이제 해가 지평선을 넘어가는 순간, 표면도 깜깜해집니다. (그림 c) 그렇다면 해가 지고 난 뒤엔 어떻게 되죠? 여전히 표면이 깜깜하겠죠? 표면에 전혀 빛이 닿지 않으니까요. 자, 그럼 이 현상을 그래프로 그려보면 어떻게 될까요? 법선과 해가 이루는 각도를 X축으로 두고 표면의 밝기를 Y축으로 하겠습니다. 여기서 Y축이 가지는 값의 범위는 0~1인데0은 표면이 아주 깜깜한 때를(0%), 1은 표면이 최고로 밝은 때(100%)를 나타냅니다.

그림 4.5. 관찰결과를 그려본 그래프

위 그래프에서 -90 ~ 90도사이의 그래프에 물음표를 달아둔 이유는 각도가 줄어듦에 따라 얼마나 빠르게 표면이 어두워지는지를 모르기 때문입니다. 이제 이 그림을 그림 4.3과 비교해 볼까요? 그림 4.3에서 결과가 0 이하인 부분들을 0으로 만들면 지금 만든 그래프와 꽤 비슷하네요? 차이점이라고는 -90 ~ 90도 사이에서 그래프가 떨어지는 속도가 조금 다르다 뿐이군요. 그렇다면 람베르트 아저씨가 표면이 어두워지는 속도를 아주 꼼꼼히 잘 관찰한 뒤에, 위 코사인 공식을 만들었다고 믿어도 될까요? 전 그렇게 믿고 있습니다. -_-

자, 그럼 람베르트 모델을 적용하면 코사인 함수 한 번으로 난 반사광을 쉽게 구할 수 있겠군요! 하지만 코사인 함수는 그다지 값싼 함수가 아니어서 쉐이더에서 매번 호출하는 것이 영 꺼림직합니다. 다른 대안이 없을까요? 수학책을 뒤적여 보니까 내적(dot product)이라는 연산이 코사인을 대신할 수 있다고 나오는 걸요?

θ = A와 B가 이루는 각도
| A |  = 방향벡터 A의 길이
| B |  = 방향벡터 B의 길이


A ∙ B = cosθ | A || B |

즉,

cosθ = (A ∙ B) ÷ (| A |ⅹ| B |);

위의 내적 공식에 따르면 두 벡터가 이루는 각의 코사인 값은 그 둘의 내적을 구한 뒤 두 벡터의 길이를 곱한 결과로 나눈 것과 같습니다. 여기서 두 벡터의 길이를 1로 만들면 공식을 더 간단히 만들 수 있습니다.

cosθ = (A' ∙ B')

두 벡터가 이루는 각의 코사인 값은 두 벡터의 내적과 같다는 군요. 근데 이렇게 저희 맘대로 벡터의 길이를 바꿔도 되는 걸까요? 이 질문을 다르게 표현하면, '난 반사광을 계산할 때 법선의 길이나 입사광 벡터의 길이가 중요한가요?'입니다. 전혀 그렇지 않지요? 두 벡터가 이루는 각이 중요할 뿐 벡터의 길이는 결과에 아무런 영향을 미치지 않습니다. 따라서 이 두 벡터의 길이를 각각 1로 만들어서 공식을 간단하게 만드는 게 훨씬 나아 보이는군요. (이렇게 길이가 1인 벡터를 단위벡터(unit vector)라고 하며, 단위벡터를 만드는 과정을 정규화(normalize)라고 합니다.)

그럼 내적이 코사인 함수보다 값싼 연산인 이유를 살펴볼까요? 벡터 A의 성분을  (a, b, c)로 두고 벡터 B의 성분을 (d, e, f)로 두면 두 벡터의 내적을 이렇게 간단히 구할 수 있습니다.

A ∙ B = (a ⅹ d) + (b ⅹ e) + (c ⅹ f)

코사인 함수보다 훨씬 간단해 보이는 게 맞죠? 당장 코사인 함수를 구하라고 하면 머리부터 긁적이실 걸요? ^^

자, 그럼 이 정도면 난 반사광에 대해 충분히 설명을 드린 것 같으니 지금 배운 내용들을 까먹기 전에 곧바로 쉐이더를 작성해 보겠습니다.




댓글 12개:

  1. 강좌 고맙습니다.
    수학이네요..,,ㅠㅠ;;;;

    이번 주는 실습이 없어서, 지난 강좌들을 열심히 반복학습해야겠습니다.

    지난주 것까지 코드(?)를 외웠습니다. 이 방식이 맞는지는 모르지만은, 일단 기억해 놓고, 강좌를 계속 반복 학습중입니다. 계속 하다 보면 이해 되겠죠, ^^;;;

    아무튼.
    새해에도 좋은 강좌 부탁합니다. ^^
    새해 복 많이 받으세요..

    답글삭제
  2. 사람따라 방법이 달라서. 어느방법이 맞다고는 못하죠.. 이번주에 코드를 안보여드린 이유는.... 너무 길어서.... 담주에 올라갈거에요.. ^_^

    저도 코드부터 먼저 하고 나중에 차차 이해가 되다가 어느순간 눈떠진 케이스라서요... 뭐 조명코드 정도야 한 번 고민해서 작성해놓으면 3대가 물려받아 쓸수 있습니다(는 좀 구라고.. 한 3년은 욹어먹을수 있죠 -_-)

    뭐 이번 강좌는 수학을 이해한다기 보다는... 조명이 대충 저런거고... 코사인 함수가 대충 저렇게 생겼구나... 0~90도 사이에서 100% -> 0% 로 떨어지는구나... 곡선을 그리는구나... 그래서 수학자들이 대충 저거 써먹는구나... 하는 정도로만 이해하시면 충분...!

    새해 복 많이 받으세요 ^^

    답글삭제
  3. 뭔가, 편리함 보다는 최적화와 다이어트군요!
    오늘도 감사합니다 ㅠㅠ

    답글삭제
  4. 엉엉 일주일을 어떻게 기다린담 ㅠ_ㅠ

    항상 잘 보고 있습니다.

    게.개.포 블로그도 잘 보고 있습니다. :D

    답글삭제
  5. 게개포~ 라고 하니 어감 참 좋네요 ㅎㅎㅎ ^_^

    답글삭제
  6. 정말 명강의 입니다. 해골책부터 시작해서 게임관련책을 꽤나 보았지만 'diffuse light 가 이거구나' 하는 느낌은 처음받았네요.

    답글삭제
    답글
    1. 감사합니다. 저도 다른 자료들에서 딱 확실히 이해가 안되서..나름대로 설명할 방법을 오래 찾은 끝에 나온 설명이거든요 ^_^

      삭제
  7. 맙소사;; 이렇게 쉽게 알게 된건 처음입니다. 맨날 남들이 짜놓은 것만 사용하다보니 그냥 당연히 사용하던 내적이 이런뜻이 있었군요.

    답글삭제
  8. 저..계속 익명으로 댓글 달고 있는 사람입니다. 추판된 도서, 블로그 양쪽봐가며 공부중인데, 혹시 출판된 부분(뒷부분 목차들)에 대한 질문 포스팅은 어디에 하나요? 제가 질문이 있어서도 그렇겠지만, 다른 분들이 공부중에 의문점 여쭤들 보시고 포프님이 그에 대한 답변해 주시는 걸 보는 것도 상당한 공부가 되는 것 같습니다. 뒤로 갈수록 내용이 어려워지는 거 같아서..뭐 앞부분도 어렵지만ㅋㅋ 암튼, 혹시 뭔가 운영이 되고 있는 건데 제가 모르는 거라면 알려주세요~ 감사합니다! 꾸벅

    답글삭제
    답글
    1. 이미 답한 질문에 다시 또 질문을 올리실 이유가 있었나요?

      http://kblog.popekim.com/2012/01/04-part-2.html

      삭제
  9. 감사합니다 답변달아주신 걸 제가 못봤네요ㅋ 혹시 다른 분들 저처럼 헤매실 수도 있으니 포스팅했던 질문은 그냥 두겠습니다. 답변 감사합니다 그리고 좋은 책 감사해요ㅎㅎ

    답글삭제