Barycentric Coordinates
1차원 무게중심 좌표
앞에서도 설명했지만
더 자세한 공식을 다뤄보겠음
두 점 A, B가 있고, 이는 같은 한 1차원 선분 위에 있음
그리고 이 선분 위에 점 P가 존재함
이 때, 점 P는 점 A, B에 가중치를 고려한 합과 같음
P=αA+βB
만약 P=3,A=1,B=5라면
1α+5β=3이 됨
α=4,β=51일때 해답이 될 수 있음
또 α=1,β=52일때도 해답이 될 수 있음
하지만 여기서 문제가 있음
α와 β가 여러 값이 될 수 있으면
이후 계산에 오차가 생김
이거를 해결하기위해 사용하는 핵심 개념이
0<=α<=1, 0<=β<=1, α+β=1
임
α와 β를 정규화를 해서 1이라는 총합으로 제한을 두어
어느 상황에서든 값을 통일시키는거임
앞서 살펴본
P=αA+βB
를 이용해보자
P=αA+βB
α+β=1
β=1−α
α=0 => P=B
α=0.5 => P=0.5A+0.5B
α=1 => P=A
이렇게 선형보간이 됨
그럼 이α와 β를 어떻게 구하지?
α,β 전개
먼저 α를 살펴보자
α=B−AB−P
P=αA+(1−α)B
P=αA+B−αB
P−B=αA−αB
P−B=−α(−A+B)
−α=−A+BP−B
α=B−AB−P
혹은
α=(B−A)⋅(−1)(B−P)⋅(−1)=A−BP−B
α=B−AB−P
β=B−AP−A
P=(B−AB−P)⋅A+βB
βB=P−(B−AB−P)⋅A
βB=P⋅(B−AB−A)−(B−AB−P)⋅A
βB=B−AP⋅(B−A)−A⋅(B−P)
βB=B−APB−PA−AB+AP
βB=B−AB(P−A)−PA+AP
βB=B−AB(P−A)
β=B−AP−A
β=B−AP−A
코드
void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color)
{
for (int x = ax; x <= bx; x++)
{
float alpha = (bx - x) / static_cast<float>(bx - ax);
int y = alpha * ay + (1 - alpha) * by;
framebuffer.set(x, y, color);
}
}
이렇게 쉽게 증가하는 직선을 그릴 수 있음
line(72, 13, 127, 127, framebuffer, white);

2차원 무게중심 좌표
1차원은 점 2개였지?
2차원은 점 3개임
즉 점 A, B, C에 대해 무게중심좌표를 구하는거임
그 외엔 모든게 같음
P=αA+βB+γC
α+β+γ=1
임
이건 점 2개를 이용할때처럼
α,1−α이런식으로 접근하기엔 변수가 많음
그래서 2가지 방법을 사용해볼 수 있음
행렬 이용(비추천)
행렬 변환
행렬 이용하는 거임
연립일차 방정식은 지립일차 방정식은 계수, 미지수, 상수 부로 나눠서 행렬로 표현할 수 있음
먼저 아래 식은 x좌표와 y좌표가 합쳐져 있음
P=αA+βB+γC
α+β+γ=1
따라서 행렬로 표현하기 위해선 미지수부를 위해 x, y로 나눠야 함
⎩⎪⎨⎪⎧αAx+βBx+γCxαAy+βBy+γCyα+β+γ=Px=Py=1
이렇게 나타낼 수 있음
이걸 행렬로 표현해보자
⎝⎜⎛AxAy1BxBy1CxCy1⎠⎟⎞⎝⎜⎛αβγ⎠⎟⎞=⎝⎜⎛PxPy1⎠⎟⎞
가 됨
수반 행렬과 역행렬
우리한테 필요한건 ⎝⎜⎛αβγ⎠⎟⎞ 임
좌변에 ⎝⎜⎛αβγ⎠⎟⎞만 남기고 우변으로 이항정리 해보자
역행렬을 곱하면 됨
⎝⎜⎛AxAy1BxBy1CxCy1⎠⎟⎞⎝⎜⎛αβγ⎠⎟⎞=⎝⎜⎛PxPy1⎠⎟⎞
⎝⎜⎛αβγ⎠⎟⎞=⎝⎜⎛AxAy1BxBy1CxCy1⎠⎟⎞−1⎝⎜⎛PxPy1⎠⎟⎞
역행렬을 수반행렬로
수반행렬과 역행렬의 관계 : A−1=detA1adjA
⎝⎜⎛αβγ⎠⎟⎞=∣∣∣∣∣∣AxAy1BxBy1CxCy1∣∣∣∣∣∣adj(AxAy1BxBy1CxCy1)(PxPy1)
이걸 전개하면 됨
소행렬, 소행렬식, 행렬식 계산, 여인수 전개, 수반행렬
선형대수에서 배우는건데
혹시 모르는 사람 있을까봐 설명함
A=⎝⎜⎛147258369⎠⎟⎞
라는 행렬이 있다고 하자
행렬은 기본적으로 행과 열의 조합으로 나타낼 수 있음
이를 Aij와 같은 식으로 표현함
소행렬
A22의 소행렬
A22=⎝⎜⎛147258369⎠⎟⎞
여기서 빨간색 글자를 제외한
파란색 글자인 부분만 사용하는게 A22에 대한 소행렬임
소행렬식
A22의 소행렬식
A22=∣∣∣∣∣1739∣∣∣∣∣
이렇게 행렬식 기호를 이용해 소행렬을 표현하면 그게 행렬식임
행렬식 계산
A22 행렬식 계산

이런 형태로 계산하면됨
1∗9−3∗7=−12가 A22의 행렬식 결과임
이때 중요한게 부호임
여인수 전개
여인수 전개
여인수 전개할때, 필요한건 부호임
Aij의 소행렬식의 계산결과를 구하기위해선
(−1)i+j의 부호를 붙여 계산해야함
예를들어 11이면, (−1)2이 되어, (1,1)위치의 수반행렬에 ∗1을 해야함
예를들어 34이면, (−1)7이 되어, (4,3)위치의 수반행렬에 ∗−1을 해야함

즉, 이런 패턴을 가짐
수반행렬
이렇게 모든 행렬의 소행렬에 행렬식 계산시에 필요한 부호를 계산하여 사용하여
모든 행렬식을 계산한것이 수반행렬임
이때 중요한건 행과 열이 바뀐다는거임
수반행렬
A23의 소행렬을 계산했다면
이 계산은 수반행렬의 adjA32에 저장됨
A=⎝⎜⎛147258369⎠⎟⎞
이걸 위 모든과정을 거쳐 수반행렬 adjA로 만들어보자
모든걸 다 할수없으니, 2행에 대해서만 소행렬, 전개, 수반행렬로 만드는 과정을 보여줌
C21=−∣∣∣∣∣2839∣∣∣∣∣=−((2×9)−(3×8))=−(18−24)=6
C22=+∣∣∣∣∣1739∣∣∣∣∣=(1×9)−(3×7)=9−21=−12
C23=−∣∣∣∣∣1728∣∣∣∣∣=−((1×8)−(2×7))=−(8−14)=6
adj(A)=⎝⎜⎛C11C12C13C21(6)C22(−12)C23(6)C31C32C33⎠⎟⎞
이 됨
전체를 전부 이런식으로 수반행렬로 만들면
adj(A)=⎝⎜⎛−36−36−126−36−3⎠⎟⎞
이렇게 됨
다시 돌아와서
⎝⎜⎛αβγ⎠⎟⎞=∣∣∣∣∣∣AxAy1BxBy1CxCy1∣∣∣∣∣∣adj(AxAy1BxBy1CxCy1)(PxPy1)
이 식의 수반행렬을 전개해보자
마지막 행이 1행이라 아주 좋음
⎝⎜⎛αβγ⎠⎟⎞=⎝⎜⎛AxAy1BxBy1CxCy1⎠⎟⎞−1⎝⎜⎛PxPy1⎠⎟⎞=∣∣∣∣∣∣∣AxAy1BxBy1CxCy1∣∣∣∣∣∣∣adj⎝⎜⎛AxAy1BxBy1CxCy1⎠⎟⎞⎝⎜⎛PxPy1⎠⎟⎞=∣∣∣∣∣∣∣AxAy1BxBy1CxCy1∣∣∣∣∣∣∣⎝⎜⎛By−Cy−(Ay−Cy)Ay−By−(Bx−Cx)Ax−Cx−(Ax−Bx)BxCy−ByCx−(AxCy−AyCx)AxBy−AyBx⎠⎟⎞⎝⎜⎛PxPy1⎠⎟⎞
행렬 곱
행렬곱은 어떻게 계산하지?
두 행렬 A,B가 있을 때,
A의 i번째 행과 B의 j번째 열의 원소들을 차례대로 곱한 뒤 모두 더하면
결과 행렬의 (i,j)번째 성분이 됨.
예를들어
A=(1324),
B=(4657)
이 있으면
A×B=(1∗4+2∗63∗4+4∗61∗5+2∗73∗5+4∗7)=(16361943)
가 되는거임
그럼 다시 돌아와서
∣∣∣∣∣∣AxAy1BxBy1CxCy1∣∣∣∣∣∣(By−Cy−(Ay−Cy)Ay−By−(Bx−Cx)Ax−Cx−(Ax−Bx)BxCy−ByCx−(AxCy−AyCx)AxBy−AyBx)(PxPy1)
을 살펴보면
분자의 행렬에 곱셈을 할 수 있음
분자부분만 살펴보자
⎝⎜⎛By−Cy−(Ay−Cy)Ay−By−(Bx−Cx)Ax−Cx−(Ax−Bx)BxCy−ByCx−(AxCy−AyCx)AxBy−AyBx⎠⎟⎞⎝⎜⎛PxPy1⎠⎟⎞
임
그럼 곱을 진행해볼까나...
⎝⎜⎛Px(By−Cy)+Py(Cx−Bx)+1(BxCy−ByCx)Px(Cy−Ay)+Py(Ax−Cx)+1(−(AxCy−AyCx))Px(Ay−By)+Py(Bx−Ax)+1(AxBy−AyBx)⎠⎟⎞
(길이가 너무 길어서 한줄씩 띄움)
(기본적으로, 1행만 존재하는 행렬임)
가 됨
그걸 행렬식으로 표현하면
∣∣∣∣∣∣AxAy1BxBy1CxCy1∣∣∣∣∣∣(∣∣∣∣∣∣PxPy1BxBy1CxCy1∣∣∣∣∣∣∣∣∣∣∣∣PxPy1CxCy1AxAy1∣∣∣∣∣∣∣∣∣∣∣∣PxPy1AxAy1BxBy1∣∣∣∣∣∣)⊤
처럼 마무리 되는거지
근데 복잡하지?
저렇게 만들고, 계산하려고 해도 행렬식이 4개라
4개를 다 계산해야함
이거 안씀
실제로는 비율을 더 자주 이용함
비율 & 신발끈 공식 (더 나은 선택)
1차원에서의 무게중심 좌표 구하는 것과 비슷한 맥락임

이런 삼각형이 있다고 가정해보자
점 A를 기준으로
BC가 밑변이고 높이가 hA일때,
△ABC=21(BC∗hA)가 됨
이때, 점 P를 기준으로
BC가 밑변이고 높이가 hP일때,
△PBC=21(BC∗hP)가 됨
즉 area(ABC)area(PBC)=21(BC∗hA)21(BC∗hP)=hAhP임
P=αA+βB+γC임
위에서 BC가 밑변일때 area(PBC)area(ABC)=hPhA이므로,
hP=αhA+βhB+γhC가 성립됨
이때,BC는 밑변이므로 높이가 존재하지 않음
hB=0,hC=0임
hP=αhA, α=hAhp가 됨
즉, α는 △PBC가 △ABC에 차지하는 비율에 의해 결정됨
α=area(ABC)area(PBC)
β=area(ABC)area(PCA)
γ=area(ABC)area(PAB)
로 결정되는거임
신발끈 공식

평면위에 다각형의 각 꼭지점 좌표가 있을때,
빨간선으로 이어진 부분을 곱하고 그 결과들을 더한것에서
파란선으로 이어진 부분을 곱하고 그 결과들을 더한것을 빼고
1/2를 곱하면 그게 다각형의 넓이임
이때 중요한건, 꼭짓점의 좌표가 한방향으로 흘러가야한다는거임
△ABC=21∗∣((Ax∗By)+(Bx∗Cy)+(Cx∗Ay)−((Bx∗Ay)+(Cx∗By)+(Ax∗Cy)))∣=21∗∣(AxBy+BxCy+CxAy−BxAy−CxBy−AxCy)∣=21∗∣(By(Ax−Cx)+Cy(Bx−Ax)+Ay(Cx−Bx))∣
이 됨
예시로 A=(16,0),B=(0,12),C=(0,0),P=(4,3)
이라고 했을때
위의 공식을 적용해서 △ABC,△PBC,△PCA,△PAB를 구하면
△ABC=96,△PBC=24,△PCA=24,△PAB=48이 나옴

α,β,γ는 각각 순서대로
△PBC,△PCA,△PAB 의 전체 삼각형 너비에 대한 비율임
코드
double get_triangle_area(Triangle t)
{
return 0.5 * (
(t.b.y-t.a.y)*(t.b.x+t.a.x) +
(t.c.y-t.b.y)*(t.c.x+t.b.x) +
(t.a.y-t.c.y)*(t.a.x+t.c.x)
);
}
먼저 삼각형 너비 구하는 공식임
이거 저번 포스트에서 설명했음
나만의 tiny renderer 만들기 (2) - 삼각형 그리기 - 코드 수정 참고!
우리가 위에서 살펴본 식으로 살펴보면
double get_triangle_area_v2(Triangle t)
{
return 0.5 * (
(t.b.y * (t.a.x - t.c.x)) +
(t.c.y * (t.b.x - t.a.x)) +
(t.a.y * (t.c.x - t.b.x))
);
}
인거임
삼각형 내부 원하는 색으로 칠하기
void triangle(Triangle t, TGAImage &framebuffer, TGAColor color)
{
int minX = std::min(std::min(t.a.x, t.b.x), t.c.x);
int minY = std::min(std::min(t.a.y, t.b.y), t.c.y);
int maxX = std::max(std::max(t.a.x, t.b.x), t.c.x);
int maxY = std::max(std::max(t.a.y, t.b.y), t.c.y);
double total_area = get_triangle_area(t);
if (total_area < 0) return;
#pragma omp parallel for
for (int x=minX; x<=maxX; x++)
{
for (int y=minY; y<=maxY; y++)
{
double alpha = get_triangle_area(Triangle(Vec3(x,y,0), Vec3(t.b.x, t.b.y, 0), Vec3(t.c.x, t.c.y, 0))) / total_area;
double beta = get_triangle_area(Triangle(Vec3(x,y,0), Vec3(t.c.x, t.c.y, 0), Vec3(t.a.x, t.a.y, 0))) / total_area;
double gamma =get_triangle_area(Triangle(Vec3(x,y,0), Vec3(t.a.x, t.a.y, 0), Vec3(t.b.x, t.b.y, 0))) / total_area;
if (alpha<0 || beta<0 || gamma<0) continue;
framebuffer.set(x, y, color);
}
}
}
지금 삼각형을 칠하는 코드는 이런 형태임
alpha, beta, gamma가 위에서 말한 무게중심좌표에 해당함
각 좌표에 대해 먼저 z축을 설정하고
이걸 이용해서
각 z축을 계산한다음, 이걸 색상으로 이용해볼거임
먼저 main.cpp에 다음과 같은 삼각형 코드를 넣음
int main(int argc, char** argv)
{
Model model(argv[1]);
TGAImage framebuffer(width, height, TGAImage::RGB);
triangle(Triangle(Vec3(7, 45, 255), Vec3(35, 100, 255), Vec3(45, 60, 255)), framebuffer);
framebuffer.write_tga_file("framebuffer.tga");
return 0;
}
그리고 triangle을 아래처럼 바꿈
void triangle(Triangle t, TGAImage &framebuffer)
{
int minX = std::min(std::min(t.a.x, t.b.x), t.c.x);
int minY = std::min(std::min(t.a.y, t.b.y), t.c.y);
int maxX = std::max(std::max(t.a.x, t.b.x), t.c.x);
int maxY = std::max(std::max(t.a.y, t.b.y), t.c.y);
double total_area = get_triangle_area(t);
if (std::abs(total_area) < 1e-5) return;
#pragma omp parallel for
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
Vec3 p(x, y, 0);
double area_a = get_triangle_area(Triangle(p, t.b, t.c));
double area_b = get_triangle_area(Triangle(p, t.c, t.a));
double area_c = get_triangle_area(Triangle(p, t.a, t.b));
double alpha = area_a / total_area;
double beta = area_b / total_area;
double gamma = area_c / total_area;
if (alpha < -1e-5 || beta < -1e-5 || gamma < -1e-5) continue;
framebuffer.set(x, y, {
static_cast<uint8_t>(alpha * t.a.z),
static_cast<uint8_t>(beta * t.b.z),
static_cast<uint8_t>(gamma * t.c.z),
255
});
}
}
}

굳~~~