diff --git a/README.md b/README.md index 24be0b7..c4f4af1 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ wasm build. - [x] [glb](https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/CesiumMilkTruck/glTF-Binary) - [x] node - [x] texture - - [ ] animation -- [ ] glTF + - [x] animation +- [x] glTF - [ ] draco - [ ] basisu -- [ ] vrm-0.x +- [WIP] vrm-0.x - [ ] vrm-1.0 diff --git a/deps/sokol_samples/sample_framework/Deform.zig b/deps/sokol_samples/sample_framework/Deform.zig new file mode 100644 index 0000000..a8c8ea3 --- /dev/null +++ b/deps/sokol_samples/sample_framework/Deform.zig @@ -0,0 +1,11 @@ +const sokol = @import("sokol"); +const sg = sokol.gfx; + +pub const Morph = struct {}; +pub const Skin = struct {}; + +pub const Deform = @This(); + +bind: sg.Bindings = sg.Bindings{}, +morph: ?Morph = null, +skin: ?Skin = null, diff --git a/deps/sokol_samples/sample_framework/Image.zig b/deps/sokol_samples/sample_framework/Image.zig new file mode 100644 index 0000000..771d940 --- /dev/null +++ b/deps/sokol_samples/sample_framework/Image.zig @@ -0,0 +1,50 @@ +const stb = @import("stb/stb.zig"); + +pub const Image = @This(); +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; +} diff --git a/deps/sokol_samples/sample_framework/Mesh.zig b/deps/sokol_samples/sample_framework/Mesh.zig index a5d3800..4b1e9a0 100644 --- a/deps/sokol_samples/sample_framework/Mesh.zig +++ b/deps/sokol_samples/sample_framework/Mesh.zig @@ -1,7 +1,7 @@ const sokol = @import("sokol"); const sg = sokol.gfx; const shader = @import("gltf.glsl.zig"); -const stb = @import("stb/stb.zig"); +const Texture = @import("Texture.zig"); pub const Vertex = struct { position: [3]f32, @@ -9,94 +9,6 @@ pub const Vertex = struct { 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, _sampler: ?sg.SamplerDesc) @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], - if (_sampler) |sampler| - sampler - else - sg.SamplerDesc{ - .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, diff --git a/deps/sokol_samples/sample_framework/Scene.zig b/deps/sokol_samples/sample_framework/Scene.zig index 33112d4..ae03f73 100644 --- a/deps/sokol_samples/sample_framework/Scene.zig +++ b/deps/sokol_samples/sample_framework/Scene.zig @@ -11,6 +11,9 @@ const Vec3 = rowmath.Vec3; const Quat = rowmath.Quat; const Mesh = @import("Mesh.zig"); +const Texture = @import("Texture.zig"); +const Image = @import("Image.zig"); +const Deform = @import("Deform.zig"); const Animation = @import("Animation.zig"); pub const Scene = @This(); @@ -22,10 +25,11 @@ allocator: std.mem.Allocator = undefined, meshes: []Mesh = &.{}, pip: sg.Pipeline = undefined, gltf: ?std.json.Parsed(zigltf.Gltf) = null, -white_texture: Mesh.Texture = undefined, +white_texture: Texture = undefined, animations: []Animation = &.{}, current_animation: ?usize = null, node_matrices: []Mat4 = &.{}, +node_deforms: []Deform = &.{}, pub fn init(self: *@This(), allocator: std.mem.Allocator) void { self.allocator = allocator; @@ -49,7 +53,7 @@ pub fn init(self: *@This(), allocator: std.mem.Allocator) void { 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, null); + self.white_texture = Texture.init(Image.white, null); } pub fn deinit(self: *@This()) void { @@ -199,9 +203,9 @@ pub fn load( if (texture.source) |source| { const image_bytes = try gltf_buffer.getImageBytes(source); const sampler = if (texture.sampler) |sampler_index| gltf.samplers[sampler_index] else null; - if (Mesh.Image.init(image_bytes)) |image| { + if (Image.init(image_bytes)) |image| { defer image.deinit(); - color_texture = Mesh.Texture.init( + color_texture = Texture.init( image, to_sokol_sampler(sampler), ); @@ -305,9 +309,17 @@ pub fn load( self.current_animation = 0; } + var deforms = std.ArrayList(Deform).init(self.allocator); + defer deforms.deinit(); var node_matrices = std.ArrayList(Mat4).init(self.allocator); defer node_matrices.deinit(); - try node_matrices.resize(gltf.nodes.len); + for (gltf.nodes, 0..) |node, i| { + _ = node; + _ = i; + try deforms.append(.{}); + try node_matrices.append(Mat4.identity); + } + self.node_deforms = try deforms.toOwnedSlice(); self.node_matrices = try node_matrices.toOwnedSlice(); _ = self.update(0); } @@ -462,7 +474,7 @@ pub fn draw(self: *@This(), camera: Camera) void { sg.applyPipeline(self.pip); const vp = camera.viewProjectionMatrix(); for (0..json.value.nodes.len) |i| { - self.draw_node(json.value, vp, i); + self.draw_node(json.value, vp, @intCast(i)); } } } @@ -471,17 +483,23 @@ fn draw_node( self: *@This(), gltf: zigltf.Gltf, vp: Mat4, - node_index: usize, + node_index: u32, ) void { const node = gltf.nodes[node_index]; const model_matrix = self.node_matrices[node_index]; if (node.mesh) |mesh_index| { - self.draw_mesh(mesh_index, vp, model_matrix); + self.draw_mesh(node_index, mesh_index, vp, model_matrix); } } -fn draw_mesh(self: *@This(), mesh_index: u32, vp: Mat4, model: Mat4) void { +fn draw_mesh( + self: *@This(), + node_index: u32, + mesh_index: u32, + vp: Mat4, + model: Mat4, +) void { const vs_params = shader.VsParams{ // rowmath では vec * mat の乗算順なので view_projection // glsl では mat * vec の乗算順なので projection_view @@ -500,18 +518,23 @@ fn draw_mesh(self: *@This(), mesh_index: u32, vp: Mat4, model: Mat4) void { }; sg.applyUniforms(.FS, shader.SLOT_fs_params, sg.asRange(&fs_params)); - var mesh = &self.meshes[mesh_index]; - + var base_mesh = &self.meshes[mesh_index]; + var deform = &self.node_deforms[node_index]; var offset: u32 = 0; - for (mesh.submeshes) |*submesh| { + for (base_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); + var bind = if (deform.morph == null and deform.skin == null) + &base_mesh.bind + else + &deform.bind; + + bind.fs = submesh.color_texture.fs; + sg.applyBindings(bind.*); sg.draw(offset, submesh.draw_count, 1); diff --git a/deps/sokol_samples/sample_framework/Texture.zig b/deps/sokol_samples/sample_framework/Texture.zig new file mode 100644 index 0000000..b889c30 --- /dev/null +++ b/deps/sokol_samples/sample_framework/Texture.zig @@ -0,0 +1,42 @@ +const sokol = @import("sokol"); +const sg = sokol.gfx; +const shader = @import("gltf.glsl.zig"); +const Image = @import("Image.zig"); + +pub const Texture = @This(); +fs: sg.StageBindings = sg.StageBindings{}, + +pub fn init(image: Image, _sampler: ?sg.SamplerDesc) @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], + if (_sampler) |sampler| + sampler + else + sg.SamplerDesc{ + .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; +}