설명

위 이미지와 같이, Ambient, Diffuse, Specular를 각각 합한(더한) 렌더링 과정을 Phong reflection model이라고하는데. 각각이 가리키는 요소는 다음과 같다.
Ambient
우리가 세상을 볼 때, 빛과 완전히 마주하지 않아도, 다른 물체에서 반사되는 기본적인 빛이 있기 때문에 빛 아래 없는 물체도 볼 수 있는데. 이를 대체해서 기본적인 빛(환경광)을 받는다고 가정하고 구현된 것이 Ambient light라고 한다.
- 유도 과정은 딱히 없고, 기본적으로 주어지는 색이라고 생각하여 리터럴 값을 넣는다.
Diffuse
빛이 물체를 비추고, 이것이 반사되어 물체의 색이 눈에 들어오게 되는데, 그 정도를 나타낸다. 반대로 빛이 반사되어 눈으로 들어오지 못한 것은 어둡게 표시된다.
유도과정

Sphere에 이를 간단하게 이를 유도할 수 있는데, 그림을 보면 가 눈으로 보는 방향일 때, 가 구에 닿는 지점을 알 수 있고, (구의 중점을 향하는 Vector) - 로 normal vector 을 구할 수 있다. 그리고 로 을 구할 수 있다. 이제 Unit vector L과 N을 Dot Product 해주면 된다. 이를 토대로 구에 빛이 얼마나 들어오는지를 알 수 있다. (를 통해서) 정리한 식은 아래와 같다.

Cos 그래프를 참고하면 구와 빛의 각도에 따라 빛이 곱해지는 정도를 알 수 있다.
그래프를 보면 90도를 넘어가면 음수가 돼버리는데 이부분이 0이되도록 처리해줘야한다.
Specular
빛에 직접적으로 비춰졌을 때, 완전히 매트한 물질이 아니라면, 빛을 반사하게된다. Specular는 이를 나타낸 것이다.
유도과정
diffuse에서의 이제 를 구하고 과의 cos값을 구하면 되는데, 먼저 를 구하는 방식은 아래 식과 같다.
를 base vector로 나눠서 표현한다.
에 대한 식으로 다시 정리한다.
반사 vector 를 구하기 위한 식
를 한번 빼면 0, 다시 빼면 이 되므로 대칭을 이루게 된다.
2번을 3번에 대입하여 식을 다시 정리
를 구하기위한 방법으로 unit vector를 Dot Product하면 투영된 길이가 나오고,
이에 을 곱함으로써 를 구할 수 있다.
4번에 5번을 대입하여 정리한 식
7번 연산을 끝내면 의 값을 얻을 수 있고, 이를 토대로 빛과의 거리(광량)를 측정할 수 있다.
6번 연산에서 의 연산은 끝.
여기서 표기되는 Vector는 모두 Unit vector이다.
구현
Ambient는 값을 리터럴로 초기화 해서 넣어주면 돼서 패스.
Diffuse
// Diffuse
const vec3 dirToLight = glm::normalize(light.pos - hit.point);
const float diff = glm::max(glm::dot(hit.normal, dirToLight), 0.0f);
Specular
// Specular
const vec3 reflectDir = 2.0f * glm::dot(glm::normalize(ray.dir), hit.normal) * hit.normal - glm::normalize(ray.dir);
const float specular = glm::max(glm::dot(-reflectDir, dirToLight), 0.0f);
// 테스트 기록
/*{
const vec3 reflectDir = 2.0f * glm::dot(dirToLight, hit.normal) * hit.normal - dirToLight;
const float test = glm::max(glm::dot(reflectDir, -ray.dir), 0.0f);
if (specular - test >= 10e-7)
std::cout << "diff: " << specular << " : " << test << std::endl;
}*/
두 가지 방식을 사용해봤고, 오차범위를 비교했을 때, 10e-7 보다 큰 오차가 없었기에 같은값으로 판단했다.
- 카메라가 보는 Vector가 구에 반사된 vector와 구에서 비친 광원의 각도 비교
- 구에서 비친 광원의 각도의 반사 vector와 카메라로 들어오는 Vector의 각도.W
QnA
Specular를 계산할 때, 빛이 Sheper 내부로 들어갈 때, 왜 빛이 점차적으로 안보이나? 빛의 위치를 나타내는 벡터는 점이기 때문에 한번에 안보여야하는데?
벡터가 float형태이기 때문에 오차때문에 일어난 일이라고 추측. (검증필요)
왜 Specular를 계산할 때, 1번코드는 안되고, 2번 코드로만 동작할까?
Ray pixelRay(camera, glm::normalize(pixelPosWorld - camera)); // x Ray pixelRay(pixelPosWorld, glm::normalize(pixelPosWorld - camera)); // o

그림에서 볼 수 있듯이 벡터 공간이 다르기 때문이다. origin을 camera 위치로 만든다면 전혀 다른 벡터가 만들어지게 된다. sphere의 표면을 찍는 벡터가 아니라 sphere의 내부로 들어가는 이상한 벡터가 만들어진다.
위 답변에 기초해서, 그렇다면 sphere collision 연산은 왜 괜찮았고 light 계산은 왜 문제가 됐을까?
Ray pixelRay(camera, glm::normalize(pixelPosWorld - camera)); // x Ray pixelRay(pixelPosWorld, glm::normalize(pixelPosWorld - camera)); // o
두 벡터는 분명히 다르고 연산 결과 또한 다르다. 하지만 sphere를 구하는 방법을 생각해보면 정확한 값으로 찾지 않고 판별식으로 hit를 판별한다. 즉, 근의 개수에 따라서 색깔을 결정한다는 말이다. 첫 번째 벡터에 ray.dir의 방향 벡터를 스케일링 한 값을 더한 것은 두 번째 벡터에 ray.dir을 스케일링 한 값과 선형종속(linearly dependent) 관계를 가지므로, 판별식 또한 같은 결과를 가진다. (세부사항은 다름)
아래 사항이 같다는 말. nable 값은 당연하게도 다르다.
- 의 경우, 근이 없다. 즉, sphere를 통과하지 않는다.
- 의 경우, 하나의 근이 있다. 즉, sphere의 한 지점과 마주한다.
- 의 경우, 두 개의 근이 있다. 즉, sphere를 관통한다.