[Rasterization] Blinn-Phone 모델(블린 퐁 모델)
블린 퐁 모델은 퐁 모델에서 specular계산을 더 간단하게 하는 방식입니다. 퐁 모델에서 specular를 계산하기 위해, 반사된 빛의 방향을 알아야 하는데, 그 계산이 조금 복잡하기 때문입니다. 이유는 이전 글 퐁모델 : https://pdy0930.tistory.com/54
을 참고하면 될 것 같습니다.
블린퐁 모델의 개념은 퐁 모델에서 specular만 다르게 생각하면 됩니다. ambient와 diffuse는 퐁 모델과 같기 때문입니다.
다음 그림을 보면, 퐁 모델에서는 없는 H벡터가 있습니다. H벡터는 V와 L의 합 벡터를 유닛화 한 벡터입니다. V를 R에 가깝게 갈 수록 H는 N의 각도가 작아집니다. V를 R에서 떨어뜨릴 수록 N과 H사이의 각도가 커집니다. 따라서 N과 H의 내적을 퐁 모델에서 처럼 이용할 수 있습니다.

이번 블린 퐁 모델은 레스터화에서 적용해 보겠습니다. 블린 퐁 모델을 적용할 때는 픽셀에 쉐이더를 입히는 것이기 때문에 픽셀 쉐이더에서 처리합니다. 버텍스 쉐이더에서 삼각형의 버텍스들을 가져와서 픽셀의 위치를 barycentric coordinate로 결정합니다. 그 후 Perspective-Correct Interpolation를 통해 가중치들을 다시 계산합니다. 그 가중치들로 현재 픽셀에 해당하는 월드좌표계의 point를 찾을 수 있습니다.
코드는 다음과 같습니다.
삼각형의 범위판정과 barycentric coordinate코드 :
void Rasterization::DrawIndexedTriangle(const size_t &startIndex,
vector<vec4> &pixels) {
const auto v0 = ProjectWorldToRaster(this->vertexBuffer[i0]);
const auto v1 = ProjectWorldToRaster(this->vertexBuffer[i1]);
const auto v2 = ProjectWorldToRaster(this->vertexBuffer[i2]);
// 삼각형 전체 넓이의 두 배, 음수일 수도 있음
const float area = EdgeFunction(v0, v1, v2);
//삼각형 범위 bounding처리
const auto xMin =..
const auto yMin =..
const auto xMax =..
const auto yMax =..
for (size_t j = yMin; j <= yMax; j++) {
for (size_t i = xMin; i <= xMax; i++) {
const vec2 point = vec2(float(i), float(j));
// 위에서 계산한 삼각형 전체 넓이 area를 재사용
// area가 음수라면 alpha0, alpha1, alpha2 모두 음수여야
// 삼각형 안에 포함되는 픽셀로 판단할 수 있습니다.
float w0 = EdgeFunction(v1, v2, point) / area;
float w1 = EdgeFunction(v2, v0, point) / area;
float w2 = EdgeFunction(v0, v1, point) / area;
if (w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) {
const float z0 = this->vertexBuffer[i0].z + distEyeToScreen;
const float z1 = this->vertexBuffer[i1].z + distEyeToScreen;
const float z2 = this->vertexBuffer[i2].z + distEyeToScreen;
const vec3 p0 = this->vertexBuffer[i0];
const vec3 p1 = this->vertexBuffer[i1];
const vec3 p2 = this->vertexBuffer[i2];
if (this->usePerspectiveProjection &&
this->usePerspectiveCorrectInterpolation) {
w0 /= z0;
w1 /= z1;
w2 /= z2;
const float wSum = w0 + w1 + w2;
w0 /= wSum;
w1 /= wSum;
w2 /= wSum;
}
// 이하 동일
const float depth = w0 * z0 + w1 * z1 + w2 * z2;
if (depth < depthBuffer[i + width * j]) {
depthBuffer[i + width * j] = depth;
PSInput psInput;
psInput.position = w0 * p0 + w1 * p1 + w2 * p2;
psInput.normal = w0 * n0 + w1 * n1 + w2 * n2;
pixels[i + width * j] = MyPixelShader(psInput);
}
}
}
}
}
픽셀 쉐이더에서 블린 퐁 모델 적용 코드 :
vec4 MyPixelShader(const PSInput psInput) {
vec3 eye = vec3(0.0f, 0.0f, -1.0f);
vec3 toEye = glm::normalize(eye - psInput.position);
vec3 color = ComputeDirectionalLight(constants.light, constants.material,
psInput.normal, toEye);
return vec4(color, 1.0f);
}
vec3 BlinnPhong(vec3 lightStrength, vec3 lightVec, vec3 normal, vec3 toEye,
Material mat) {
// Halfway vector 계산
vec3 halfway = glm::normalize(toEye + lightVec);
// Halyway vector를 이용해서 specular albedo 계산
vec3 specular =
mat.specular *
glm::pow(glm ::max(dot(halfway, normal), 0.0f), mat.shininess);
vec3 diff =
mat.diffuse * glm ::max(dot(normal, lightVec), 0.0f) * lightStrength;
// ambient, diffuse, specular 합쳐서 계산
return mat.ambient + diff + specular;
}
vec3 ComputeDirectionalLight(Light L, Material mat, vec3 normal, vec3 toEye) {
// 계산에 사용하는 lightVector는 directional light가 향하는 방향의 반대
vec3 lightVec = -L.direction;
vec3 lightStrength = L.strength;
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
여기서 normal벡터(법선벡터)는 어떻게 형성될까요? Raytracing에서는 물체에 ray를 쏴서 맞은 물체의 지점에서 법선벡터를 구하였습니다. 레스터화에서는 삼각형 형성 시, 모든 버텍스마다 normal벡터를 같이 지정해줍니다. 마치 다음 코드 처럼요.
// 윗면
this->vertices.push_back(vec3(-1.0f, 1.0f, -1.0f) * scale);
this->vertices.push_back(vec3(-1.0f, 1.0f, 1.0f) * scale);
this->vertices.push_back(vec3(1.0f, 1.0f, 1.0f) * scale);
this->vertices.push_back(vec3(1.0f, 1.0f, -1.0f) * scale);
this->normals.push_back(vec3(0.0f, 1.0f, 0.0f));
this->normals.push_back(vec3(0.0f, 1.0f, 0.0f));
this->normals.push_back(vec3(0.0f, 1.0f, 0.0f));
this->normals.push_back(vec3(0.0f, 1.0f, 0.0f));
강의 출처 :
홍정모 그래픽스 새싹코스 part 2
https://honglab.co.kr/collections