tastynoob
Articles58
Tags18
Categories7
一个简单的3d渲染器

一个简单的3d渲染器

一个简单的3D渲染器
仅支持三维物体的渲染
不包括着色系统

闲来无事写的玩的项目

主要算法是利用矩阵乘法来实现对物体顶点的变换

再利用中心投影算法将物体投影到屏幕缓冲区上

实际上最主要的是如何投影,如何渲染

对于简单的3d渲染只需要使用很简单的算法即可实现

比如物体的旋转,平移,缩放均可使用初中知识

更进一步可以使用矩阵变换来实现更复杂的物体变换

这里使用矩阵变换进行操作

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

//中心投影
//认为摄像机位于(0,0,d)的位置
Vec3 Project(Vec3& a, qval d) {
Vec3 n;
n[0] /= (1 - a[2] / d);
n[1] /= (1 - a[2] / d);
n[2] /= (1 - a[2] / d);
return n;
}

//矩阵乘法
void Mat4Mul(TMat& a, TMat& b, TMat& c) {
TMat temp;
for (quint i = 0; i < 4; i++) {
for (quint j = 0; j < 4; j++) {
temp[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j] + a[i][3] * b[3][j];
}
}
c = temp;
}

//绕x,y,z轴旋转
void TMat::Rotate(qval x, qval y, qval z) {
//绕x轴旋转
float ax[4][4]{
{ 1, 0, 0, 0 },
{ 0, cos(x), -sin(x), 0 },
{ 0, sin(x), cos(x), 0 },
{ 0, 0, 0, 1 }
};
//绕y轴旋转
float ay[4][4]{
{ cos(y), 0, sin(y), 0 },
{ 0, 1, 0, 0 },
{ -sin(y), 0, cos(y), 0 },
{ 0, 0, 0, 1 }
};
//绕z轴旋转
float az[4][4]{
{ cos(z), -sin(z), 0, 0 },
{ sin(z), cos(z), 0, 0 },
{ 0, 0, 1, 0 },
{ 0, 0, 0, 1 }
};
TMat tempx(ax);
TMat tempy(ay);
TMat tempz(az);
TMat temp;
Mat4Mul(tempx, tempy, temp);
Mat4Mul(temp, tempz, temp);
Mat4Mul(temp, *this, *this);
}

//平移
void TMat::Translate(qval x, qval y, qval z) {
float a[4][4]{
{ 1, 0, 0, x },
{ 0, 1, 0, y },
{ 0, 0, 1, z },
{ 0, 0, 0, 1 }
};
TMat temp(a);
Mat4Mul(temp, *this, *this);
}
//缩放
void TMat::Scale(qval x, qval y, qval z) {
float a[4][4]{
{ x, 0, 0, 0 },
{ 0, y, 0, 0 },
{ 0, 0, z, 0 },
{ 0, 0, 0, 1 }
};
TMat temp(a);
Mat4Mul(temp, *this, *this);
}

//中心投影
//认为摄像机位于(0,0,d)的位置
void TMat::Projection(qval d) {
float a[4][4]{
{ 1, 0, 0, 0 },
{ 0, 1, 0, 0 },
{ 0, 0, 1, 0 },
{ 0, 0, -1/d, 1}
};
TMat temp(a);
Mat4Mul(temp, *this, *this);
}

//对顶点应用变换矩阵
void Vec3::Multiply(TMat& tm) {
qval x = vec[0] * tm[0][0] + vec[1] * tm[0][1] + vec[2] * tm[0][2] + tm[0][3];
qval y = vec[0] * tm[1][0] + vec[1] * tm[1][1] + vec[2] * tm[1][2] + tm[1][3];
qval z = vec[0] * tm[2][0] + vec[1] * tm[2][1] + vec[2] * tm[2][2] + tm[2][3];
qval w = vec[0] * tm[3][0] + vec[1] * tm[3][1] + vec[2] * tm[3][2] + tm[3][3];
vec[0] = x / w;
vec[1] = y / w;
vec[2] = z / w;
}

对于物体渲染

最基本的是使用三角形进行物体渲染

将物体表面分割为三角形,并且把三角形渲染到屏幕上

然后要主要渲染的层次关系

比如一个三角形被另一个三角形遮挡,则需要把没有被遮挡的部分渲染出来

这里使用的是Z-buffer算法

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

//利用叉乘判断点是否在三角形内部
//vec3,vec3,vec3,vec2
#define crossProduct(a, b, c, p) \
Vec2 pa(a[0] - p[0], a[1] - p[1]);\
Vec2 pb(b[0] - p[0], b[1] - p[1]);\
Vec2 pc(c[0] - p[0], c[1] - p[1]);\
bool k1 = (pa ^ pb) >= 0;\
bool k2 = (pb ^ pc) >= 0;\
bool k3 = (pc ^ pa) >= 0;\
avalid = (k1 && k2 && k3) || (!(k1||k2||k3));


//在zbuff中绘制三角形
//poscol:三个顶点的颜色(如果不使用颜色混合则使用第一个顶点的颜色)
//use_colmix:是否使用颜色混合
void DrawTriangle(Vec3 pos[3], qcol* poscol = nullptr , bool use_colmix = false) {
qval x1, y1, z1, x2, y2, z2, x3, y3, z3;
x1 = pos[0][0] + screen_width / 2;
y1 = screen_height / 2 - pos[0][1];
z1 = pos[0][2];
x2 = pos[1][0] + screen_width / 2;
y2 = screen_height / 2 - pos[1][1];
z2 = pos[1][2];
x3 = pos[2][0] + screen_width / 2;
y3 = screen_height / 2 - pos[2][1];
z3 = pos[2][2];

Vec3 npos[3];
npos[0][0] = x1;
npos[0][1] = y1;
npos[0][2] = z1;
npos[1][0] = x2;
npos[1][1] = y2;
npos[1][2] = z2;
npos[2][0] = x3;
npos[2][1] = y3;
npos[2][2] = z3;


qval minX = min(x1, min(x2, x3));
minX = minX < 0 ? 0 : minX;
qval maxX = max(x1, max(x2, x3));
maxX = maxX > screen_width ? screen_width : maxX;

qval minY = min(y1, min(y2, y3));
minY = minY < 0 ? 0 : minY;
qval maxY = max(y1, max(y2, y3));
maxY = maxY > screen_height ? screen_height : maxY;

//求平面方程
qval ak, bk, ck, dk;
// A = (y2 - y1)(z3 - z1) - (z2 - z1)(y3 - y1);
ak = (npos[1][1] - npos[0][1]) * (npos[2][2] - npos[0][2]) - (npos[1][2] - npos[0][2]) * (npos[2][1] - npos[0][1]);
// B = (x3 - x1)(z2 - z1) - (z3 - z1)(x2 - x1);
bk = (npos[2][0] - npos[0][0]) * (npos[1][2] - npos[0][2]) - (npos[2][2] - npos[0][2]) * (npos[1][0] - npos[0][0]);
// C = (x2 - x1)(y3 - y1) - (y2 - y1)(x3 - x1);
ck = (npos[1][0] - npos[0][0]) * (npos[2][1] - npos[0][1]) - (npos[1][1] - npos[0][1]) * (npos[2][0] - npos[0][0]);
// D = -(A * x1 + B * y1 + C * z1);
dk = -(ak * npos[0][0] + bk * npos[0][1] + ck * npos[0][2]);

for (quint px = minX;px < maxX;px++) {
for (quint py = minY;py < maxY;py++) {
Vec2 p_(px, py);
//判断点是否在三角形内部
bool avalid;
crossProduct(npos[0], npos[1], npos[2], p_);
if (avalid) {
//计算p点的深度
qval pz = -(ak * px + bk * py + dk) / ck;
//判断深度是否在摄像头视锥体内
if (pz > Zbuf[py][px]) {
//计算深度
Zbuf[py][px] = pz;
//计算颜色
Zcol[py][px] = poscol[0];
}
}
}
}
}


这里仅展示部分代码,但也够用

对于简单的3d渲染也用不打破太复杂的技术

渲染效果如下

1.png

Author:tastynoob
Link:https://tastynoob.github.io/1970/01/01/%E7%AE%97%E6%B3%95/%E4%B8%80%E4%B8%AA%E5%8D%81%E5%88%86%E7%AE%80%E5%8D%95%E7%9A%843d%E6%B8%B2%E6%9F%93%E5%99%A8/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×