그래픽스 기술

[Rasterization] Blinn-Phone 모델(블린 퐁 모델)

doyyy_0 2024. 11. 5. 16:06

블린 퐁 모델은 퐁 모델에서 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