t
Loading...
Searching...
No Matches
Rasterizer.hpp
1#include "cameras/Camera.hpp"
2#include "math/Matrix3x3.hpp"
3#include "primitives/Fragment.hpp"
4#include "primitives/Mesh.hpp"
5#include "primitives/RenderTarget.hpp"
6#include "primitives/Scene.hpp"
7#include <cmath>
8#include <functional>
9#include <set>
10#include <stack>
11
12#ifndef RASTERIZER_HPP
13#define RASTERIZER_HPP
14
15namespace t {
16
26public:
34 template <class BufferType>
35 void render(Scene &scene, Camera &camera,
36 RenderTarget<BufferType> &renderTarget) {
37 RenderTarget<double> depthTexture(renderTarget.texture.width,
38 renderTarget.texture.height,
40 // Clear the depth texture and the render target
41
42 for (int i = 0; i < renderTarget.width * renderTarget.height; ++i) {
43 depthTexture.texture.image[i] = 2; // NDC Z ranges from -1 to 1
44 renderTarget.texture.image[i * 3] = 0;
45 renderTarget.texture.image[i * 3 + 1] = 0;
46 renderTarget.texture.image[i * 3 + 2] = 0;
47 }
48
49 // Traverse the 3D scene tree and update the local and world matrices
50
51 std::vector<std::reference_wrapper<Mesh>> meshes;
52 std::vector<std::reference_wrapper<Light>> lights;
53 std::stack<std::reference_wrapper<Object3D>> objects;
54
55 objects.push(scene);
56
57 while (!objects.empty()) {
58 Object3D &parent = objects.top();
59 objects.pop();
60
61 parent.updateLocalMatrix();
62 parent.updateModelMatrix();
63
64 for (Object3D &child : parent.children) {
65 if (child.isMesh()) {
66 child.updateLocalMatrix();
67 child.updateModelMatrix();
68 meshes.push_back(static_cast<Mesh &>(child));
69 } else {
70 objects.push(child);
71
72 if (child.isLight()) {
73 lights.push_back(static_cast<Light &>(child));
74 }
75 }
76 }
77 }
78
79 const auto cameraWorldPosHomo =
80 camera.modelMatrix * Vector4(camera.localPosition, 1);
81 auto cameraWorldPos = Vector3(cameraWorldPosHomo.x / cameraWorldPosHomo.w,
82 cameraWorldPosHomo.y / cameraWorldPosHomo.w,
83 cameraWorldPosHomo.z / cameraWorldPosHomo.w);
84
85 // Compute the view matrix
86
87 // FIXME: don't hardcode the lookat
88 auto w = (cameraWorldPos - Vector3(0, 0, 0)).normalize();
89 auto u = Vector3::cross(w, camera.up).normalize();
90 auto v = Vector3::cross(w, u);
91
92 // clang-format off
93 auto viewMatrix = Matrix4x4(
94 u.x, u.y, u.z, 0,
95 v.x, v.y, v.z, 0,
96 w.x, w.y, w.z, 0,
97 0, 0, 0, 1
98 ) * Matrix4x4(
99 1, 0, 0, -cameraWorldPos.x,
100 0, 1, 0, -cameraWorldPos.y,
101 0, 0, 1, -cameraWorldPos.z,
102 0, 0, 0, 1
103 );
104 // clang-format on
105
106 // Compute the viewport transform matrix‒used to transform from clip space
107 // to viewport space
108
109 // clang-format off
110 auto viewportMatrix = Matrix4x4(
111 renderTarget.width / 2.0, 0, 0, (renderTarget.width - 1) / 2.0,
112 0, renderTarget.height / 2.0, 0, (renderTarget.height - 1) / 2.0,
113 0, 0, 1, 0,
114 0, 0, 0, 1
115 );
116 // clang-format on
117
118 // Draw the meshes; one mesh per "draw call".
119
120 for (Mesh &mesh : meshes) {
121 auto geometry = mesh.geometry;
122 auto modelViewMatrix = viewMatrix * mesh.modelMatrix;
123 auto normalMatrix =
124 mesh.modelMatrix.topLeft3x3Matrix().inverse().transpose();
125
126 if (geometry.faceIndices) {
127 for (int i = 0; i < geometry.faceIndices.value().array.size(); i += 3) {
128 const int vertexAIndex = geometry.faceIndices.value().array.at(i);
129 const int vertexBIndex = geometry.faceIndices.value().array.at(i + 1);
130 const int vertexCIndex = geometry.faceIndices.value().array.at(i + 2);
131
132 processTriangle(vertexAIndex, vertexBIndex, vertexCIndex, geometry,
133 mesh, modelViewMatrix, camera, viewMatrix,
134 normalMatrix, cameraWorldPos, viewportMatrix, lights,
135 renderTarget, depthTexture);
136 }
137 } else {
138 for (int i = 0; i < geometry.vertexPositions.array.size() / 3; i += 3) {
139 processTriangle(i, i + 1, i + 2, geometry, mesh, modelViewMatrix,
140 camera, viewMatrix, normalMatrix, cameraWorldPos,
141 viewportMatrix, lights, renderTarget, depthTexture);
142 }
143 }
144 }
145 }
146
147private:
148 template <class BufferType>
149 void processTriangle(int vertexAIndex, int vertexBIndex, int vertexCIndex,
150 Geometry &geometry, Mesh &mesh,
151 Matrix4x4 &modelViewMatrix, Camera &camera,
152 Matrix4x4 &viewMatrix, Matrix3x3 &normalMatrix,
153 Vector3 &cameraPosition, Matrix4x4 &viewportMatrix,
154 std::vector<std::reference_wrapper<Light>> &lights,
155 RenderTarget<BufferType> &renderTarget,
156 RenderTarget<double> &depthTexture) {
157 auto localVertexA =
158 Vector3::fromBufferAttribute(geometry.vertexPositions, vertexAIndex);
159 auto localVertexB =
160 Vector3::fromBufferAttribute(geometry.vertexPositions, vertexBIndex);
161 auto localVertexC =
162 Vector3::fromBufferAttribute(geometry.vertexPositions, vertexCIndex);
163
164 auto vertexANormal =
165 Vector3::fromBufferAttribute(geometry.vertexNormals, vertexAIndex);
166 auto vertexBNormal =
167 Vector3::fromBufferAttribute(geometry.vertexNormals, vertexBIndex);
168 auto vertexCNormal =
169 Vector3::fromBufferAttribute(geometry.vertexNormals, vertexCIndex);
170
171 Uniforms uniforms{
172 mesh.modelMatrix, modelViewMatrix, camera.projectionMatrix,
173 viewMatrix, normalMatrix, cameraPosition};
174 Attributes attributes{localVertexA, vertexANormal};
175 Varyings varyingsVertexA = {localVertexA, vertexANormal};
176 Varyings varyingsVertexB = {localVertexB, vertexBNormal};
177 Varyings varyingsVertexC = {localVertexC, vertexCNormal};
178
179 const auto transformedVertexA =
180 mesh.material.vertexShader(uniforms, attributes);
181
182 attributes.localPosition = localVertexB;
183 attributes.localNormal = vertexBNormal;
184 const auto transformedVertexB =
185 mesh.material.vertexShader(uniforms, attributes);
186
187 attributes.localPosition = localVertexC;
188 attributes.localNormal = vertexCNormal;
189 const auto transformedVertexC =
190 mesh.material.vertexShader(uniforms, attributes);
191
192 auto screenSpaceVertexA = viewportMatrix * transformedVertexA;
193 auto screenSpaceVertexB = viewportMatrix * transformedVertexB;
194 auto screenSpaceVertexC = viewportMatrix * transformedVertexC;
195
196 screenSpaceVertexA /= screenSpaceVertexA.w;
197 screenSpaceVertexA.w = 1.0 / transformedVertexA.w;
198 screenSpaceVertexB /= screenSpaceVertexB.w;
199 screenSpaceVertexB.w = 1.0 / transformedVertexB.w;
200 screenSpaceVertexC /= screenSpaceVertexC.w;
201 screenSpaceVertexC.w = 1.0 / transformedVertexC.w;
202
203 const auto clockwiseMatrix = Matrix3x3(
204 screenSpaceVertexA.x, screenSpaceVertexA.y, 1, screenSpaceVertexB.x,
205 screenSpaceVertexB.y, 1, screenSpaceVertexC.x, screenSpaceVertexC.y, 1);
206
207 if (clockwiseMatrix.determinant() *
208 static_cast<int>(mesh.geometry.frontFace) *
209 static_cast<int>(mesh.material.cullMode) <
210 0) {
211 return;
212 }
213
214 const auto maxY = static_cast<int>(std::round(
215 std::max(std::max(screenSpaceVertexA.y, screenSpaceVertexB.y),
216 screenSpaceVertexC.y)));
217 const auto minY = static_cast<int>(std::round(
218 std::min(std::min(screenSpaceVertexA.y, screenSpaceVertexB.y),
219 screenSpaceVertexC.y)));
220 const auto yDiff = maxY - minY;
221
222 std::vector<std::set<Fragment>> fragments(yDiff + 1);
223
224 const auto bresenhamAB =
225 bresenham(static_cast<int>(std::round(screenSpaceVertexA.x)),
226 static_cast<int>(std::round(screenSpaceVertexA.y)),
227 static_cast<int>(std::round(screenSpaceVertexB.x)),
228 static_cast<int>(std::round(screenSpaceVertexB.y)));
229 const auto bresenhamBC =
230 bresenham(static_cast<int>(std::round(screenSpaceVertexB.x)),
231 static_cast<int>(std::round(screenSpaceVertexB.y)),
232 static_cast<int>(std::round(screenSpaceVertexC.x)),
233 static_cast<int>(std::round(screenSpaceVertexC.y)));
234 const auto bresenhamCA =
235 bresenham(static_cast<int>(std::round(screenSpaceVertexC.x)),
236 static_cast<int>(std::round(screenSpaceVertexC.y)),
237 static_cast<int>(std::round(screenSpaceVertexA.x)),
238 static_cast<int>(std::round(screenSpaceVertexA.y)));
239
240 for (auto [x, y] : bresenhamAB) {
241 fragments[y - minY].insert(Fragment{x, y});
242 }
243
244 for (auto [x, y] : bresenhamBC) {
245 fragments[y - minY].insert(Fragment{x, y});
246 }
247
248 for (auto [x, y] : bresenhamCA) {
249 fragments[y - minY].insert(Fragment{x, y});
250 }
251
252 for (auto &line : fragments) {
253 auto startFragment = line.begin();
254 auto endFragment = std::next(startFragment);
255
256 const auto y = startFragment->y;
257
258 while (endFragment != line.end()) {
259 for (int x = startFragment->x; x <= endFragment->x; x++) {
260 const auto bary = barycentric(
261 Vector3(x, y, 0),
262 Vector3(screenSpaceVertexA.x, screenSpaceVertexA.y, 0),
263 Vector3(screenSpaceVertexB.x, screenSpaceVertexB.y, 0),
264 Vector3(screenSpaceVertexC.x, screenSpaceVertexC.y, 0));
265
266 const auto perspectiveBary =
267 Vector3(bary.x * screenSpaceVertexA.w,
268 bary.y * screenSpaceVertexB.w,
269 bary.z * screenSpaceVertexC.w) /
270 (bary.x * screenSpaceVertexA.w + bary.y * screenSpaceVertexB.w +
271 bary.z * screenSpaceVertexC.w);
272
273 Vector3 localPosition =
274 varyingsVertexA.localPosition * perspectiveBary.x +
275 varyingsVertexB.localPosition * perspectiveBary.y +
276 varyingsVertexC.localPosition * perspectiveBary.z;
277 Vector3 localNormal =
278 varyingsVertexA.localNormal * perspectiveBary.x +
279 varyingsVertexB.localNormal * perspectiveBary.y +
280 varyingsVertexC.localNormal * perspectiveBary.z;
281
282 Varyings varyings = {localPosition, localNormal};
283
284 Color color =
285 mesh.material.fragmentShader(uniforms, varyings, lights);
286
287 // Clip vertices outside of the screen space
288
289 if (x < 0 || x >= renderTarget.width || y < 0 ||
290 y >= renderTarget.height) {
291 continue;
292 }
293
294 const double z = bary.x * screenSpaceVertexA.z +
295 bary.y * screenSpaceVertexB.z +
296 bary.z * screenSpaceVertexC.z;
297
298 const auto currentDepth = depthTexture.read(x, y).x;
299
300 if (!(mesh.material.depthTest ^ (z <= currentDepth))) {
301 renderTarget.write(x, y, color);
302 if (mesh.material.depthWrite) {
303 depthTexture.write(x, y, Color(z, 0.0, 0.0));
304 }
305 }
306 }
307
308 startFragment = std::next(startFragment);
309 endFragment = std::next(endFragment);
310 }
311 }
312 }
313};
314
315} // namespace t
316
317#endif // RASTERIZER_HPP
The base camera class.
Definition Camera.hpp:26
Matrix4x4 projectionMatrix
The projection matrix of this camera.
Definition Camera.hpp:28
The base 3D geometry class.
Definition Geometry.hpp:37
BufferAttribute< double > vertexNormals
The normal buffer.
Definition Geometry.hpp:54
BufferAttribute< double > vertexPositions
The vertex buffer.
Definition Geometry.hpp:40
FrontFace frontFace
The vertex winding order which classifies the front face of a triangle.
Definition Geometry.hpp:57
The base lighting class.
Definition Light.hpp:17
bool depthTest
Whether or not to perform the depth test.
Definition Material.hpp:28
CullMode cullMode
The face to cull (not draw) in the render.
Definition Material.hpp:26
bool depthWrite
Whether or not to write the depth of the object to the depth texture.
Definition Material.hpp:31
virtual Vector4 vertexShader(const Uniforms &uniforms, const Attributes &attributes)=0
The vertex shader of this material, which will be run for every vertex of the mesh's geometry.
virtual Color fragmentShader(const Uniforms &uniforms, const Varyings &varyings, const std::vector< std::reference_wrapper< Light > > &lights)=0
The fragment shader of this material, which will be run for every fragment that the mesh covers on th...
The matrix class.
Definition Matrix3x3.hpp:24
The matrix class.
Definition Matrix4x4.hpp:35
The triangular 3D mesh class.
Definition Mesh.hpp:18
Material & material
The material of this mesh.
Definition Mesh.hpp:21
Geometry & geometry
The geometry of this mesh.
Definition Mesh.hpp:20
The 3D object class.
Definition Object3D.hpp:24
Vector3 localPosition
The position of this 3D object relative to its parent.
Definition Object3D.hpp:32
Vector3 up
The vector pointing to the up direction in object space.
Definition Object3D.hpp:30
Matrix4x4 modelMatrix
The model matrix of this 3D object, transformating local space to world space.
Definition Object3D.hpp:46
Object3D & updateLocalMatrix()
Updates the local transformation matrix of this 3D object.
Definition Object3D.hpp:144
std::vector< std::reference_wrapper< Object3D > > children
The children of this 3D object.
Definition Object3D.hpp:52
Object3D & updateModelMatrix()
Updates the model matrix of this 3D object.
Definition Object3D.hpp:163
The most basic renderer that renders your beautiful 3D scene.
Definition Rasterizer.hpp:25
void render(Scene &scene, Camera &camera, RenderTarget< BufferType > &renderTarget)
Renders the given scene using the given camera to the given render target.
Definition Rasterizer.hpp:35
Definition RenderTarget.hpp:8
The 3D scene class.
Definition Scene.hpp:18
The 3D vector class.
Definition Vector3.hpp:20
static Vector3 cross(const Vector3 &a, const Vector3 &b)
Returns the cross product of two vectors.
Definition Vector3.hpp:53
Vector3 & normalize()
Normalizes this 3D vector.
Definition Vector3.hpp:159
double x
The x component of this 3D vector.
Definition Vector3.hpp:22
static Vector3 fromBufferAttribute(const BufferAttribute< double > &bufferAttribute, int index)
Returns 3 consecutive values at the specified index in a BufferAttribute at the specified in a Vector...
Definition Vector3.hpp:36
The 4D vector class.
Definition Vector4.hpp:20
The t software 3D graphics library namespace.
Definition algorithms.hpp:12
std::vector< std::pair< int, int > > bresenham(int x0, int y0, int x1, int y1)
Computes and returns the points of a line using the Bresenham's line algorithm.
Definition algorithms.hpp:87
Vector3 barycentric(Vector3 point, Vector3 vertexA, Vector3 vertexB, Vector3 vertexC)
Computes and returns the barycentric coordinates of a point in a triangle.
Definition algorithms.hpp:112
@ Depth
Stores the depth in normalized floating-point values.
The uniforms available to vertex shaders and fragment shaders.
Definition Uniforms.hpp:19