render-zig

A 3D rendering engine written in Zig
git clone git://git.christianermann.dev/render-zig
Log | Files | Refs

commit e8c54fdd890cb1cd5da91b2de598de228d03a766
parent 50544678c6d4e3c38597da9dcf23562fa02942c6
Author: Christian Ermann <christianermann@gmail.com>
Date:   Sun,  8 Dec 2024 13:46:44 -0800

Add UV sphere

Diffstat:
Msrc/camera.zig | 4++--
Msrc/main.zig | 10++++++++++
Asrc/uvsphere.zig | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 184 insertions(+), 2 deletions(-)

diff --git a/src/camera.zig b/src/camera.zig @@ -75,12 +75,12 @@ fn f32x3_dot(u: f32x3, v: f32x3) f32 { return @reduce(.Add, u * v); } -fn f32x3_normalize(v: f32x3) f32x3 { +pub fn f32x3_normalize(v: f32x3) f32x3 { return v / @as(f32x3, @splat(@sqrt(f32x3_dot(v, v)))); } /// Modified from https://geometrian.com/programming/tutorials/cross-product/index.php -fn f32x3_mul(u: f32x3, v: f32x3) f32x3 { +pub fn f32x3_mul(u: f32x3, v: f32x3) f32x3 { const mask = @Vector(3, i32){ 1, 2, 0 }; const t1 = u * @shuffle(f32, v, undefined, mask); const t2 = v * @shuffle(f32, u, undefined, mask); diff --git a/src/main.zig b/src/main.zig @@ -428,6 +428,9 @@ pub fn main() !void { const cbunny_mesh = try buildClusters(allocator, &bunny_mesh, 64, 126); std.log.info("num clusters: {}", .{cluster_mesh.descriptors.?.len}); + const generateUVSphere = @import("uvsphere.zig").generateUVSphere; + const sphere_mesh = try generateUVSphere(16, 16, allocator); + var rp = try MeshRenderPipeline.init( &app, .{ @@ -458,6 +461,7 @@ pub fn main() !void { var transform_1 = Transform{}; var transform_2 = Transform{}; + var transform_3 = Transform{ .offset = .{ 3, 3, 3 } }; var render_mesh = try render_data.addMesh( &transform_1, @@ -471,6 +475,12 @@ pub fn main() !void { allocator, ); + _ = try render_data.addMesh( + &transform_3, + &sphere_mesh, + allocator, + ); + const corners = mesh.bboxVertices(); const bbox_indices = Mesh.bboxIndices(); diff --git a/src/uvsphere.zig b/src/uvsphere.zig @@ -0,0 +1,172 @@ +const std = @import("std"); + +const Mesh = @import("mesh.zig"); +const f32x3_normalize = @import("camera.zig").f32x3_normalize; +const f32x3_mul = @import("camera.zig").f32x3_mul; + +const f32x3 = @Vector(3, f32); +const f32x4 = @Vector(4, f32); + +const pi: f32 = 3.14159; + +pub fn generateUVSphere( + n_rows: u32, + n_cols: u32, + allocator: std.mem.Allocator, +) !Mesh { + const positions = try generatePositions(n_rows, n_cols, allocator); + const normals = try generateNormals(positions, allocator); + const tex_coords = try generateTexCoordsEqualArea(normals, allocator); + const tangents = try generateTangents(positions, allocator); + const bitangents = try generateBitangents(normals, tangents, allocator); + + const n_vertices: u32 = @intCast(positions.len); + const indices = try generateIndices(n_rows, n_cols, n_vertices, allocator); + + return .{ + .positions = positions, + .normals = normals, + .tex_coords = tex_coords, + .indices = indices, + .tangents = tangents, + .bitangents = bitangents, + }; +} + +fn generatePositions( + n_rows: u32, + n_cols: u32, + allocator: std.mem.Allocator, +) ![]f32x4 { + var positions = std.ArrayList(f32x4).init(allocator); + defer positions.deinit(); + // top + try positions.append(f32x4{ 0.0, 1.0, 0.0, 1.0 }); + // middle + for (1..n_rows) |i| { + const phi = pi * @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(n_rows)); + for (0..n_cols) |j| { + const theta = 2.0 * pi * @as(f32, @floatFromInt(j)) / @as(f32, @floatFromInt(n_cols)); + const x = @sin(phi) * @cos(theta); + const y = @cos(phi); + const z = @sin(phi) * @sin(theta); + try positions.append(f32x4{ x, y, z, 1.0 }); + } + } + // bottom + try positions.append(f32x4{ 0.0, -1.0, 0.0, 1.0 }); + return try positions.toOwnedSlice(); +} + +fn generateNormals( + positions: []f32x4, + allocator: std.mem.Allocator, +) ![]f32x3 { + const normals = try allocator.alloc(f32x3, positions.len); + for (positions, normals) |p, *n| { + n.* = f32x3_normalize(.{ p[0], p[1], p[2] }); + } + return normals; +} + +fn generateTangents( + positions: []f32x4, + allocator: std.mem.Allocator, +) ![]f32x3 { + const up = f32x3{ 0, 1, 0 }; + const tangents = try allocator.alloc(f32x3, positions.len); + for (positions, tangents) |p, *t| { + t.* = f32x3_normalize(f32x3_mul(up, .{ p[0], p[1], p[2] })); + } + tangents[0] = .{ 1, 0, 0 }; + tangents[tangents.len - 1] = .{ 1, 0, 0 }; + return tangents; +} + +fn generateBitangents( + normals: []f32x3, + tangents: []f32x3, + allocator: std.mem.Allocator, +) ![]f32x3 { + const bitangents = try allocator.alloc(f32x3, normals.len); + for (normals, tangents, bitangents) |n, t, *b| { + b.* = f32x3_mul(n, t); + } + bitangents[0] = .{ 0, 0, 1 }; + bitangents[bitangents.len - 1] = .{ 0, 0, 1 }; + return bitangents; +} + +fn generateTexCoordsEqualArea( + normals: []f32x3, + allocator: std.mem.Allocator, +) ![]f32x3 { + const tex_coords = try allocator.alloc(f32x3, normals.len); + for (normals, tex_coords) |n, *tc| { + tc.* = .{ + (std.math.atan2(n[0], -n[2]) / pi + 1.0) * 0.5, + std.math.asin(n[1]) / pi + 0.5, + 0.0, + }; + } + return tex_coords; +} + +fn generateTexCoordsEquirectangular( + normals: []f32x3, + allocator: std.mem.Allocator, +) ![]f32x3 { + const tex_coords = try allocator.alloc(f32x3, normals.len); + for (normals, tex_coords) |n, *tc| { + tc.* = .{ + // equirectangular + std.math.atan2(n[0], n[2]) / (2.0 * pi) + 0.5, + n[1] * 0.5 + 0.5, + 0.0, + }; + } +} + +fn generateIndices( + n_rows: u32, + n_cols: u32, + n_vertices: u32, + allocator: std.mem.Allocator, +) ![]u32 { + var indices = std.ArrayList(u32).init(allocator); + defer indices.deinit(); + // top + for (0..n_cols) |i| { + const a = (i + 1) % n_rows + 1; + const b = i + 1; + try indices.append(0); + try indices.append(@intCast(a)); + try indices.append(@intCast(b)); + } + // bottom + for (0..n_cols) |i| { + const a = i + n_cols * (n_rows - 2) + 1; + const b = (i + 1) % n_cols + n_cols * (n_rows - 2) + 1; + try indices.append(@intCast(n_vertices - 1)); + try indices.append(@intCast(a)); + try indices.append(@intCast(b)); + } + // center + for (0..n_rows - 2) |j| { + const x = j * n_cols + 1; + const y = (j + 1) * n_cols + 1; + for (0..n_cols) |i| { + const a = x + i; + const b = x + (i + 1) % n_cols; + const c = y + (i + 1) % n_cols; + const d = y + i; + try indices.append(@intCast(a)); + try indices.append(@intCast(b)); + try indices.append(@intCast(c)); + try indices.append(@intCast(a)); + try indices.append(@intCast(c)); + try indices.append(@intCast(d)); + } + } + return try indices.toOwnedSlice(); +}