用JS实现一个3D模型渲染器

之前我们讲了一系列的关于Unity渲染原理的博客,这次我们就开始实战一下,首先是用js实现一下矩阵和向量的计算,这个是基础们也是我们系里博客的第一篇:线性代数与模型变换

向量计算

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
class Vector extends GObject {
// 表示二维点的类,二维点需要三个坐标表示,是因为齐次坐标的表示下,可以将平移旋转和缩放统一到一个表达式中,具体可以参考开头的博客
constructor(x, y, z) {
super()
this.x = x
this.y = y
this.z = z
}
// 两个向量之间做插值
interpolate(other, factor) {
let p1 = this
let p2 = other
let x = p1.x + (p2.x - p1.x) * factor
let y = p1.y + (p2.y - p1.y) * factor
let z = p1.z + (p2.z - p1.z) * factor
return Vector.new(x, y, z)
}
toString() {
let s = ''
s += this.x.toFixed(3)
s += this.y.toFixed(3)
s += this.z.toFixed(3)
return s
}
multi_num(n) {
return Vector.new(this.x * n, this.y * n, this.z * n)
}
sub(v) {
let x = this.x - v.x
let y = this.y - v.y
let z = this.z - v.z
return Vector.new(x, y, z)
}
length() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
}
// 将向量变为单位向量
normalize() {
let l = this.length()
if (l == 0) {
return this
}
let factor = 1 / l

return this.multi_num(factor)
}
// 向量点乘
dot(v) {
return this.x * v.x + this.y * v.y + this.z * v.z
}
// 向量叉乘
cross(v) {
let x = this.y * v.z - this.z * v.y
let y = this.z * v.x - this.x * v.z
let z = this.x * v.y - this.y * v.x
return Vector.new(x, y, z)
}
}

矩阵计算

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
class Matrix extends GObject {
constructor(matrix_list) {
super()
if (matrix_list) {
this.m = matrix_list
} else {
this.m = [
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
]
}
}
toString() {
let s = ''
let m = this.m
for (let i = 0; i < m.length; i++) {
s += m[i].toFixed(3)
}
return s
}
multiply(other) {
let m1 = this.m
let m2 = other.m
let m = []
for (let index = 0; index < 16; index++) {
let i = Math.floor(index / 4)
let j = index % 4
m[i * 4 + j] = m1[i * 4] * m2[j] + m1[i * 4 + 1] * m2[4 + j] + m1[i * 4 + 2] * m2[2 * 4 + j] + m1[i * 4 + 3] * m2[3 * 4 + j]
}
return Matrix.new(m)
}
static zero() {
return this.new()
}
static identity() {
m = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]
return this.new(m)
}
static lookAtLH(eye, target, up) {
// z轴方向为眼睛位置指向目标物体的方向
let zaxis = target.sub(eye).normalize()
// x方向为up和z方向的叉乘的结果,简单来说,就是右手定则,拇指从up方向,旋转到z方向,大拇指指向即位x方向
let xaxis = up.cross(zaxis).normalize()
// y方向为z方向与x方向的叉乘结果
let yaxis = zaxis.cross(xaxis).normalize()
// 最终x,y,z三个组成直角坐标轴

let ex = -xaxis.dot(eye)
let ey = -yaxis.dot(eye)
let ez = -zaxis.dot(eye)

let m = [
xaxis.x, yaxis.x, zaxis.x, 0,
xaxis.y, yaxis.y, zaxis.y, 0,
xaxis.z, yaxis.z, zaxis.z, 0,
ex, ey, ez, 1,
]
return Matrix.new(m)
}
static perspectiveFovLH(field_of_view, aspect, znear, zfar) {
let h = 1 / Math.tan(field_of_view / 2)
let w = h / aspect
let m = [
w, 0, 0, 0,
0, h, 0, 0,
0, 0, zfar / (zfar - znear), 1,
0, 0, (znear * zfar) / (znear - zfar), 0,
]
return Matrix.new(m)
}
static rotationX(angle) {
let s = Math.sin(angle)
let c = Math.cos(angle)
let m = [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1,
]
return Matrix.new(m)
}
static rotationY(angle) {
let s = Math.sin(angle)
let c = Math.cos(angle)
let m = [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1,
]
return Matrix.new(m)
}
static rotationZ(angle) {
let s = Math.sin(angle)
let c = Math.cos(angle)
let m = [
c, s, 0, 0,
-s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]
return Matrix.new(m)
}
static rotation(angle) {
let x = Matrix.rotationZ(angle.z)
let y = Matrix.rotationX(angle.x)
let z = Matrix.rotationY(angle.y)
return x.multiply(y).multiply(z)
}
static translation(v) {
let { x, y, z } = v
let m = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
x, y, z, 1,
]
return Matrix.new(m)
}
transform(v) {
let m = this.m
let x = v.x * m[0] + v.y * m[1 * 4 + 0] + v.z * m[2 * 4 + 0] + m[3 * 4 + 0]
let y = v.x * m[1] + v.y * m[1 * 4 + 1] + v.z * m[2 * 4 + 1] + m[3 * 4 + 1]
let z = v.x * m[2] + v.y * m[1 * 4 + 2] + v.z * m[2 * 4 + 2] + m[3 * 4 + 2]
let w = v.x * m[3] + v.y * m[1 * 4 + 3] + v.z * m[2 * 4 + 3] + m[3 * 4 + 3]

return Vector.new(x / w, y / w, z / w)
}
}