나만의 tiny renderer 만들기 (2) - 삼각형 그리기

그래픽스

목록 보기
19/20
post-thumbnail

추가 사항

#ifndef VEC3_H
#define VEC3_H
#include <cmath>

class Vec3
{
public:
    float x, y, z;
    
    Vec3(float x_ = 0.0f, float y_ = 0.0f, float z_ = 0.0f)
        : x(x_), y(y_), z(z_) {}
    
    Vec3 operator-(const Vec3 &v) const
    {
        return Vec3(this->x - v.x, this->y - v.y, this->z - v.z);
    }
    
    Vec3 operator+(const Vec3 &v) const
    {
        return Vec3(this->x + v.x, this->y + v.y, this->z + v.z);
    }
    
    float length_squared() const 
    {
        return this->x * this->x + this->y * this->y + this->z * this->z;
    }
    
    float length() const 
    {
        return std::sqrt(length_squared());
    }
};

inline float dot(Vec3 v1, Vec3 v2)
{
    return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

inline Vec3 cross(Vec3 v1, Vec3 v2)
{
    return Vec3(
        v1.y * v2.z - v1.z * v2.y,
        v1.z * v2.x - v1.x * v2.z,
        v1.x * v2.y - v1.y * v2.x);   
}

class Triangle
{
public:
    Vec3 a;
    Vec3 b;
    Vec3 c;
    Triangle(Vec3 v1, Vec3 v2, Vec3 v3) : a(v1), b(v2), c(v3) {}
    
};

#endif // VEC3_H

일단 벡터 클래스와, 벡터 3개를 이용한 Triangle클래스를 만들어줌 ㅇㅇ

벡터클래스에는 연산자 오버로딩, dot, cross 연산을 정의해놓음ㅇㅇ

벡터 외적 특성 이용하기

일단 2차원에서 진행된다는 점을 잊지 말 것!

Triangle t1(Vec3(7,45,0), Vec3(35,100,0), Vec3(45,60,0));
Triangle t2(Vec3(120,35,0), Vec3(90,5,0), Vec3(45,110,0));
Triangle t3(Vec3(115,83,0), Vec3(80,90,0), Vec3(85,120,0));
	
draw_triangle(t1.a.x, t1.a.y, t1.b.x, t1.b.y, t1.c.x, t1.c.y, framebuffer, red);
draw_triangle(t2.a.x, t2.a.y, t2.b.x, t2.b.y, t2.c.x, t2.c.y, framebuffer, white);
draw_triangle(t3.a.x, t3.a.y, t3.b.x, t3.b.y, t3.c.x, t3.c.y, framebuffer, green);

이렇게 3개의 triangle을 그리도록 했음

draw_triangle

void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) 
{
	bool is_x_smaller = (std::abs(ax - bx) < std::abs(ay - by));
    
	if (is_x_smaller) { std::swap(ax, ay); std::swap(bx, by); }
	if (ax > bx)      { std::swap(ax, bx); std::swap(ay, by); }
    
	int dx   = bx - ax;
	int dy   = std::abs(by - ay);
	int idiff = 0;
	int y    = ay;
	int ystep = (by > ay) ? 1 : -1;

	for (int x = ax; x <= bx; x++) 
	{
		if (is_x_smaller) framebuffer.set(y, x, color);
		else               framebuffer.set(x, y, color);

		idiff += 2 * dy;
		if (idiff > dx) 
		{
			y     += ystep;
			idiff -= 2 * dx;
		}
	}
}

void draw_triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuffer, TGAColor color) 
{
	line(ax, ay, bx, by, framebuffer, color);
	line(bx, by, cx, cy, framebuffer, color);
	line(cx, cy, ax, ay, framebuffer, color);
}

이렇게 작동함

왜 저런 코드가 나왓는지 궁금하면
나만의 tiny renderer 만들기 (1) - Bresenham Algorithm 참고!

이런 삼각형 3개가 만들어지지

여기에 이제 특정 픽셀이 삼각형 내부인지를 판별해야함
이걸 쉽게 구할 수 있는 방식이 있음

그게 바로 외적 공식의 기하학적 특성을 이용한거임

두 벡터의 외적된 벡터의 절대값(길이)는
두 벡터가 이루는 평행사변형의 크기와 같음

12외적=삼각형크기\frac{1}{2} * 외적 = 삼각형 크기 가 되는거임

또, 이렇게
점 p에서 삼각형 각 꼭짓점까지 만들 수 있는 새로운 삼각형 3개의 넓이의 합 == 기존 삼각형의 넓이
= 세 삼각형 넓이의 합 - 기존 삼각형 넓이 == 0
라면, 점 p는 삼각형 내부에 있는거임

여기서 외적의 성질을 이용하면 삼각형 넓이가 아닌
두 벡터의 외적 길이인 평행사변형의 크기를 이용해도 문제가 없음

코드

따라서 먼저 다음과 같은 넓이 구하는 코드 + 점 p의 위치가 삼각형 내부에 있는지 판별하는 메서드를 만들어줌

float get_triangle_area(const Triangle &t)
{
	auto ba = t.b - t.a;
	auto ca = t.c - t.a;
	
	auto cr = cross(ba, ca);
	
	return cr.length();
}

bool is_inside_triangle(const float area, const Vec3 &p, const Triangle &t)
{
	Triangle pt1(Vec3(p), Vec3(t.a), Vec3(t.b));
	Triangle pt2(Vec3(p), Vec3(t.b), Vec3(t.c));
	Triangle pt3(Vec3(p), Vec3(t.c), Vec3(t.a));
	
	auto pt1_area = get_triangle_area(pt1);
	auto pt2_area = get_triangle_area(pt2);
	auto pt3_area = get_triangle_area(pt3);
	
	float diff = area - (pt1_area + pt2_area + pt3_area);
	
	if (0 <= diff && diff < 1e-4) return true;
	return false;
}

점 p를 이용해 만들 수 있는 삼각형을 만들고,
해당 삼각형의 외적과 외적의 길이를 구하여 평행사변형의 넓이를 구함

std::abs와 같은 절대값 처리가 없음?

쉬움
오른손 좌표계냐 왼손 좌표계냐에 따라 외적의 방향이 달라지지?
두 벡터가 오른손 좌표계에서 외적이 특정 방향(d)이었다면
해당 두 벡터는 왼손 좌표계에서 외적은 -d 가 됨

근데, 벡터의 길이를 구하는 로직은 자기 자신의 모든 좌표를 제곱하는 것과 같음

float length_squared() const 
{
    return this->x * this->x + this->y * this->y + this->z * this->z;
}

그러니까 외적의 특정 축이 음수였다고 하더라도,
길이를 구할때는 무조건 양수가 나오게 되는거임

이제 전체 main.cpp를 봐보자

int main(int argc, char** argv)
{
	TGAImage framebuffer(width, height, TGAImage::RGB);
	
	Triangle t1(Vec3(7,45,0), Vec3(35,100,0), Vec3(45,60,0));
	Triangle t2(Vec3(120,35,0), Vec3(90,5,0), Vec3(45,110,0));
	Triangle t3(Vec3(115,83,0), Vec3(80,90,0), Vec3(85,120,0));
	
	draw_triangle(t1.a.x, t1.a.y, t1.b.x, t1.b.y, t1.c.x, t1.c.y, framebuffer, red);
	draw_triangle(t2.a.x, t2.a.y, t2.b.x, t2.b.y, t2.c.x, t2.c.y, framebuffer, white);
	draw_triangle(t3.a.x, t3.a.y, t3.b.x, t3.b.y, t3.c.x, t3.c.y, framebuffer, green);
	
	float t1_area = get_triangle_area(t1);
	float t2_area = get_triangle_area(t2);
	float t3_area = get_triangle_area(t3);
	
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			Vec3 p(j, i, 0);
			
			if (is_inside_triangle(t1_area, p, t1))
			{
				framebuffer.set(j, i, red);
			}
			
			if (is_inside_triangle(t2_area, p, t2))
			{
				framebuffer.set(j, i, white);
			}
			
			if (is_inside_triangle(t3_area, p, t3))
			{
				framebuffer.set(j, i, green);
			}
		}
	}
	
	framebuffer.write_tga_file("framebuffer.tga");
	return 0;
}

height와 width를 전부 돌면서
삼각형 t1,t2,t3와 점 p가 어떤 관계인지를 파악하고
그걸 framebuffer에 넣어 출력하는거임


그럼 이렇게 내부가 칠해진 삼각형이 나오게 됨~~ㄷㄷㄷㄷ

바운딩 박스 최적화

지금 코드는 모든 width와 height의 픽셀에 대하여 해당 픽셀이 삼각형 내부인지 아닌지를 판별함

근데, 사진에서 잘 보면 삼각형 어디에도 절대 포함될 수 없는 픽셀들이 존재함

빨간 삼각형을 기준으로 봐보자

이렇게 빗금칠 된 공간은 절대로 삼각형 내부에 들어갈 수 없음

그래서 저부분을 빼고 계산을 해준다는 개념이 바운딩 박스

먼저 모든 삼각형을 하나의 객체처럼 관리하기 위해 배열에 넣어줌

std::vector<std::pair<Triangle, TGAColor>> triangles; //add

int main(int argc, char** argv)
{
	TGAImage framebuffer(width, height, TGAImage::RGB);
	
	Triangle t1(Vec3(7,45,0), Vec3(35,100,0), Vec3(45,60,0));
	Triangle t2(Vec3(120,35,0), Vec3(90,5,0), Vec3(45,110,0));
	Triangle t3(Vec3(115,83,0), Vec3(80,90,0), Vec3(85,120,0));
	
	triangles.push_back(std::make_pair(t1, red));
	triangles.push_back(std::make_pair(t2, white));
	triangles.push_back(std::make_pair(t3, green));
	
	//...
}

그리고 특정 점 p가 삼각형 내부인지 판별하기전에
해당 삼각형의 min, max x-y의 범위에서 loop를 돌도록 하면 됨

따라서 전체 main은 다음처럼 바뀜

std::vector<std::pair<Triangle, TGAColor>> triangles;

int main(int argc, char** argv)
{
	TGAImage framebuffer(width, height, TGAImage::RGB);
	
	Triangle t1(Vec3(7,45,0), Vec3(35,100,0), Vec3(45,60,0));
	Triangle t2(Vec3(120,35,0), Vec3(90,5,0), Vec3(45,110,0));
	Triangle t3(Vec3(115,83,0), Vec3(80,90,0), Vec3(85,120,0));
	
	triangles.push_back(std::make_pair(t1, red));
	triangles.push_back(std::make_pair(t2, white));
	triangles.push_back(std::make_pair(t3, green));
	
	draw_triangle(t1.a.x, t1.a.y, t1.b.x, t1.b.y, t1.c.x, t1.c.y, framebuffer, red);
	draw_triangle(t2.a.x, t2.a.y, t2.b.x, t2.b.y, t2.c.x, t2.c.y, framebuffer, white);
	draw_triangle(t3.a.x, t3.a.y, t3.b.x, t3.b.y, t3.c.x, t3.c.y, framebuffer, green);

	for (int ti = 0; ti < triangles.size(); ++ti)
	{
		auto p = triangles[ti];
		Triangle t = p.first;
		TGAColor color = p.second;
				
         //box bounding
		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);
		
		float area = get_triangle_area(t);

		for (int j = minY; j <= maxY; ++j)
		{
			for (int i = minX; i <= maxX; ++i)
			{
				Vec3 point(i, j, 0);
				
				if (is_inside_triangle(area, point, t))
				{
					framebuffer.set(i, j, color);
				}
			}
		}
	}
	
	framebuffer.write_tga_file("framebuffer.tga");
	return 0;
}

잘 출력댐

박스 바운딩 전/후 속도차이

박스 바운딩 전 박스 바운딩 후

3개의 삼각형을 그리는데도 차이가 심함!

그러니까 바운딩 박스 최적화를 잘 사용해보자

백페이스 컬링

지금 삼각형 내부를 판별하는 코드는 문제가 있음

대충 어떤 모델을 렌더링한 결과임

잘 보면 이상한 부분이 많음

이게 이렇게 렌더링되는 이유는

  • 삼각형의 앞,뒷면을 가리지 않고 모두 렌더링 하기 때문임

코드 수정

먼저 공부중인 코스에 맞춰 코드를 수정함

float 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)
	);
}

void triangle(Triangle t, TGAImage &framebuffer, TGAColor color) 
{
	int bbminx = std::min(std::min(t.a.x, t.b.x), t.c.x); 
	int bbminy = std::min(std::min(t.a.y, t.b.y), t.c.y); 
	int bbmaxx = std::max(std::max(t.a.x, t.b.x), t.c.x);
	int bbmaxy = std::max(std::max(t.a.y, t.b.y), t.c.y);
	
	double total_area = get_triangle_area(t);

#pragma omp parallel for
	for (int x=bbminx; x<=bbmaxx; x++) 
	{
		for (int y=bbminy; y<=bbmaxy; 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);
		}
	}
}

이렇게 코드를 바꿈

is_inside_triangle는 삭제하고 위처럼 코드를 수정함

중요한건

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)
	);

이부분임

전개

지금은 2차원 평면이므로, 모든 좌표의 z좌표가 0임

두 3차원 벡터의 외적공식은 다음과 같음

a×b=(aybzazby,azbxaxbz,axbyaybx)\vec{a} \times \vec{b} = (a_y b_z - a_z b_y, a_z b_x - a_x b_z, a_x b_y - a_y b_x)

이때 벡터는 각각

ba,ca\vec{b-a}, \vec{c-a}

그리고 z축과 관련된 벡터의 요소는 0임

따라서 ba,ca\vec{b-a}, \vec{c-a}의 외적은 다음과 같음(z축과 관련된 항 모두 제거)

ba×ca=(bxax)(cyay)(byay)(cxax)\vec{b-a} \times \vec{c-a} = {(b_x - a_x)} \cdot {(c_y - a_y)} - {(b_y - a_y)} \cdot {(c_x - a_x)}

이걸 분배법칙을 이용해 전개해보면 다음과 같음

=bxcybxayaxcy+axay(bycxbyaxaycx+ayax)= b_xc_y - b_xa_y - a_xc_y + a_xa_y - (b_yc_x - b_ya_x - a_yc_x + a_ya_x)
=bxcybxayaxcy+axaybycx+byax+aycxayax= b_xc_y - b_xa_y - a_xc_y + a_xa_y - b_yc_x + b_ya_x + a_yc_x - a_ya_x

이때 두 스칼라의 곱셈법칙은 교환법칙이 성립함

=bxcybxayaxcy+axaybycx+byax+aycx.axay= b_xc_y - b_xa_y - a_xc_y + a_xa_y - b_yc_x + b_ya_x + a_yc_x - .a_xa_y
=bxcybxayaxcybycx+byax+aycx= b_xc_y - b_xa_y - a_xc_y - b_yc_x + b_ya_x + a_yc_x

좀 어지러우니 식을 정리해보자

=axbyaybx+bxcybycxaxcy+aycx= a_xb_y - a_yb_x + b_xc_y - b_yc_x - a_xc_y + a_yc_x

여기서 트릭 하나 발동

0=bxbyaxay+cxcybxby+axaycxcy0 = b_xb_y - a_xa_y + c_xc_y - b_xb_y + a_xa_y - c_xc_y

라는 식 한개를 정의해보자
이항정리를 하면 0이 되는걸 알수있음

그럼 이 식은 합이 0이니, 위의 식에 넣어도 0이니, 값에 변화가 없음을 알 수 있음

=axbyaybx+bxcybycxaxcy+aycx+bxbyaxay+cxcybxby+axaycxcy= a_xb_y - a_yb_x + b_xc_y - b_yc_x - a_xc_y + a_yc_x \\ + b_xb_y - a_xa_y + c_xc_y - b_xb_y + a_xa_y - c_xc_y

와 같이 두개를 더한거나 아닌거나 기존의 외적과 같은 값이라는거임

그럼 이제 분배법칙을 이용하기 위해 식을 조금 정리해보자

=bxby+axbyaybxayax+cxcy+bxcybycxbybx+aycx+ayaxaxcycxcy= b_xb_y + a_xb_y - a_yb_x - a_ya_x \\+ c_xc_y + b_xc_y - b_yc_x - b_yb_x \\+ a_yc_x + a_ya_x - a_xc_y - c_xc_y

처럼 전개 가능함(개빡세네 에휴...)

그럼 이제 각 요소의 곱셈자리마다 분배법칙을 이용할 수 있음

=by(bx+ax)ay(bx+ax)+cy(cx+bx)by(cx+bx)+ay(cx+ax)cy(ax+cx)= b_y(b_x + a_x) - a_y(b_x + a_x) \\+ c_y(c_x + b_x) - b_y(c_x + b_x) \\+ a_y(c_x + a_x) - c_y(a_x + c_x)

로 식을 변환할 수 있음

그럼 이제 다시 곱셈법칙을 이용해서 처리할 수 있는데

=(byay)(bx+ax)= (b_y - a_y)(b_x + a_x)
+(cyby)(cx+bx)+ (c_y - b_y)(c_x + b_x)
+(aycy)(cx+ax)+ (a_y - c_y)(c_x + a_x)

로 전개가 완료됨


원래대로 잘 출력됨!

이제 백페이스 컬링 할차례

백페이스 컬링

void triangle(Triangle t, TGAImage &framebuffer, TGAColor color) 
{
	int bbminx = std::min(std::min(t.a.x, t.b.x), t.c.x); 
	int bbminy = std::min(std::min(t.a.y, t.b.y), t.c.y); 
	int bbmaxx = std::max(std::max(t.a.x, t.b.x), t.c.x);
	int bbmaxy = std::max(std::max(t.a.y, t.b.y), t.c.y);
	
	double total_area = get_area(t);

#pragma omp parallel for
	for (int x=bbminx; x<=bbmaxx; x++) 
	{
		for (int y=bbminy; y<=bbmaxy; y++) 
		{
			double alpha = get_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_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_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);
		}
	}
}

이코드를 잘 봐보자

점 a,b,c가 있을때

오른손 좌표계를 기준으로 함

처럼 됨.
이때 외적 벡터의 z는 양수가 됨

  • 왼손 좌표계면 z는 음수지 ㅇㅇ

하지만 이렇게 좌표가 있다면
오른손 좌표계 기준으로
외적 벡터의 z는 음수가 됨

  • 왼손 좌표계면 z는 양수지 ㅇㅇ

이렇게 좌표계에 맞춰서
원하는 좌표계를 정했다면

정면이 아닌 z축을 가지는 삼각형은 렌더링을 패싱하면 되는거임

void triangle(Triangle t, TGAImage &framebuffer, TGAColor color) 
{
	int bbminx = std::min(std::min(t.a.x, t.b.x), t.c.x); 
	int bbminy = std::min(std::min(t.a.y, t.b.y), t.c.y); 
	int bbmaxx = std::max(std::max(t.a.x, t.b.x), t.c.x);
	int bbmaxy = std::max(std::max(t.a.y, t.b.y), t.c.y);
	
	double total_area = get_area(t);
	
    //add
	if (total_area < 0) return;

#pragma omp parallel for
	for (int x=bbminx; x<=bbmaxx; x++) 
	{
		for (int y=bbminy; y<=bbmaxy; y++) 
		{
			double alpha = get_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_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_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);
		}
	}
}

그게 add 주석 부분임

졸라 간단스


이런 렌더링이 됨

하지만 아직 문제가 있음

뒷면에 해당되는 삼각형
예를들어 입안, 옷 안쪽은 무시되거나 다른 삼각형 위에 렌더링이 됨

이건 다음에 해결하도록~~~

전체 코드

  • main.cpp
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include "tgaimage.h"
#include <iostream>
#include <fstream>
#include <string>

#include "Model.h"
#include "Vec3.h"

constexpr TGAColor white   = {255, 255, 255, 255};
constexpr TGAColor green   = {  0, 255,   0, 255};
constexpr TGAColor red     = {  0,   0, 255, 255};
constexpr TGAColor blue    = {255, 128,  64, 255};
constexpr TGAColor yellow  = {  0, 200, 255, 255};
constexpr int width  = 1024;
constexpr int height = 1024;


float 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)
	);
}

void triangle(Triangle t, TGAImage &framebuffer, TGAColor color) 
{
	int bbminx = std::min(std::min(t.a.x, t.b.x), t.c.x); 
	int bbminy = std::min(std::min(t.a.y, t.b.y), t.c.y); 
	int bbmaxx = std::max(std::max(t.a.x, t.b.x), t.c.x);
	int bbmaxy = 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=bbminx; x<=bbmaxx; x++) 
	{
		for (int y=bbminy; y<=bbmaxy; 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);
		}
	}
}

std::tuple<int,int> project(Vec3 v) 
{
	return { static_cast<int>(std::round((v.x + 1.) * width / 2)),
			 static_cast<int>(std::round((v.y + 1.) * height / 2)) };
}

std::vector<std::pair<Triangle, TGAColor>> triangles;

int main(int argc, char** argv)
{	
	if (argc != 2) 
	{
		std::cerr << "Err Usage: " << argv[0] << " obj/model.obj" << std::endl;
		return 1;
	}
	
	Model model(argv[1]);
	TGAImage framebuffer(width, height, TGAImage::RGB);
	
	// triangle(Triangle(Vec3(7, 45, 0), Vec3(35, 100, 0), Vec3(45,  60, 0)), framebuffer, red);
	// triangle(Triangle(Vec3(120, 35, 0), Vec3(90,   5, 0), Vec3(45, 110, 0)), framebuffer, white);
	// triangle(Triangle(Vec3(115, 83, 0), Vec3(80,  90, 0), Vec3(85, 120, 0)), framebuffer, green);
	
	Vec3 camera_pos(0,0, 1);
	
	for (int i = 0; i < model.nfaces(); i++) 
	{	
		auto [ax, ay] = project(model.vert(i, 0));
		auto [bx, by] = project(model.vert(i, 1));
		auto [cx, cy] = project(model.vert(i, 2));
	
		TGAColor rnd;
		for (int c = 0; c < 3; c++) rnd[c] = std::rand() % 255;
		
		triangle(Triangle(Vec3(ax, ay, 0), Vec3(bx, by, 0), Vec3(cx, cy, 0)), framebuffer, rnd);
	}
	
	framebuffer.write_tga_file("framebuffer.tga");
	return 0;
}
  • Vec3.h
#ifndef VEC3_H
#define VEC3_H
#include <cmath>

class Vec3
{
public:
    float x, y, z;
    
    Vec3(float x_ = 0.0f, float y_ = 0.0f, float z_ = 0.0f)
        : x(x_), y(y_), z(z_) {}
    
    Vec3 operator-(const Vec3 &v) const
    {
        return Vec3(this->x - v.x, this->y - v.y, this->z - v.z);
    }
    
    Vec3 operator+(const Vec3 &v) const
    {
        return Vec3(this->x + v.x, this->y + v.y, this->z + v.z);
    }
    
    float& operator[](int i)
    {
        if (i == 0) return x;
        if (i == 1) return y;
        return z;
    }
    
    const float& operator[](int i) const
    {
        if (i == 0) return x;
        if (i == 1) return y;
        return z;
    }
    
    float length_squared() const 
    {
        return this->x * this->x + this->y * this->y + this->z * this->z;
    }
    
    float length() const 
    {
        return std::sqrt(length_squared());
    }
};

inline float dot(Vec3 v1, Vec3 v2)
{
    return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

inline Vec3 cross(Vec3 v1, Vec3 v2)
{
    return Vec3(
        v1.y * v2.z - v1.z * v2.y,
        v1.z * v2.x - v1.x * v2.z,
        v1.x * v2.y - v1.y * v2.x);   
}

class Triangle
{
public:
    Vec3 a;
    Vec3 b;
    Vec3 c;
    Triangle(Vec3 v1, Vec3 v2, Vec3 v3) : a(v1), b(v2), c(v3) {}
    
};

#endif // VEC3_H
  • Model.h
#ifndef MODEL_H
#define MODEL_H

#include <string>
#include <vector>

class Vec3;

class Model 
{
    std::vector<Vec3> verts = {};
    std::vector<int> facet_vrt = {};
public:
    Model(const std::string filename);
    int nverts() const;
    int nfaces() const;
    Vec3 vert(const int i) const;
    Vec3 vert(const int iface, const int nthvert) const;
};

#endif // MODEL_H
  • Model.cpp
#include <fstream>
#include <iostream>
#include <sstream>
#include "model.h"
#include "Vec3.h"

Model::Model(const std::string filename) {
    std::ifstream in;
    in.open(filename, std::ifstream::in);
    if (in.fail()) return;
    std::string line;
    while (!in.eof()) {
        std::getline(in, line);
        std::istringstream iss(line.c_str());
        char trash;
        if (!line.compare(0, 2, "v ")) {
            iss >> trash;
            Vec3 v;
            for (int i : {0,1,2}) iss >> v[i];
            verts.push_back(v);
        } else if (!line.compare(0, 2, "f ")) {
            int f,t,n, cnt = 0;
            iss >> trash;
            while (iss >> f >> trash >> t >> trash >> n) {
                facet_vrt.push_back(--f);
                cnt++;
            }
            if (3!=cnt) {
                std::cerr << "Error: the obj file is supposed to be triangulated" << std::endl;
                return;
            }
        }
    }
    std::cerr << "# v# " << nverts() << " f# "  << nfaces() << std::endl;
}

int Model::nverts() const { return verts.size(); }
int Model::nfaces() const { return facet_vrt.size()/3; }

Vec3 Model::vert(const int i) const {
    return verts[i];
}

Vec3 Model::vert(const int iface, const int nthvert) const {
    return verts[facet_vrt[iface*3+nthvert]];
}
profile
그래픽스 공부중

0개의 댓글