CG-3D渲染器-0.1

引言

  • 在学习GAMES101的路上,我总是会心血来潮,想要做出一个3D渲染器。所谓3D渲染器,就是可以加载模型,并且通过调整参数来渲染出不同的视觉效果。
  • 最近看视频看到了第9课,想着做个作业,于是通过作业1接触到了Eigen库,就不可停止的开始了我的3D渲染器制作之路。
  • 本文仅用于记录3D渲染器1.0开发心得,欢迎所有大佬大神批评指教!

变换矩阵的计算

 可参考GAMES101的知识点。

平移矩阵

 任何矩阵乘以单位矩阵都等于它本身,因此我们将平移变换矩阵trsModel初始化为单位矩阵。
 变换矩阵第4列前三个数表示平移,我们根据平移量trs设置它即可。
 最终返回的应该是原变换矩阵model经过平移变换trs后得到的新模型矩阵trs*model

// 平移后产生新模型矩阵
Eigen::Matrix4f get_model_matrix_translation(Eigen::Matrix4f model, Eigen::Vector3f trs)
{
    Eigen::Matrix4f trsModel = Eigen::Matrix4f::Identity();
    for (int i = 0; i < 3; i++)
        trsModel(i, 3) = trs(i);
    return trsModel * model;
}

缩放矩阵

 缩放矩阵很简单,设置对角线上前三个元素为对应轴的缩放倍率即可。
 需要注意的一件事是,如果我们要把x轴缩放0.5倍,我们通常会传入的scale_size为(1,0,0),也就是说我们默认认为传入0的缩放倍率表示不进行缩放,因此我们需要判断scale_size的分量如果为0则设置为1(缩放倍率为1即不缩放)。但是scale_size为浮点数,浮点数比较相等无法精确,因此我们判断scale_size是否接近0,如果非常接近0,则对应轴不进行缩放。

// 缩放后产生新模型矩阵
Eigen::Matrix4f get_model_matrix_scale(Eigen::Matrix4f model, Eigen::Vector3f scale_size)
{
    Eigen::Matrix4f scaleModel = Eigen::Matrix4f::Identity();
    for (int i = 0; i < 3; i++)
        scaleModel(i, i) = ( abs(scale_size(i) - 0) > 0.1 ? scale_size(i) : 1);
    return scaleModel * model;
}

镜像矩阵

 其实镜像矩阵就是缩放矩阵,只不过缩放倍率为负数,如对x轴缩放倍率为-1,就变成了关于x轴对称。因此下面的代码和缩放矩阵一样。
 镜像矩阵我们可以认为不进行缩放,即倍率只能为1或-1,因此我们将参数axle设为整数int类型,这样判断其值是否为0而不进行操作,也很简单。

// 镜像后产生新模型矩阵
Eigen::Matrix4f get_model_matrix_Reflection(Eigen::Matrix4f model, Eigen::Vector3i reflection_axle)
{
    Eigen::Matrix4f reflectionModel = Eigen::Matrix4f::Identity();
    for (int i = 0; i < 3; i++)
        reflectionModel(i, i) = (reflection_axle(i) != 0 ? reflection_axle(i) : 1);
    Eigen::Matrix4f newModle = reflectionModel * model;
    return newModle;
}

旋转矩阵

  • 可参考GAMES101知识点。

绕X轴旋转

// 旋转后产生新模型矩阵
Eigen::Matrix4f get_model_matrix_RotateX(Eigen::Matrix4f model, float rotation_angle)
{
    Eigen::Matrix4f rotateModel = Eigen::Matrix4f::Identity();
    rotateModel(1, 1) = cos(rotation_angle);
    rotateModel(1, 2) = -sin(rotation_angle);
    rotateModel(2, 1) = -rotateModel(1, 2);
    rotateModel(2, 2) = rotateModel(1, 1);
    return rotateModel * model;
}

绕Y轴旋转

Eigen::Matrix4f get_model_matrix_RotateY(Eigen::Matrix4f model, float rotation_angle)
{
    Eigen::Matrix4f rotateModel = Eigen::Matrix4f::Identity();
    rotateModel(0, 0) = cos(rotation_angle);
    rotateModel(0, 2) = sin(rotation_angle);
    rotateModel(2, 0) = -rotateModel(0, 2);
    rotateModel(2, 2) = rotateModel(0, 0);
    return rotateModel * model;
}

绕Z轴旋转

Eigen::Matrix4f get_model_matrix_RotateZ(Eigen::Matrix4f model, float rotation_angle)
{
    Eigen::Matrix4f rotateModel = Eigen::Matrix4f::Identity();
    rotateModel(0, 0) = cos(rotation_angle);
    rotateModel(0, 1) = -sin(rotation_angle);
    rotateModel(1, 0) = -rotateModel(0, 1);
    rotateModel(1, 1) = rotateModel(0, 0);
    return rotateModel * model;
}

绕任意轴旋转

 绕任意轴旋转根据闫老师推导的公式即可,可参考绕3D空间任意轴旋转。
 要注意,这里axis表示的旋转轴是指过原点(0,0,0)方向为axis的轴。

Eigen::Matrix4f get_model_matrix_Rotate(Eigen::Matrix4f model,
    Eigen::Vector3f axis, float rotation_angle)
{
    axis /= axis.sum();
    Eigen::Matrix4f rotateModel = Eigen::Matrix4f::Identity();
    rotateModel << (1 - cos(rotation_angle)) * (axis(0) * axis(0)) + cos(rotation_angle),
        (1 - cos(rotation_angle))* (axis(0) * axis(1)) - axis(2) * sin(rotation_angle),
        (1 - cos(rotation_angle))* (axis(0) * axis(2)) + axis(1) * sin(rotation_angle),
        (1 - cos(rotation_angle))* (axis(0) * axis(1)) + axis(2) * sin(rotation_angle),
        (1 - cos(rotation_angle))* (axis(1) * axis(1)) + cos(rotation_angle),
        (1 - cos(rotation_angle))* (axis(1) * axis(2)) - axis(0) * sin(rotation_angle),
        (1 - cos(rotation_angle))* (axis(0) * axis(2)) - axis(1) * sin(rotation_angle),
        (1 - cos(rotation_angle))* (axis(1) * axis(2)) + axis(0) * sin(rotation_angle),
        (1 - cos(rotation_angle))* (axis(2) * axis(2)) + cos(rotation_angle);
    return rotateModel * model;
}

 当旋转轴axis的起点pos不为原点时,我们先对原模型矩阵进行平移,将pos移到原点(0,0,0)上,使得旋转轴axis过原点。然后在原点绕axis轴旋转angle角度,再将pos移回原来的位置。

igen::Matrix4f get_model_matrix_Rotate(Eigen::Matrix4f model, Eigen::Vector3f pos,
    Eigen::Vector3f axis, float rotation_angle)
{
    Eigen::Matrix4f trsed_1 = get_model_matrix_translation(model, -(pos));
    Eigen::Matrix4f rotated = get_model_matrix_Rotate(trsed_1, axis, rotation_angle);
    Eigen::Matrix4f trsed_2 = get_model_matrix_translation(rotated, pos);
    return trsed_2;
}

观察矩阵的计算

 可参考GAMES101知识点。
 要注意的变换的顺序,我们要先将摄像机平移到原点,然后再将摄像机角度调整为标准角度。

// 获得观察矩阵
Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos, Eigen::Vector3f front,
    Eigen::Vector3f up)
{
    Eigen::Matrix4f view = Eigen::Matrix4f::Identity();

    Eigen::Matrix4f translate;
    translate << 1, 0, 0, -eye_pos[0], 0, 1, 0, -eye_pos[1], 0, 0, 1,
        -eye_pos[2], 0, 0, 0, 1;

    Eigen::Matrix4f rotate;
    Eigen::Vector3f gxt = front.cross(up);

    rotate << gxt[0], gxt[1], gxt[2], 0,
        up[0], up[1], up[2], 0,
        -front[0], -front[1], -front[2], 0,
        0, 0, 0, 1;

    view = rotate * translate * view;

    return view;
}

投影矩阵的计算

 看参考投影矩阵的计算。


Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
                                      float zNear, float zFar)
{
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
    //完全按照课程里的参数取值,这道题的相机就在(0,0,0),因此远近平面都是在z的负半轴,所以n和f的值应该为负
    float f, n, l, r, b, t, fov;
    fov = eye_fov / 180 * MY_PI;
    n = -zNear; //znear是正值
    f = zFar;
    t = tan(fov/2) * zNear;
    b = -t;
    r = t * aspect_ratio;
    l = -r;
    //透视->正交 perspective->orthographic
    Eigen::Matrix4f pertoorth;
    pertoorth << n, 0, 0, 0,
        0, n, 0, 0,
        0, 0, n + f, -n*f,
        0, 0, 1, 0;
    //正交——移动
    Eigen::Matrix4f orth1;
    orth1 << 1, 0, 0, -(r + l) / 2,
        0, 1, 0, -(t + b) / 2,
        0, 0, 1, -(n + f) / 2,
        0, 0, 0, 1;
    //正交——缩放
    Eigen::Matrix4f orth2;
    orth2 << 2 / (r - l), 0, 0, 0,
        0, 2 / (t - b), 0, 0,
        0, 0, 2 / (n - f), 0,
    0, 0, 0, 1;
    projection = orth2*orth1 * pertoorth;//注意矩阵顺序,变换从右往左依次进行
    return projection;
}

Element图元

#pragma once
#include 
#include 
// 图元基类
class Element
{
public:
	// 枚举值记录图元的类型
	enum Type
	{
		POINT,
		LINE,
		TRIANGLE
	};
	Element() {

	};
	Type type;
};
// 图元-点
class Point :Element
{
public:
	Point()
	{

	}
	// 点具有一个齐次坐标和一个四维颜色
	Eigen::Vector4f position;
	Eigen::Vector4f color;
	Point(Eigen::Vector4f position, Eigen::Vector4f color)
	{
		this->position = position;
		this->color = color;
		this->type = POINT;
	}
};
// 图元-线
class Line :Element
{
public:
	Line()
	{

	}
	// 线具有两个齐次坐标和一个四维颜色
	Eigen::Vector4f position[2];
	Eigen::Vector4f color;
	Line(Eigen::Vector4f position[2], Eigen::Vector4f color)
	{
		this->position[0] = position[0];
		this->position[1] = position[1];
		this->color = color;
		this->type = LINE;
	}
};
class Triangle :Element
{
public:
	Triangle()
	{

	}
	// 三角形具有三个齐次坐标和一个四维颜色
	Eigen::Vector4f position[3];
	Eigen::Vector4f color;
	Triangle(Eigen::Vector4f position[3], Eigen::Vector4f color)
	{
		this->position[0] = position[0];
		this->position[1] = position[1];
		this->position[2] = position[2];
		this->color = color;
		this->type = TRIANGLE;
	}
};

ScreenWindow

#pragma once
#include 
#include 
#include "Element.h"
#include "MatrixTransformation.h"
#include 

// 定义屏幕的最大像素值(VS STDIO需要设置堆栈保留大小很大才可运行)
#define MAX_SCREEN_WIDTH 1920
#define MAX_SCREEN_HEIGHT 1080

// 定义颜色缓冲(借鉴OpenGL)
class ColorBuffer
{
public:
	// 颜色缓冲具有大小
	int row, line;
	ColorBuffer()
	{

	}
	// 小白不会动态申请空间,直接申请最大的数组,存储每个像素的四维颜色值
	Eigen::Vector4f buffer[MAX_SCREEN_WIDTH][MAX_SCREEN_HEIGHT];
	// 存储清空颜色缓冲所用颜色(借鉴OpenGL)
	Eigen::Vector4f clearColor;
	ColorBuffer(int row, int line)
	{
		this->row = row;
		this->line = line;
		clearColor << 0, 0, 0, 1;
	}
	// 清空颜色缓冲
	void clear()
	{
		for (int i = 0; i < row; i++)
			for (int j = 0; j < line; j++)
				buffer[i][j] = clearColor;
	}
};
// 定义深度缓冲(借鉴OpenGL)
class DepthBuffer
{
public:
	int row, line;
	DepthBuffer()
	{

	}
	// 对每个像素深度缓冲存储的一个浮点数深度值
	float buffer[MAX_SCREEN_WIDTH][MAX_SCREEN_HEIGHT];
	// 清空深度缓冲的值(构造函数中设置其为负无穷大)
	float clearDepth;
	DepthBuffer(int row, int line)
	{
		this->row = row;
		this->line = line;
		clearDepth = -FLT_MAX;
	}
	// 清空深度缓冲
	void clear()
	{
		for (int i = 0; i < row; i++)
			for (int j = 0; j < line; j++)
				buffer[i][j] = clearDepth;
	}
};

// 着色类(主要负责使用图元更新缓冲区)
class Shader
{
public:
	// 定义着色器中的图元数据(包括图元数量和原始数据)
	int points, lines, triangles;
	Point point[100];
	Line line[100];
	Triangle triangle[100];
	// 定义变换矩阵
	Eigen::Matrix4f model;
	Eigen::Matrix4f view;
	Eigen::Matrix4f projection;
	Shader()
	{

	}
	Shader(int points, int lines, int triangles)
	{
		for (int i = 0; i < points; i++)
		{
			this->point[i] = Point();
			this->line[i] = Line();
			this->triangle[i] = Triangle();
		}

	}
	// 更新颜色缓冲区
	void Draw(ColorBuffer &colorBuffer,int width, int height)
	{
		// 创建新的三角形数组,记录模型中三角形经过变换后的位置
		Triangle triangle[100];
		// 对每一个三角形
		for (int i = 1; i <= triangles; i++)
		{
			// 先计算出三角形通过 模型、视图、投影变换后的顶点位置
			for (int j = 0; j < 3; j++)
				triangle[i].position[j] = projection * view * model * this->triangle[i].position[j];
			// 对于屏幕上的每个像素进行光栅化
			for (int j = 0; j < width; j++)
			{
				for (int k = 0; k < height; k++)
				{
					// 创建pos记录像素位置,position记录三角形变换后的顶点位置(为了后面三维做叉积方便在此转换一下)
					Eigen::Vector3f pos(j, k, 0);
					Eigen::Vector3f position[3];
					// 提取出三角形顶点的三维坐标
					for (int r = 0; r < 3; r++)
					{
						position[r][0] = triangle[i].position[r][0];
						position[r][1] = triangle[i].position[r][1];
						position[r][2] = triangle[i].position[r][2];
					}
					// 判断像素是否在三角形中
					bool isIn = isInside(pos, position);
					// 如果在就更新颜色缓冲的颜色值
					if (isIn)
						colorBuffer.buffer[j][k] = this->triangle[i].color;
				}
			}
		}
	}
	
	// 判断像素点pos是否在三角形顶点position中
	bool isInside(Eigen::Vector3f pos, Eigen::Vector3f position[3])
	{

		// 三次叉乘
		Eigen::Vector3f res1 = (pos - position[0]).cross(position[1] - position[0]);

		Eigen::Vector3f res2 = (pos - position[1]).cross(position[2] - position[1]);

		Eigen::Vector3f res3 = (pos - position[2]).cross(position[0] - position[2]);

		// 要求叉积同向
		if (res1[2] * res2[2] > 0 && res2[2] * res3[2] > 0 && res1[2] * res3[2] >0 )
			return true;
		else
			return false;
	}
};

// 屏幕控制类(借鉴glfw)
class ScreenWindow
{
public:
	// 屏幕的尺寸
	int width, height;
	// 清空缓冲区时是否清空颜色缓冲、深度缓冲
	bool clearColor, clearDepth;
	// 当前屏幕使用的缓冲区号
	int index;
	// 前后缓冲区
	ColorBuffer colorBuffer[2];
	DepthBuffer depthBuffer[2];
	// shader负责处理图元更新缓冲
	Shader shader;
	ScreenWindow()
	{

	}
	ScreenWindow(int width, int height)
	{
		this->index = 0;
		this->width = width;
		this->height = height;
		colorBuffer[0] = ColorBuffer(width, height);
		colorBuffer[1] = ColorBuffer(width, height);
		depthBuffer[0] = DepthBuffer(width, height);
		depthBuffer[0].clear();
		depthBuffer[1] = DepthBuffer(width, height);
		depthBuffer[1].clear();
		// 默认不清空缓冲
		clearColor = false;
		clearDepth = false;
		std::cout << "窗口初始化完毕。" << std::endl;
		// 使用EasyX绘制,初始化窗口
		initgraph(width, height);
	}
	// 将图元加载到颜色缓冲中(借鉴OpenGL)
	void Draw()
	{
		// 调用shader,使用shader中的图元更新未被渲染的!index缓冲(index要么为0,要么为1)
		shader.Draw(colorBuffer[!index], width, height);
		std::cout << "更新数据到缓冲区:" << !index << std::endl;
	}
	// 清空颜色缓冲
	void clearBuffer()
	{
		if (clearColor)
			colorBuffer[!index].clear();
		if (clearDepth)
			depthBuffer[!index].clear();
		std::cout << "清空缓冲区:" << !index << std::endl;
	}
	// 交换前后缓冲区(借鉴OpenGL)
	void swapBuffer()
	{
		// 交换缓冲区
		index = !index;
		std::cout << "交换缓冲区完毕,当前使用缓冲区:" << index << std::endl;
		// 绘制新替换的默认缓冲
		DrawScreen();
	}
	void DrawScreen()
	{
		// 使用Easy的双缓冲
		BeginBatchDraw();
		// 绘制每一个像素处的颜色值
		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				COLORREF color = RGB(colorBuffer[index].buffer[i][j][0],
					colorBuffer[index].buffer[i][j][1],
					colorBuffer[index].buffer[i][j][2]);
				putpixel(i, j, color);
			}
		}
		std::cout << "屏幕渲染完毕,当前使用缓冲区:" << index << std::endl;
		FlushBatchDraw();
	}
};

Main函数

#include 
#include 
#include 
#include 
#include 
#include "ScreenWindow.h"
#include "Element.h"
#include "MatrixTransformation.h"

int main(int argc, const char** argv)
{
	int width = 800, height = 600;
	int row = 4, line = 4;
	
	// 创建窗口
	ScreenWindow window(width,height);
	// 设置窗口选项
	window.colorBuffer[0].clearColor = Eigen::RowVector4f(127, 254, 212, 1);
	window.colorBuffer[1].clearColor = Eigen::RowVector4f(127, 254, 212, 1);
	window.clearColor = true;

	// 设置图元
	Eigen::Vector4f position[3];
	Eigen::Vector4f color;
	position[0] << 0.0f, 0.0f, 0.0f, 1.0f;
	position[1] << 240.0f, 0.0f, 0.0f, 1.0f;
	position[2] << 0.0f, 240.0f, 0.0f, 1.0f;
	color << 0, 255, 0, 1;
	Triangle triangle(position, color);
	window.shader.triangles = 1;
	window.shader.triangle[1] = triangle;

	clock_t start_time = clock();

	while (1)
	{
		// 计算时间
		clock_t end_time = clock();
		double deltTime = static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC * 1000;
			
		std::cout << std::endl << "Game Loop:" << std::endl;
		std::cout << "当前运行时间:" << deltTime/1000 << std::endl;
		std::cout << "当前使用缓冲区:" << window.index << std::endl;

		// 清空缓冲区
		window.clearBuffer();
		
		// 向Shdaer传入变换矩阵
		window.shader.model = Eigen::Matrix4f::Identity();
		window.shader.model = get_model_matrix_translation(window.shader.model,
			Eigen::Vector3f(300, 200, 0));
		Eigen::Vector3f eye_pos(0, 0, 1);
		Eigen::Vector3f front(0, 0, -1);
		Eigen::Vector3f up(0, 1, 0);
		window.shader.view = get_view_matrix(eye_pos, front, up);
		window.shader.projection = Eigen::Matrix4f::Identity();

		// 将图元加载到缓冲区中
		window.Draw();
		
		// 交换前后缓冲区
		window.swapBuffer();
	}
	// 释放资源
	closegraph(); 
	return 0;
}



你可能感兴趣的:(GAMES101,计算机图形学,图像处理,GAMES101,计算机图形学,CG)