From abb658ad331634b7e66b5ff818d81ff57cb1bff5 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 23 Sep 2024 23:31:43 +0900 Subject: [PATCH] texture --- deps/sokol_samples/build.zig | 7 ++ deps/sokol_samples/build.zig.zon | 4 + deps/sokol_samples/sample_framework/Mesh.zig | 86 +++++++++++++++++++ deps/sokol_samples/sample_framework/Scene.zig | 42 ++++++++- deps/sokol_samples/sample_framework/gltf.glsl | 11 ++- deps/sokol_samples/sample_framework/main.zig | 1 + .../sample_framework/stb/image.zig | 6 ++ deps/sokol_samples/sample_framework/stb/stb.c | 2 + .../sample_framework/stb/stb.zig | 1 + deps/zigltf/src/GltfBuffer.zig | 15 +++- deps/zigltf/src/types/Mesh.zig | 14 ++- 11 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 deps/sokol_samples/sample_framework/stb/image.zig create mode 100644 deps/sokol_samples/sample_framework/stb/stb.c create mode 100644 deps/sokol_samples/sample_framework/stb/stb.zig diff --git a/deps/sokol_samples/build.zig b/deps/sokol_samples/build.zig index a7dc8d5..516653b 100644 --- a/deps/sokol_samples/build.zig +++ b/deps/sokol_samples/build.zig @@ -62,6 +62,13 @@ pub fn build(b: *std.Build) void { sample_framework.root_module.addImport("sokol", sokol_dep.module("sokol")); sample_framework.root_module.addImport("zigltf", zigltf_dep.module("zigltf")); sample_framework.root_module.addImport("rowmath", rowmath_dep.module("rowmath")); + if (!target.result.isWasm()) { + const stb_dep = b.dependency("stb", .{}); + sample_framework.addIncludePath(stb_dep.path("")); + sample_framework.addCSourceFile(.{ + .file = b.path("sample_framework/stb/stb.c"), + }); + } // create a build step which invokes the Emscripten linker var emsdk_dep_: ?*std.Build.Dependency = null; diff --git a/deps/sokol_samples/build.zig.zon b/deps/sokol_samples/build.zig.zon index ba97dbd..e23978f 100644 --- a/deps/sokol_samples/build.zig.zon +++ b/deps/sokol_samples/build.zig.zon @@ -29,6 +29,10 @@ .url = "git+https://github.com/ousttrue/gltf-test-models.git#55328b2d063141993373ab8c770c72d7cfda260f", .hash = "1220aa78e130eb4c584dc21b408dcd26e8cfd30043e0f673ba6d229a0bfff592c54e", }, + .stb = .{ + .url = "git+https://github.com/nothings/stb.git#f75e8d1cad7d90d72ef7a4661f1b994ef78b4e31", + .hash = "1220c4fe5a4c4ebec402f5cdef08bc264b56fb07f259107d2b01ba8d416d88624b50", + }, }, .paths = .{ "", diff --git a/deps/sokol_samples/sample_framework/Mesh.zig b/deps/sokol_samples/sample_framework/Mesh.zig index 6553845..0ae9a5d 100644 --- a/deps/sokol_samples/sample_framework/Mesh.zig +++ b/deps/sokol_samples/sample_framework/Mesh.zig @@ -1,15 +1,100 @@ const sokol = @import("sokol"); const sg = sokol.gfx; const shader = @import("gltf.glsl.zig"); +const stb = @import("stb/stb.zig"); pub const Vertex = struct { position: [3]f32, normal: [3]f32, + uv: [2]f32, }; +pub const Image = struct { + width: u32, + height: u32, + channels: u32, + pixels: []const u8, + + pub const white = Image{ + .width = 2, + .height = 2, + .channels = 4, + .pixels = &.{ + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + }, + }; + + pub fn init(data: []const u8) ?@This() { + var img_width: c_int = undefined; + var img_height: c_int = undefined; + var num_channels: c_int = undefined; + const desired_channels = 4; + const pixels = stb.image.stbi_load_from_memory( + @ptrCast(&data[0]), + @intCast(data.len), + &img_width, + &img_height, + &num_channels, + desired_channels, + ) orelse { + return null; + }; + return .{ + .width = @intCast(img_width), + .height = @intCast(img_height), + .channels = @intCast(num_channels), + .pixels = pixels[0..@intCast(img_width * img_height * num_channels)], + }; + } + + pub fn deinit(self: @This()) void { + stb.image.stbi_image_free(&self.pixels[0]); + } + + pub fn byteLength(self: @This()) u32 { + return self.width * self.height * 4; + } +}; + +pub const Texture = struct { + fs: sg.StageBindings = sg.StageBindings{}, + + pub fn init(image: Image) @This() { + // init sokol + var texture = Texture{}; + texture.fs.images[shader.SLOT_colorTexture2D] = sg.allocImage(); + texture.fs.samplers[shader.SLOT_colorTextureSmp] = sg.allocSampler(); + sg.initSampler(texture.fs.samplers[shader.SLOT_colorTextureSmp], .{ + .wrap_u = .REPEAT, + .wrap_v = .REPEAT, + .min_filter = .LINEAR, + .mag_filter = .LINEAR, + .compare = .NEVER, + }); + + // initialize the sokol-gfx texture + var img_desc = sg.ImageDesc{ + .width = @intCast(image.width), + .height = @intCast(image.height), + // set pixel_format to RGBA8 for WebGL + .pixel_format = .RGBA8, + }; + img_desc.data.subimage[0][0] = .{ + .ptr = &image.pixels[0], + .size = image.byteLength(), + }; + sg.initImage(texture.fs.images[shader.SLOT_colorTexture2D], img_desc); + + return texture; + } +}; pub const Submesh = struct { submesh_params: shader.SubmeshParams, draw_count: u32, + color_texture: Texture, }; pub const Mesh = @This(); @@ -37,6 +122,7 @@ pub fn init( .label = "gltf-indices", }); } + return mesh; } diff --git a/deps/sokol_samples/sample_framework/Scene.zig b/deps/sokol_samples/sample_framework/Scene.zig index 597e4ff..0f02b76 100644 --- a/deps/sokol_samples/sample_framework/Scene.zig +++ b/deps/sokol_samples/sample_framework/Scene.zig @@ -17,6 +17,7 @@ allocator: std.mem.Allocator = undefined, meshes: []Mesh = &.{}, pip: sg.Pipeline = undefined, gltf: ?std.json.Parsed(zigltf.Gltf) = null, +white_texture: Mesh.Texture = undefined, pub fn init(self: *@This(), allocator: std.mem.Allocator) void { self.allocator = allocator; @@ -37,7 +38,10 @@ pub fn init(self: *@This(), allocator: std.mem.Allocator) void { // pip_desc.layout.buffers[0].stride = 28; pip_desc.layout.attrs[shader.ATTR_vs_aPos].format = .FLOAT3; pip_desc.layout.attrs[shader.ATTR_vs_aNormal].format = .FLOAT3; + pip_desc.layout.attrs[shader.ATTR_vs_aTexCoord].format = .FLOAT2; self.pip = sg.makePipeline(pip_desc); + + self.white_texture = Mesh.Texture.init(Mesh.Image.white); } pub fn deinit(self: *@This()) void { @@ -106,6 +110,16 @@ pub fn load( mesh_vertices.items[vertex_count + i].normal = normal; } } + if (primitive.attributes.TEXCOORD_0) |tex0_accessor_index| { + // uv + const tex0s = try gltf_buffer.getAccessorBytes( + [2]f32, + tex0_accessor_index, + ); + for (tex0s, 0..) |tex0, i| { + mesh_vertices.items[vertex_count + i].uv = tex0; + } + } if (primitive.indices) |indices_accessor_index| { // indies @@ -129,11 +143,27 @@ pub fn load( color = base_color; } } + + var color_texture = self.white_texture; + if (material.pbrMetallicRoughness) |pbr| { + if (pbr.baseColorTexture) |base| { + const texture = gltf.textures[base.index]; + if (texture.source) |source| { + const image_bytes = try gltf_buffer.getImageBytes(source); + if (Mesh.Image.init(image_bytes)) |image| { + defer image.deinit(); + color_texture = Mesh.Texture.init(image); + } + } + } + } + try submeshes.append(.{ .draw_count = index_accessor.count, .submesh_params = .{ .material_rgba = color, }, + .color_texture = color_texture, }); } else { @panic("no material"); @@ -218,7 +248,7 @@ fn draw_node( } } -fn draw_mesh(self: *const @This(), mesh_index: u32, vp: Mat4, model: Mat4) void { +fn draw_mesh(self: *@This(), mesh_index: u32, vp: Mat4, model: Mat4) void { const vs_params = shader.VsParams{ // rowmath では vec * mat の乗算順なので view_projection // glsl では mat * vec の乗算順なので projection_view @@ -235,17 +265,21 @@ fn draw_mesh(self: *const @This(), mesh_index: u32, vp: Mat4, model: Mat4) void }; sg.applyUniforms(.FS, shader.SLOT_fs_params, sg.asRange(&fs_params)); - const mesh = &self.meshes[mesh_index]; - sg.applyBindings(mesh.bind); + var mesh = &self.meshes[mesh_index]; var offset: u32 = 0; - for (mesh.submeshes) |submesh| { + for (mesh.submeshes) |*submesh| { sg.applyUniforms( .FS, shader.SLOT_submesh_params, sg.asRange(&submesh.submesh_params), ); + + mesh.bind.fs = submesh.color_texture.fs; + sg.applyBindings(mesh.bind); + sg.draw(offset, submesh.draw_count, 1); + offset += submesh.draw_count; } } diff --git a/deps/sokol_samples/sample_framework/gltf.glsl b/deps/sokol_samples/sample_framework/gltf.glsl index 1b99ef8..3a32d24 100644 --- a/deps/sokol_samples/sample_framework/gltf.glsl +++ b/deps/sokol_samples/sample_framework/gltf.glsl @@ -6,14 +6,17 @@ uniform vs_params { in vec3 aPos; in vec3 aNormal; +in vec2 aTexCoord; out vec3 FragPos; out vec3 Normal; +out vec2 TexCoord; void main() { gl_Position = projection_view * model * vec4(aPos, 1.0); FragPos = vec3(model * vec4(aPos, 1.0)); Normal = aNormal; + TexCoord = aTexCoord; } @end @@ -25,16 +28,22 @@ uniform submesh_params { vec4 material_rgba; }; +uniform texture2D colorTexture2D; +uniform sampler colorTextureSmp; +#define colorTexture sampler2D(colorTexture2D, colorTextureSmp) + in vec3 FragPos; in vec3 Normal; +in vec2 TexCoord; out vec4 frag_color; void main() { vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); + vec4 texel = texture(colorTexture, TexCoord); // vec3 diffuse = diff * lightColor; - frag_color = vec4( + frag_color = texel * vec4( material_rgba.r * diff, material_rgba.g * diff, material_rgba.b * diff, diff --git a/deps/sokol_samples/sample_framework/main.zig b/deps/sokol_samples/sample_framework/main.zig index ee22370..8252064 100644 --- a/deps/sokol_samples/sample_framework/main.zig +++ b/deps/sokol_samples/sample_framework/main.zig @@ -1,2 +1,3 @@ pub usingnamespace @import("Scene.zig"); pub const gltf_fetcher = @import("gltf_fetcher.zig"); +pub const stb = @import("stb/stb.zig"); diff --git a/deps/sokol_samples/sample_framework/stb/image.zig b/deps/sokol_samples/sample_framework/stb/image.zig new file mode 100644 index 0000000..2384a8f --- /dev/null +++ b/deps/sokol_samples/sample_framework/stb/image.zig @@ -0,0 +1,6 @@ +// flip the image vertically, so the first pixel in the output array is the bottom left +pub extern "c" fn stbi_set_flip_vertically_on_load(flag_true_if_should_flip: c_int) void; + +pub extern "c" fn stbi_load_from_memory(buffer: [*c]const u8, len: c_int, x: *c_int, y: *c_int, comp: *c_int, req_comp: c_int) [*c]const u8; + +pub extern "c" fn stbi_image_free(retval_from_stbi_load: *const anyopaque) void; diff --git a/deps/sokol_samples/sample_framework/stb/stb.c b/deps/sokol_samples/sample_framework/stb/stb.c new file mode 100644 index 0000000..8ddfd1f --- /dev/null +++ b/deps/sokol_samples/sample_framework/stb/stb.c @@ -0,0 +1,2 @@ +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" diff --git a/deps/sokol_samples/sample_framework/stb/stb.zig b/deps/sokol_samples/sample_framework/stb/stb.zig new file mode 100644 index 0000000..3f42493 --- /dev/null +++ b/deps/sokol_samples/sample_framework/stb/stb.zig @@ -0,0 +1 @@ +pub const image = @import("image.zig"); diff --git a/deps/zigltf/src/GltfBuffer.zig b/deps/zigltf/src/GltfBuffer.zig index 2588a69..62b00d9 100644 --- a/deps/zigltf/src/GltfBuffer.zig +++ b/deps/zigltf/src/GltfBuffer.zig @@ -22,12 +22,23 @@ pub fn init( }; } -pub fn deinit(self:*@This())void -{ +pub fn deinit(self: *@This()) void { // TODO: self.uriMap.deinit(); } +pub fn getImageBytes(self: *@This(), image_index: u32) ![]const u8 { + const image = self.gltf.images[image_index]; + if (image.bufferView) |bufferView_index| { + return try self.getBufferViewBytes(bufferView_index); + } else if (image.uri) |uri| { + _ = uri; + @panic("image.uri not implemented"); + } else { + unreachable; + } +} + pub fn getAccessorBytes(self: *@This(), T: type, accessor_index: u32) ![]const T { const accessor = self.gltf.accessors[accessor_index]; std.debug.assert(@sizeOf(T) == accessor.stride()); diff --git a/deps/zigltf/src/types/Mesh.zig b/deps/zigltf/src/types/Mesh.zig index 9ab066c..6ec067c 100644 --- a/deps/zigltf/src/types/Mesh.zig +++ b/deps/zigltf/src/types/Mesh.zig @@ -5,6 +5,11 @@ pub const Mesh = @This(); const Attributes = struct { POSITION: u32, NORMAL: ?u32 = null, + TANGENT: ?u32 = null, + TEXCOORD_0: ?u32 = null, + COLOR_0: ?u32 = null, + JOINTS_0: ?u32 = null, + WEIGHTS_0: ?u32 = null, pub fn format( self: @This(), @@ -17,9 +22,12 @@ const Attributes = struct { // if (self.POSITION) |POSITION| { try writer.print("[POS=>#{}]", .{self.POSITION}); // } - if (self.NORMAL) |NORMAL| { - try writer.print("[NOM=>#{}]", .{NORMAL}); - } + if (self.NORMAL) |NORMAL| try writer.print("[NOM=>#{}]", .{NORMAL}); + if (self.TANGENT) |TANGENT| try writer.print("[NOM=>#{}]", .{TANGENT}); + if (self.TEXCOORD_0) |TEXCOORD_0| try writer.print("[NOM=>#{}]", .{TEXCOORD_0}); + if (self.COLOR_0) |COLOR_0| try writer.print("[NOM=>#{}]", .{COLOR_0}); + if (self.JOINTS_0) |JOINTS_0| try writer.print("[NOM=>#{}]", .{JOINTS_0}); + if (self.WEIGHTS_0) |WEIGHTS_0| try writer.print("[NOM=>#{}]", .{WEIGHTS_0}); } };