본문 바로가기
공부

그래픽스 Opengl - Barycentric 좌표

by 라이티아 2025. 11. 11.
#define _USE_MATH_DEFINES 
#define GLM_ENABLE_EXPERIMENTAL

#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/string_cast.hpp>
#include <fstream>
#include <chrono>

#include <vector>

#include "cow.h"
#include "ppm_io.h"


void convertNDCtoImage(const glm::vec4 vertexNDC, glm::vec4 vertexView, const uint32_t& imageWidth, const uint32_t& imageHeight, glm::vec3& vertexRaster)
{
    vertexRaster.x = ((vertexNDC.x + 1.0f) / 2.0f) * imageWidth;
    vertexRaster.y = ((1.0f - vertexNDC.y) * 0.5f) * imageHeight;
    vertexRaster.z = -vertexView.z;
}



float min3(const float& a, const float& b, const float& c)
{
    return std::min(a, std::min(b, c));
}

float max3(const float& a, const float& b, const float& c)
{
    return std::max(a, std::max(b, c));
}


bool edge(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c)
{
    return (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x) >= 0;
    // return 1.0f;
}

glm::mat4 lookAt(glm::vec3 campos, glm::vec3 look, glm::vec3 up)
{
    glm::vec3 zaxis = glm::normalize(campos - look);
    glm::vec3 xaxis = glm::normalize(glm::cross(up, zaxis));
    glm::vec3 yaxis = glm::cross(zaxis, xaxis);

    glm::mat4 view = {
        { xaxis.x, yaxis.x, zaxis.x, 0 },
        { xaxis.y, yaxis.y, zaxis.y, 0 },
        { xaxis.z, yaxis.z, zaxis.z, 0 },
        { -glm::dot(xaxis, campos), -glm::dot(yaxis, campos), -glm::dot(zaxis, campos), 1 }
    };

    return view;
}

glm::mat4 perspective(float fovy, float aspect, float near, float far)
{
	glm::mat4 pro_matrix =
	{
		{ 1/(aspect * std::tan(glm::radians(fovy/2))), 0, 0, 0 },
		{ 0, 1 / std::tan(glm::radians(fovy/2)), 0, 0 },
		{ 0 , 0, -((far + near) / (far - near)), -1 },
		{ 0, 0, -2.0f*((far*near)/(far-near)), 0 }
	};
	return pro_matrix;
}

const uint32_t imageWidth = 640;
const uint32_t imageHeight = 480;


const uint32_t ntris = 3156;
const float nearClippingPlane = 1;
const float farClippingPLane = 1000;




int main(int argc, char** argv)
{
  
    glm::mat4 worldToCamera = glm::lookAt(glm::vec3(20, 10, 20), glm::vec3(0, 5, 0), glm::vec3(0, 1, 0));

    float t, b, l, r;

    PPM ppmOut;
    ppmOut.setBinary(true);



    struct rgb
    {
        uint8_t r, g, b;
    };

    std::vector <rgb>  frameBuffer;
    frameBuffer.resize(imageWidth * imageHeight);


    for (uint32_t i = 0; i < imageWidth * imageHeight; ++i) {
        frameBuffer[i].r = 0;
        frameBuffer[i].g = 0;
        frameBuffer[i].b = 0;
    }

    float* depthBuffer = new float[imageWidth * imageHeight];

    for (uint32_t i = 0; i < imageWidth * imageHeight; ++i) {
        depthBuffer[i] = farClippingPLane;
    }

    auto t_start = std::chrono::high_resolution_clock::now();

    for (uint32_t i = 0; i < ntris; ++i) {

        // 삼각형 만들기 = model coordinate
        const glm::vec3& v0 = vertices[nvertices[i * 3]];
        const glm::vec3& v1 = vertices[nvertices[i * 3 + 1]];
        const glm::vec3& v2 = vertices[nvertices[i * 3 + 2]];


        // 모델 행렬 = 로컬 -> 뷰
        glm::mat4 modelMatrix(1.0f);
        // 글로벌 -> 카메라
        glm::mat4 viewMatrix = lookAt(glm::vec3(0, 50, 10), glm::vec3(0, 5, 0), glm::vec3(0, 1, 0));
        // 로컬 -> 카메라
        glm::mat4 modelViewMatrix = viewMatrix * modelMatrix;

        //Camera(view) coordinates
        glm::vec4 v0e;
        glm::vec4 v1e;
        glm::vec4 v2e;

        //Code here (calculate v0e, v1e, v2e here)
        v0e = modelViewMatrix * glm::vec4(v0, 1.0f);
        v1e = modelViewMatrix * glm::vec4(v1, 1.0f);
        v2e = modelViewMatrix * glm::vec4(v2, 1.0f);

        // std::cout << glm::to_string(v0e) << "\n" << glm::to_string(v1e) << "\n" << glm::to_string(v2e) << "\n\n";

        glm::mat4 projection = perspective((45.0f), imageWidth / (float)imageHeight, nearClippingPlane, farClippingPLane);



        //Clip coodinates
        glm::vec4 v0c, v1c, v2c;

        // clip = projection * view
        //Code here (calculate v0c, v1c, v2c here)
        v0c = projection * v0e;
        v1c = projection * v1e;
        v2c = projection * v2e;
        // std::cout << glm::to_string(v0c) << "\n" << glm::to_string(v1c) << "\n" << glm::to_string(v2c) << "\n\n";

        //Perspective division (divide v0c.x, v0c.y, v0c.z by v0c.w   (same to v1c, v2c)

        glm::vec4 v0p = v0c;
        glm::vec4 v1p = v1c;
        glm::vec4 v2p = v2c;

        //Code here
        // 모든 좌표값음 -1 ~ 1로 바꿈 = NDC
        v0p /= v0c.w;
        v1p /= v1c.w;
        v2p /= v2c.w;


        glm::vec3 v0Raster, v1Raster, v2Raster;
        convertNDCtoImage(v0p, v0e, imageWidth, imageHeight, v0Raster);
        convertNDCtoImage(v1p, v1e, imageWidth, imageHeight, v1Raster);
        convertNDCtoImage(v2p, v2e, imageWidth, imageHeight, v2Raster);

        // std::cout << glm::to_string(v0Raster) << "\n" << glm::to_string(v1Raster) << "\n" << glm::to_string(v2Raster) << "\n\n";

        //bound box
        float xmin = min3(v0Raster.x, v1Raster.x, v2Raster.x);
        float ymin = min3(v0Raster.y, v1Raster.y, v2Raster.y);
        float xmax = max3(v0Raster.x, v1Raster.x, v2Raster.x);
        float ymax = max3(v0Raster.y, v1Raster.y, v2Raster.y);

        if (xmin > imageWidth - 1 || xmax < 0 || ymin > imageHeight - 1 || ymax < 0) continue;

        uint32_t x0 = std::max(int32_t(0), (int32_t)(std::floor(xmin)));
        uint32_t x1 = std::min(int32_t(imageWidth) - 1, (int32_t)(std::floor(xmax)));
        uint32_t y0 = std::max(int32_t(0), (int32_t)(std::floor(ymin)));
        uint32_t y1 = std::min(int32_t(imageHeight) - 1, (int32_t)(std::floor(ymax)));


        //calculat the area of triangle  (area)
        float area = edge(v0Raster, v1Raster, v2Raster);

        for (uint32_t y = y0; y <= y1; ++y) {
            for (uint32_t x = x0; x <= x1; ++x) {

                glm::vec3 pixelSample(x + 0.5, y + 0.5, 0);

                //calculate the areas of  three suvdivided triangles

                bool w0 = edge(v1Raster, v2Raster, pixelSample); //w0
                bool w1 = edge(v2Raster, v0Raster, pixelSample); //w1
                bool w2 = edge(v0Raster, v1Raster, pixelSample); //w2


                if (w0 >= 0 && w1 >= 0 && w2 >= 0) { //inside

                    
                    //calculate the ratio here
                    w0 /= area;
                    w1 /= area;
                    w2 /= area;

                    //calculate the z of pixelSample
                    float z;


                    //code here


                    
                    if (z < depthBuffer[y * imageWidth + x]) {

                        depthBuffer[y * imageWidth + x] = z;


                        //calculate normal vector from v0e, v1e, v2e

                        glm::vec3 n;
                        

                        frameBuffer[y * imageWidth + x].r = n.x * 255;
                        frameBuffer[y * imageWidth + x].g = n.y * 255;
                        frameBuffer[y * imageWidth + x].b = n.z * 255;
                    }
                }
                // frameBuffer[y * imageWidth + x].r = 255;
                // frameBuffer[y * imageWidth + x].g = 255;
                // frameBuffer[y * imageWidth + x].b = 255;
            } // 안쪽 for loop
        }
    } // 바깥쪽 for loop

    auto t_end = std::chrono::high_resolution_clock::now();
    auto passedTime = std::chrono::duration<double, std::milli>(t_end - t_start).count();
    std::cerr << "Wall passed time: " << passedTime << "ms" << std::endl;


    rgb* p = frameBuffer.data();

    ppmOut.load(&p[0].r, imageHeight, imageWidth, 255, "P6");
    ppmOut.write("../../output.ppm");

    delete[] depthBuffer;

    return 0;
}

에서 

        for (uint32_t y = y0; y <= y1; ++y) {
            for (uint32_t x = x0; x <= x1; ++x) {

안쪽을 구현한다

 

이때 사용되는 것이 Barycentric 좌표를 사용한다

Barycentric 좌표란 한 점이 각 점에서 얼마나 떨어져 있는가에 대한 이야기를 하는 것이다

 

예를 들면, P에 대해서 생각할때 P는 어디쪽에 가중치가 더 높을까? 그리고 그것을 어떻게 표현할까? 라는 것이다

당연히 P가 어디에 있는가에 따라 각 가중치가 달라지게 된다

 

이때 Barycentric 좌표는 삼각형의 3개 면적을 같게 하는 것을 목표로 하는 한 점 P를 구하는 것이다

 

이때 삼각형의 전체 면적을 D라고 했을시 해당 수식으로 나타낼 수 있다

// 세개의 꼭짓점으로 이루어진 삼각형 면적
float area(const glm::vec3 a, const glm::vec3 b, const glm::vec3 c)
{
    return fabs((c[0] - a[0]) * (b[1] - a[1]) - (c[1] - a[1]) * (b[0] - a[0]));
}

이를 코드상에서 구하기 위해 삼각형 면적을 구하는 함수를 작성 후

        //calculat the area of triangle  (area)
        float total = area(v0Raster, v1Raster, v2Raster);

        for (uint32_t y = y0; y <= y1; ++y) {
            for (uint32_t x = x0; x <= x1; ++x) {

                glm::vec3 pixelSample(x + 0.5, y + 0.5, 0);

                //calculate the areas of  three suvdivided triangles

                bool w0 = edge(v1Raster, v2Raster, pixelSample); //w0
                bool w1 = edge(v2Raster, v0Raster, pixelSample); //w1
                bool w2 = edge(v0Raster, v1Raster, pixelSample); //w2

                if (w0 >= true && w1 >= true && w2 >= true) { //inside

                    float a2 = area(v0Raster, v1Raster, pixelSample);
                    float a0 = area(v1Raster, v2Raster, pixelSample);
                    float a1 = area(v2Raster, v0Raster, pixelSample);
                    //Barycentric 좌표
                    a0 /= total;
                    a1 /= total;
                    a2 /= total;

면적으로 나누어서 Barycentric 좌표를 구해준다

 

                    frameBuffer[y * imageWidth + x].r = 255;
                    frameBuffer[y * imageWidth + x].g = 0;
                    frameBuffer[y * imageWidth + x].b = 0;

임시로 모든 픽셀을 255, 0, 0으로 하여 모든 색을 red로 고정한 후 출력한다

 

보정이 잘 되서 나오는 것을 임시로 확인할 수 있다

 

이제 3d모델의 앞뒤를 확인하기 위해서 카메라와 물체와의 거리를 확인해야 한다

즉, 앞에 있는것 = z값이 높은것을 우선시 해서 앞으로 랜더링 해야 한다

if (w0 >= true && w1 >= true && w2 >= true) { //inside

                    float a2 = area(v0Raster, v1Raster, pixelSample);
                    float a0 = area(v1Raster, v2Raster, pixelSample);
                    float a1 = area(v2Raster, v0Raster, pixelSample);
                    //Barycentric 좌표
                    a0 /= total;
                    a1 /= total;
                    a2 /= total;

                    
                    float oneOverZ = v0Raster.z * a0 + v1Raster.z * a1 + v2Raster.z * a2;
                    float z = 1 / oneOverZ; //이 값이 픽셀의 depth
                    
                    //랜더링 3d모델 카메라 기준 앞인지 뒤인지 확인, 카메라와 가장 가까운지 확인 해야함
                    if (z < depthBuffer[y * imageWidth + x]) {

                        depthBuffer[y * imageWidth + x] = z;

                        //calculate normal vector from v0e, v1e, v2e
                        glm::vec3 e1 = glm::vec3(v1e - v0e);
                        glm::vec3 e2 = glm::vec3(v2e - v0e);
                        glm::vec3 n = glm::normalize(glm::cross(e1, e2)); // 카메라 노멀벡터

                        // [-1,1] → [0,1]
                        n = (n + glm::vec3(1, 1, 1)) * 0.5f;
                        

                        frameBuffer[y * imageWidth + x].r = n.x * 255;
                        frameBuffer[y * imageWidth + x].g = n.y * 255;
                        frameBuffer[y * imageWidth + x].b = n.z * 255;
                    }
                }

각 픽셀마다 색 조정을 거치도록 해주면

형광색 소를 볼 수 있다