环境:20200520版EasyX,vs2019
项目地址:https://github.com/zsybh1/EasyX-Based-Renderer
目标:使用EasyX编写一个光栅化渲染器,实现三角形的显示和插值
基本没有参考什么资料,可能一些解决方案和一般方法不太一样,仅供参考
主要思路:建立数据结构 -> 建立视角模型 -> 投影三角形顶点 -> 映射到实际窗口 -> 插值绘制
vec3向量类,pos坐标类,实现简单的向量和点的运算
color颜色类,实现颜色的加法、乘法,方便插值
tricolor_triangle带色三角形类,用三个pos和对应的三个color描述一个三角形
一个视点COP和一个视图窗口,物体和COP连线在视窗上的交集即为投影。为了简单起见,取平面为z=0
令视点COP在点e,COP到三角形某一顶点的向量为d,向量和视窗的交点为p,有方程
e + t d = p e+td=p e+td=p
其中pz=0,可得 t = − z z ′ t=-\frac{z}{z'} t=−z′z,代入方程求得p
在视窗上选取一块区域,投影到程序窗口上进行显示
有多种选取方式,我选择的是将上面计算出来的坐标p放大500倍显示在窗口上
需要对于窗口上的指定像素,判断是否存在于三角形内部,并给出三个顶点对这个像素的贡献系数
令投影在窗口上的三个顶点分别为ABC,要计算的像素为P
令 A P ⃗ = u A B ⃗ + v A C ⃗ \vec{AP}=u\vec{AB}+v\vec{AC} AP =uAB +vAC
当满足 u ≥ 0 , v ≥ 0 , u + v ≤ 1 u\ge0,v\ge 0,u+v\le1 u≥0,v≥0,u+v≤1时,P在三角形ABC内
该方程可以从x和y两个维度展开成二元一次方程组,运用克莱姆法则可得到解
此时 1 − u − v , u , v 1-u-v,u,v 1−u−v,u,v分别为A, B, C对P的贡献系数
将贡献系数分别乘以每个点的颜色,即可计算出指定像素P的颜色
为了遍历的效率,可以取得三个顶点的X、Y坐标的最大最小值,然后在这个矩形中遍历每个像素对方程求解
主体代码如下:
void draw_tricolor_triangle(const tricolor_triangle &tri, const pos &e) {
const pos *r[3] = { &tri.A,&tri.B,&tri.C };
pos p[3];
for (int j = 0; j < 3; ++j) {
//映射顶点到视窗
float t = -r[j]->z / e.z;
float x = e.x + t * r[j]->x;
float y = e.y + t * r[j]->y;
//将视窗映射到窗口像素
p[j].x = int(x * 500);
p[j].y = int(y * 500);
}
//计算包围三角形的最小轴对齐矩形
int maxX = max(max(p[0].x, p[1].x), p[2].x);
int maxY = max(max(p[0].y, p[1].y), p[2].y);
int minX = min(min(p[0].x, p[1].x), p[2].x);
int minY = min(min(p[0].y, p[1].y), p[2].y);
vec3 vec[3] = { vec3(),p[1] - p[0], p[2] - p[0] };
for (int i = maxY; i >= minY; --i) {
for (int j = minX; j <= maxX; ++j) {
//遍历每个像素,计算像素是否在三角形内
vec[0] = pos(j, i, 0) - p[0];
float M = vec[1].x * vec[2].y - vec[2].x * vec[1].y;
float v = (vec[1].x * vec[0].y - vec[0].x * vec[1].y) / M;
float u = -(vec[2].x * vec[0].y - vec[0].x * vec[2].y) / M;
if (0 <= u && 0 <= v && u+v <= 1) {
//如果在,则插值上色
color Color = tri.Acolor * (1 - u - v) + tri.Bcolor * u + tri.Ccolor * v;
putpixel(j, i, Color.getBGR());
}
}
}
}
最后完成的效果如图所示