Add textures to rendered primitives

main
copygirl 6 months ago
parent 42619616cc
commit 39c2eb6781
  1. 11
      build.zig
  2. 4
      build.zig.zon
  3. BIN
      src/gfx/default.png
  4. 99
      src/primitives.zig
  5. 74
      src/renderer.zig
  6. 13
      src/shader.wgsl

@ -6,20 +6,17 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const zmath_pkg = zmath.package(b, target, optimize, .{
.options = .{ .enable_cross_platform_determinism = true },
});
const mach_core_dep = b.dependency("mach_core", .{ .target = target, .optimize = optimize });
const zigimg_dep = b.dependency("zigimg", .{ .target = target, .optimize = optimize });
const zmath_pkg = zmath.package(b, target, optimize, .{});
const mach_core_dep = b.dependency("mach_core", .{
.target = target,
.optimize = optimize,
});
const app = try mach_core.App.init(b, mach_core_dep.builder, .{
.name = "zig-bloxel-game",
.src = "src/main.zig",
.target = target,
.optimize = optimize,
.deps = &.{
.{ .name = "zigimg", .module = zigimg_dep.module("zigimg") },
.{ .name = "zmath", .module = zmath_pkg.zmath },
},
});

@ -14,6 +14,10 @@
.url = "https://pkg.machengine.org/mach-core/6a62bcc90e0d072d632788a6575d77942bd09a19.tar.gz",
.hash = "12209d39954fcda0be158461c10f64d14d5c7d097bd6d26785b332d75ffefa7dd7a0",
},
.zigimg = .{
.url = "https://github.com/zigimg/zigimg/archive/ad6ad042662856f55a4d67499f1c4606c9951031.tar.gz",
.hash = "1220bf6b616ca219f95be1205b12aa8cdb7e09838fcebeae90b48b5ab0a030c5ab45",
},
.zmath = .{ .path = "libs/zig-gamedev/libs/zmath" },
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

@ -10,6 +10,7 @@ const createAndWriteBuffer = Renderer.createAndWriteBuffer;
/// Describes the layout of each vertex that a primitive is made of.
pub const VertexData = struct {
position: [3]f32,
uv: [2]f32,
};
/// Contains the data to render a primitive (3D shape or model).
@ -43,8 +44,8 @@ pub fn createPrimitive(
};
}
fn vert(x: f32, y: f32, z: f32) VertexData {
return .{ .position = .{ x, y, z } };
fn vert(x: f32, y: f32, z: f32, tx: f32, ty: f32) VertexData {
return .{ .position = .{ x, y, z }, .uv = .{ tx, ty } };
}
pub fn createTrianglePrimitive(length: f32) PrimitiveData {
@ -60,9 +61,9 @@ pub fn createTrianglePrimitive(length: f32) PrimitiveData {
// / \
// 1-----2
&.{
vert(@sin(a0) * radius, @cos(a0) * radius, 0.0),
vert(@sin(a1) * radius, @cos(a1) * radius, 0.0),
vert(@sin(a2) * radius, @cos(a2) * radius, 0.0),
vert(@sin(a0) * radius, @cos(a0) * radius, 0.0, 0.5, 0.0),
vert(@sin(a1) * radius, @cos(a1) * radius, 0.0, 0.0, 1.0),
vert(@sin(a2) * radius, @cos(a2) * radius, 0.0, 1.0, 1.0),
},
// Vertices have to be specified in counter-clockwise,
// so the "front" of the triangle is facing the right way.
@ -83,10 +84,10 @@ pub fn createSquarePrimitive(width: f32) PrimitiveData {
// 1---3
&.{
// zig fmt: off
vert(-half_width, -half_width, 0.0),
vert(-half_width, half_width, 0.0),
vert( half_width, -half_width, 0.0),
vert( half_width, half_width, 0.0),
vert(-half_width, -half_width, 0.0, 0.0, 0.0),
vert(-half_width, half_width, 0.0, 0.0, 1.0),
vert( half_width, -half_width, 0.0, 1.0, 0.0),
vert( half_width, half_width, 0.0, 1.0, 1.0),
// zig fmt: on
},
// ... but it has to be split up into 2 triangles.
@ -127,35 +128,35 @@ pub fn createCubePrimitive(width: f32) PrimitiveData {
// zig fmt: off
&.{
// Right (+X)
vert( half_width, half_width, -half_width),
vert( half_width, -half_width, -half_width),
vert( half_width, half_width, half_width),
vert( half_width, -half_width, half_width),
vert( half_width, half_width, -half_width, 0.0, 0.0),
vert( half_width, -half_width, -half_width, 0.0, 1.0),
vert( half_width, half_width, half_width, 1.0, 0.0),
vert( half_width, -half_width, half_width, 1.0, 1.0),
// Left (-X)
vert(-half_width, half_width, half_width),
vert(-half_width, -half_width, half_width),
vert(-half_width, half_width, -half_width),
vert(-half_width, -half_width, -half_width),
vert(-half_width, half_width, half_width, 0.0, 0.0),
vert(-half_width, -half_width, half_width, 0.0, 1.0),
vert(-half_width, half_width, -half_width, 1.0, 0.0),
vert(-half_width, -half_width, -half_width, 1.0, 1.0),
// Top (+Y)
vert( half_width, half_width, -half_width),
vert( half_width, half_width, half_width),
vert(-half_width, half_width, -half_width),
vert(-half_width, half_width, half_width),
vert( half_width, half_width, -half_width, 0.0, 0.0),
vert( half_width, half_width, half_width, 0.0, 1.0),
vert(-half_width, half_width, -half_width, 1.0, 0.0),
vert(-half_width, half_width, half_width, 1.0, 1.0),
// Bottom (-Y)
vert(-half_width, -half_width, -half_width),
vert(-half_width, -half_width, half_width),
vert( half_width, -half_width, -half_width),
vert( half_width, -half_width, half_width),
vert(-half_width, -half_width, -half_width, 1.0, 0.0),
vert(-half_width, -half_width, half_width, 0.0, 0.0),
vert( half_width, -half_width, -half_width, 1.0, 1.0),
vert( half_width, -half_width, half_width, 0.0, 1.0),
// Front (+Z)
vert( half_width, half_width, half_width),
vert( half_width, -half_width, half_width),
vert(-half_width, half_width, half_width),
vert(-half_width, -half_width, half_width),
vert( half_width, half_width, half_width, 0.0, 0.0),
vert( half_width, -half_width, half_width, 0.0, 1.0),
vert(-half_width, half_width, half_width, 1.0, 0.0),
vert(-half_width, -half_width, half_width, 1.0, 1.0),
// Back (-Z)
vert(-half_width, half_width, -half_width),
vert(-half_width, -half_width, -half_width),
vert( half_width, half_width, -half_width),
vert( half_width, -half_width, -half_width),
vert(-half_width, half_width, -half_width, 0.0, 0.0),
vert(-half_width, -half_width, -half_width, 0.0, 1.0),
vert( half_width, half_width, -half_width, 1.0, 0.0),
vert( half_width, -half_width, -half_width, 1.0, 1.0),
},
&.{
0, 1, 2, 3, 2, 1, // Right
@ -175,26 +176,26 @@ pub fn createPyramidPrimitive(width: f32) PrimitiveData {
// zig fmt: off
&.{
// Right
vert( 0.0, half_width, 0.0),
vert( half_width, -half_width, -half_width),
vert( half_width, -half_width, half_width),
vert( 0.0, half_width, 0.0, 0.5, 0.0),
vert( half_width, -half_width, -half_width, 0.0, 1.0),
vert( half_width, -half_width, half_width, 1.0, 1.0),
// Left
vert( 0.0, half_width, 0.0),
vert(-half_width, -half_width, half_width),
vert(-half_width, -half_width, -half_width),
vert( 0.0, half_width, 0.0, 0.5, 0.0),
vert(-half_width, -half_width, half_width, 0.0, 1.0),
vert(-half_width, -half_width, -half_width, 1.0, 1.0),
// Front
vert( 0.0, half_width, 0.0),
vert( half_width, -half_width, half_width),
vert(-half_width, -half_width, half_width),
vert( 0.0, half_width, 0.0, 0.5, 0.0),
vert( half_width, -half_width, half_width, 0.0, 1.0),
vert(-half_width, -half_width, half_width, 1.0, 1.0),
// Back
vert( 0.0, half_width, 0.0),
vert(-half_width, -half_width, -half_width),
vert( half_width, -half_width, -half_width),
vert( 0.0, half_width, 0.0, 0.5, 0.0),
vert(-half_width, -half_width, -half_width, 0.0, 1.0),
vert( half_width, -half_width, -half_width, 1.0, 1.0),
// Bottom
vert(-half_width, -half_width, -half_width),
vert(-half_width, -half_width, half_width),
vert( half_width, -half_width, -half_width),
vert( half_width, -half_width, half_width),
vert(-half_width, -half_width, -half_width, 0.0, 0.0),
vert(-half_width, -half_width, half_width, 0.0, 1.0),
vert( half_width, -half_width, -half_width, 1.0, 0.0),
vert( half_width, -half_width, half_width, 1.0, 1.0),
},
&.{
0, 1, 2, // Right

@ -1,5 +1,9 @@
const std = @import("std");
const zigimg = @import("zigimg");
const Rgb24 = zigimg.color.Rgb24;
const Rgba32 = zigimg.color.Rgba32;
const core = @import("mach-core");
const gpu = core.gpu;
@ -40,6 +44,11 @@ pub fn init(app: *App) !*Renderer {
// A string buffer used to format objects' labels.
var label_buffer: [256]u8 = undefined;
// Embed `default.png` texture in the executable and upload it to the GPU.
const texture_bytes = @embedFile("./gfx/default.png");
const texture_view = try loadTexture(app.allocator, texture_bytes);
defer texture_view.release();
const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
defer shader_module.release();
@ -56,8 +65,10 @@ pub fn init(app: *App) !*Renderer {
var model_bind_group_layout = core.device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
.label = "Model Bind Group Layout",
.entries = &.{
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .uniform, false, 0),
gpu.BindGroupLayout.Entry.sampler(0, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(1, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .uniform, false, 0),
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .uniform, false, 0),
},
}));
defer model_bind_group_layout.release();
@ -82,6 +93,7 @@ pub fn init(app: *App) !*Renderer {
.step_mode = .vertex,
.attributes = &.{
.{ .format = .float32x3, .shader_location = 0, .offset = @offsetOf(VertexData, "position") },
.{ .format = .float32x2, .shader_location = 1, .offset = @offsetOf(VertexData, "uv") },
},
}),
},
@ -116,6 +128,11 @@ pub fn init(app: *App) !*Renderer {
},
}));
// Create a sampler that tells the GPU how to sample pixels from a texture.
// Includes filtering (nearest-neighbor, linear, ..) and wrapping behavior.
const texture_sampler = core.device.createSampler(&.{});
defer texture_sampler.release();
// Set up the primitives we want to render.
//
// Using `dupe` to allocate a slice here allows easily adjusting the
@ -171,8 +188,10 @@ pub fn init(app: *App) !*Renderer {
.label = model_bind_group_label,
.layout = model_bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, model_matrix_buffer, 0, @sizeOf(zm.Mat)),
gpu.BindGroup.Entry.buffer(1, model_color_buffer, 0, @sizeOf([3]f32)),
gpu.BindGroup.Entry.sampler(0, texture_sampler),
gpu.BindGroup.Entry.textureView(1, texture_view),
gpu.BindGroup.Entry.buffer(2, model_matrix_buffer, 0, @sizeOf(zm.Mat)),
gpu.BindGroup.Entry.buffer(3, model_color_buffer, 0, @sizeOf([3]f32)),
},
}));
@ -318,6 +337,53 @@ pub fn update(self: *Renderer) void {
core.queue.submit(&.{command});
}
/// Loads a texture from the provided buffer and uploads it to the GPU.
pub fn loadTexture(allocator: std.mem.Allocator, buffer: []const u8) !*gpu.TextureView {
var img = try zigimg.Image.fromMemory(allocator, buffer);
defer img.deinit();
const img_size = gpu.Extent3D{
.width = @intCast(img.width),
.height = @intCast(img.height),
};
const texture = core.device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
defer texture.release();
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @intCast(img.width * 4),
.rows_per_image = @intCast(img.height),
};
switch (img.pixels) {
.rgba32 => |pixels| {
core.queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels);
},
.rgb24 => |pixels_rgb24| {
const pixels = try rgb24ToRgba32(allocator, pixels_rgb24);
defer allocator.free(pixels);
core.queue.writeTexture(&.{ .texture = texture }, &data_layout, &img_size, pixels);
},
else => std.debug.panic("Unsupported image color format {s}", .{@tagName(img.pixels)}),
}
return texture.createView(&.{});
}
/// Converts a raw 24-bit RGB pixel buffer to 32-bit RGBA.
fn rgb24ToRgba32(allocator: std.mem.Allocator, in: []Rgb24) ![]Rgba32 {
const out = try allocator.alloc(Rgba32, in.len);
for (in, out) |src, *dest|
dest.* = .{ .r = src.r, .g = src.g, .b = src.b, .a = 255 };
return out;
}
/// Creates a depth texture. This is used to ensure that when things are
/// rendered, an object behind another won't draw over one in front, simply
/// because it was rendered at a later point in time.

@ -1,15 +1,20 @@
@group(0) @binding(0) var<uniform> view_proj_matrix: mat4x4<f32>;
@group(1) @binding(0) var<uniform> model_matrix: mat4x4<f32>;
@group(1) @binding(1) var<uniform> model_color: vec3<f32>;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var model_texture: texture_2d<f32>;
@group(1) @binding(2) var<uniform> model_matrix: mat4x4<f32>;
@group(1) @binding(3) var<uniform> model_color: vec3<f32>;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) uv: vec2<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) frag_uv: vec2<f32>,
};
struct FragmentOutput {
@ -21,11 +26,13 @@ struct FragmentOutput {
let mvp = model_matrix * view_proj_matrix;
out.position = vec4<f32>(in.position, 1.0) * mvp;
out.color = vec4<f32>(model_color, 1.0);
out.frag_uv = in.uv;
return out;
}
@fragment fn frag_main(in: VertexOutput) -> FragmentOutput {
var out: FragmentOutput;
out.pixel_color = in.color;
let texture_color = textureSample(model_texture, texture_sampler, in.frag_uv);
out.pixel_color = texture_color * in.color;
return out;
}

Loading…
Cancel
Save