-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender.cpp
More file actions
209 lines (163 loc) · 7.11 KB
/
render.cpp
File metadata and controls
209 lines (163 loc) · 7.11 KB
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include "render.h"
float **zbuf = NULL;
void resetZbuf(int x, int y) {
// set all values in buffer to infinity
for(int i = 0; i < x; i++) {
for(int j = 0; j < y; j++) {
zbuf[i][j] = INFINITY;
}
}
}
void initZBuf(int x, int y) {
if(zbuf == NULL) {
zbuf = (float **) malloc(sizeof(float*) * x);
for(int i = 0; i < x; i++) {
zbuf[i] = (float*) malloc(sizeof(float) * y);
}
}
resetZbuf(x, y);
}
void destructZbuf(int x, int y) {
for(int i = 0; i < x; i++) {
free(zbuf[i]);
}
free(zbuf);
}
// takes all three points in the triangle and applies the mat4 as an affine transformation.
void multiplyTriByMatrix(Tri *triPtr, Mat4 *matPtr) {
triPtr->v1.position = vec3(multiply(*matPtr, vec4(triPtr->v1.position)));
triPtr->v2.position = vec3(multiply(*matPtr, vec4(triPtr->v2.position)));
triPtr->v3.position = vec3(multiply(*matPtr, vec4(triPtr->v3.position)));
}
/*
* Take an array of triangles (in camera space)
* and converts them all into screen space.
*/
void convertToWindowCoordinates(Tri *meshdata, int triCount, Tri *result, Mat4 modelTransform, Mat4 cameraTransform) {
// matrix to convert a screen space
Mat4 planeToScreen {{
{180, 0, 0, 180},
{0, -180, 0, 180},
{0, 0, 1, 0},
{0, 0, 0, 1}
}};
// TODO parallelize these pixel calculations using worker threads?
for(int i = 0; i < triCount; i++) {
result[i] = meshdata[i];
multiplyTriByMatrix(&result[i], &modelTransform);
multiplyTriByMatrix(&result[i], &cameraTransform);
result[i].v1.position.x /= result[i].v1.position.z;
result[i].v1.position.y /= result[i].v1.position.z;
result[i].v2.position.x /= result[i].v2.position.z;
result[i].v2.position.y /= result[i].v2.position.z;
result[i].v3.position.x /= result[i].v3.position.z;
result[i].v3.position.y /= result[i].v3.position.z;
multiplyTriByMatrix(&result[i], &planeToScreen);
}
}
/*
* Takes a triangle and set of barycentric coordinates and returns an interpolated z value
*/
float interpolateZ(Tri t, Vector3 b) {
return t.v1.position.z * b.x + t.v2.position.z * b.y + t.v3.position.z * b.z;
}
/*
* Take a set of screen-space triangles and rasterize them to the surface
*/
void renderTris(Tri *screenSpaceData, int triCount, SDL_Surface *surface) {
// avoid corruption by locking the surface (thread-safety..? idk)
SDL_LockSurface(surface);
// for each triangle, we will iterate over all the pixels in the surface
// and determine if the pixel intersects with the triangle (and color it)
Uint32 *pixels = (Uint32*)surface->pixels;
// clipping planes
float near = 1.f; // plane defined as z = near
float far = 80000.f; // plane defined as z = far
for(int i = 0; i < triCount; i++) {
Tri currentTri = screenSpaceData[i];
// calculate a bounding box for the triangle before iterating over tris
int minX = (int) fminf(fminf(currentTri.v1.position.x, currentTri.v2.position.x), currentTri.v3.position.x) - 1;
int minY = (int) fminf(fminf(currentTri.v1.position.y, currentTri.v2.position.y), currentTri.v3.position.y) - 1;
int maxX = (int) fmaxf(fmaxf(currentTri.v1.position.x, currentTri.v2.position.x), currentTri.v3.position.x) + 1;
int maxY = (int) fmaxf(fmaxf(currentTri.v1.position.y, currentTri.v2.position.y), currentTri.v3.position.y) + 1;
// iterate over the pixels in the bounding box (do not go past edges of screen)
for(int y = minY > 0 ? minY : 0; y < (maxY < surface->h ? maxY : surface->h); y++) {
for(int x = minX > 0 ? minX : 0; x < (maxX < surface->w ? maxX : surface->w); x++) {
// address arithmetic - "pitch" tells us the length of each row, in bytes,
// and each individual pixel is an uint32.
Uint32 *currentPixel = ((Uint32*) (((char*) pixels) + (surface->pitch * y))) + x;
Vector3 tricoord = barycentric(currentTri, {(float) x,(float) y, 0.0});
float depth = interpolateZ(currentTri, tricoord);
// check if the current pixel is actually within the triangle.
if(tricoord.x >= 0 && tricoord.y >= 0 && tricoord.z >= 0) {
// if the current tri is behind something in the z buffer, skip this pixel.
if(zbuf[x][y] < depth) {
continue;
}
// if the current pixel is not within the near or far plane, skip writing the pixel.
if(depth < near || depth > far) {
continue;
}
// write the pixel to the z buffer
zbuf[x][y] = depth;
// calculate the fragment color (apply fragment shader type code here)
SDL_Color fragColor = interpolateColor(currentTri, tricoord);
// write the pixel to the screen
*currentPixel = SDL_MapRGBA(surface->format, fragColor.r, fragColor.g, fragColor.b, fragColor.a);
}
}
}
}
SDL_UnlockSurface(surface);
}
/*
* Takes a triangle and a set of barycentric coordinates and returns
* an interpolated color across the color data of all three vertices.
*/
SDL_Color interpolateColor(Tri t, Vector3 b) {
Uint8 lerpR = t.v1.color.r * b.x + t.v2.color.r * b.y + t.v3.color.r * b.z;
Uint8 lerpG = t.v1.color.g * b.x + t.v2.color.g * b.y + t.v3.color.g * b.z;
Uint8 lerpB = t.v1.color.b * b.x + t.v2.color.b * b.y + t.v3.color.b * b.z;
Uint8 lerpA = t.v1.color.a * b.x + t.v2.color.a * b.y + t.v3.color.a * b.z;
return {lerpR, lerpG, lerpB, lerpA};
}
/*
* Get the barycentric coordinates of a point with respect to a triangle
*/
Vector3 barycentric(Tri t, Vector3 p) {
// calculate vectors from point p to each triangle point
Vector3 pa = VEC_SUB(p, t.v1.position);
Vector3 pb = VEC_SUB(p, t.v2.position);
Vector3 pc = VEC_SUB(p, t.v3.position);
// calculate the areas of the subtriangles
float area1 = CROSS2D(pb, pc);
float area2 = CROSS2D(pc, pa);
float area3 = CROSS2D(pa, pb);
// calculate the total area
Vector3 ab = VEC_SUB(t.v2.position, t.v1.position);
Vector3 ac = VEC_SUB(t.v3.position ,t.v1.position);
float totalarea = CROSS2D(ab, ac);
// calculate barycentric coordinates
float t1 = area1 / totalarea;
float t2 = area2 / totalarea;
float t3 = area3 / totalarea;
return {t1,t2,t3};
}
/*
* Put it all together.
*/
void render3D(Tri *meshdata, int triCount, SDL_Surface *screen, Mat4 model, Mat4 cam) {
// initialization of z-buffer if necessary
if(zbuf == NULL) {
initZBuf(screen->w, screen->h);
}
else {
resetZbuf(screen->w, screen->h);
}
// space transforms
Tri *screenspace = (Tri*) malloc(sizeof(Tri) * triCount);
convertToWindowCoordinates(meshdata, triCount, screenspace, model, cam);
// put triangles through the rasterizer!
renderTris(screenspace, triCount, screen);
free(screenspace);
}