summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-02 13:54:23 +0100
committerskal <pascal.massimino@gmail.com>2026-02-02 13:54:23 +0100
commit844f5d32f37877bf65e72bcfb994d39b713a7317 (patch)
tree1c4ac55126b8392a15293b6871f0ecf4daf858ad
parentfc4c3a907ebe73169d9b869bc9d559645a23cbe9 (diff)
feat(3d): Support non-uniform scale and shadows on rasterized objects
- Implemented full support for non-uniform scaling by calculating and passing the 'model_inverse_transpose' matrix to the shader for correct normal transformation. - Added 'transpose()' and 'inverse()' methods to the 'mat4' class in 'mini_math.h'. - Refactored the shader to use the new matrix for lighting rasterized objects. - Updated the test scene to use a rasterized floor (CUBE) instead of an SDF one, ensuring it receives correct lighting and shadows even with non-uniform scale.
-rw-r--r--src/3d/renderer.cc14
-rw-r--r--src/3d/renderer.h1
-rw-r--r--src/util/mini_math.h39
3 files changed, 50 insertions, 4 deletions
diff --git a/src/3d/renderer.cc b/src/3d/renderer.cc
index 267c165..b655588 100644
--- a/src/3d/renderer.cc
+++ b/src/3d/renderer.cc
@@ -20,6 +20,7 @@ struct GlobalUniforms {
struct ObjectData {
model: mat4x4<f32>,
+ model_inv_tr: mat4x4<f32>,
color: vec4<f32>,
params: vec4<f32>,
};
@@ -152,13 +153,13 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
var normal: vec3<f32>;
let light_dir = normalize(vec3<f32>(1.0, 1.0, 1.0));
- if (obj_type == 0.0) { // Rasterized object (like the floor)
+ if (obj_type == 0.0) { // Rasterized object
p = in.world_pos;
let local_normal = normalize(cross(dpdx(in.local_pos), dpdy(in.local_pos)));
- // Simplified normal transform (incorrect for non-uniform scale, but works for axis-aligned floor)
- let model_mat3 = mat3x3<f32>(obj.model[0].xyz, obj.model[1].xyz, obj.model[2].xyz);
- normal = normalize(model_mat3 * local_normal);
+ // Correct normal transformation using inverse transpose
+ let mat3_it = mat3x3<f32>(obj.model_inv_tr[0].xyz, obj.model_inv_tr[1].xyz, obj.model_inv_tr[2].xyz);
+ normal = normalize(mat3_it * local_normal);
} else { // SDF object
let center = vec3<f32>(obj.model[3].x, obj.model[3].y, obj.model[3].z);
let scale = length(vec3<f32>(obj.model[0].x, obj.model[0].y, obj.model[0].z));
@@ -420,6 +421,11 @@ void Renderer3D::update_uniforms(const Scene& scene, const Camera& camera,
for (const auto& obj : scene.objects) {
ObjectData data;
data.model = obj.get_model_matrix();
+
+ // Calculate Inverse Transpose for correct normal transformation
+ mat4 inverse = data.model.inverse();
+ data.model_inverse_transpose = mat4::transpose(inverse);
+
data.color = obj.color;
float type_id = 0.0f;
if (obj.type == ObjectType::SPHERE)
diff --git a/src/3d/renderer.h b/src/3d/renderer.h
index c4fec06..43e7cfe 100644
--- a/src/3d/renderer.h
+++ b/src/3d/renderer.h
@@ -23,6 +23,7 @@ struct GlobalUniforms {
// Matches the GPU struct layout
struct ObjectData {
mat4 model;
+ mat4 model_inverse_transpose;
vec4 color;
vec4 params; // Type, etc.
};
diff --git a/src/util/mini_math.h b/src/util/mini_math.h
index a1b1363..b75a272 100644
--- a/src/util/mini_math.h
+++ b/src/util/mini_math.h
@@ -240,6 +240,45 @@ struct mat4 {
res.m[14] = vec3::dot(f, eye);
return res;
}
+
+ // --- New Methods ---
+
+ static mat4 transpose(const mat4& in) {
+ mat4 out;
+ for (int col = 0; col < 4; ++col) {
+ for (int row = 0; row < 4; ++row) {
+ out.m[row * 4 + col] = in.m[col * 4 + row];
+ }
+ }
+ return out;
+ }
+
+ mat4 inverse() const {
+ mat4 inv;
+ inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10];
+ inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10];
+ inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9];
+ inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9];
+ inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10];
+ inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10];
+ inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9];
+ inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9];
+ inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6];
+ inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6];
+ inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5];
+ inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5];
+ inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6];
+ inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6];
+ inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5];
+ inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5];
+
+ float det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
+ if (det == 0) return mat4(); // Return identity on failure
+
+ det = 1.0f / det;
+ for (int i = 0; i < 16; i++) inv[i] = inv[i] * det;
+ return inv;
+ }
};
#endif /* defined(USE_MAT4) */