Texture pipeline, pixel-art shader
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
@@ -141,10 +141,22 @@ vec3 lightOutgoingRadiance(
|
|||||||
return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL;
|
return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec4 texture2DArrayAA(texture2DArray tex, vec2 texCoord) {
|
||||||
|
vec2 size = vec2(textureSize(sampler2DArray(tex, _Sampler), 0).xy);
|
||||||
|
vec2 texCoordPX = texCoord * size;
|
||||||
|
vec2 seam = floor(texCoordPX + vec2(0.5));
|
||||||
|
|
||||||
|
texCoordPX = (texCoordPX - seam) / fwidth(texCoordPX) + seam;
|
||||||
|
texCoordPX = clamp(texCoordPX, seam - 0.5, seam + 0.5);
|
||||||
|
|
||||||
|
vec3 texCoord3 = vec3(texCoordPX / size, float(_TextureIndex));
|
||||||
|
return texture(sampler2DArray(tex, _Sampler), texCoord3);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 baseColorTexel = texture(sampler2DArray(_BaseColorTexture, _Sampler), vec3(var_texCoord, float(_TextureIndex)));
|
vec4 baseColorTexel = texture2DArrayAA(_BaseColorTexture, var_texCoord);
|
||||||
vec4 occlusionRoughnessMetallicTexel = texture(sampler2DArray(_OcclusionRoughnessMetallicTexture, _Sampler), vec3(var_texCoord, float(_TextureIndex)));
|
vec4 occlusionRoughnessMetallicTexel = texture2DArrayAA(_OcclusionRoughnessMetallicTexture, var_texCoord);
|
||||||
vec4 normalTextureTexel = texture(sampler2DArray(_NormalTexture, _Sampler), vec3(var_texCoord, float(_TextureIndex)));
|
vec4 normalTextureTexel = texture2DArrayAA(_NormalTexture, var_texCoord);
|
||||||
|
|
||||||
vec3 baseColor = baseColorTexel.rgb;
|
vec3 baseColor = baseColorTexel.rgb;
|
||||||
float occlusion = occlusionRoughnessMetallicTexel.r;
|
float occlusion = occlusionRoughnessMetallicTexel.r;
|
||||||
|
|||||||
BIN
assets/textures/BaseColor.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/BaseColor.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/Normal.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/Normal.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/textures/OcclusionRoughnessMetallic.png
(Stored with Git LFS)
Normal file
BIN
assets/textures/OcclusionRoughnessMetallic.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -27,6 +27,8 @@ pub fn build(b: *std.Build) !void {
|
|||||||
});
|
});
|
||||||
sokol_dep.artifact("sokol_clib").addIncludePath(cimgui_dep.path(cimgui_conf.include_dir));
|
sokol_dep.artifact("sokol_clib").addIncludePath(cimgui_dep.path(cimgui_conf.include_dir));
|
||||||
|
|
||||||
|
const zstbi_dep = b.dependency("zstbi", .{});
|
||||||
|
|
||||||
const sokol_mod = sokol_dep.module("sokol");
|
const sokol_mod = sokol_dep.module("sokol");
|
||||||
const shdc_dep = sokol_dep.builder.dependency("shdc", .{});
|
const shdc_dep = sokol_dep.builder.dependency("shdc", .{});
|
||||||
|
|
||||||
@@ -41,9 +43,12 @@ pub fn build(b: *std.Build) !void {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const zstbi_mod = zstbi_dep.module("root");
|
||||||
|
|
||||||
exe_mod.addImport("cimgui", cimgui_dep.module(cimgui_conf.module_name));
|
exe_mod.addImport("cimgui", cimgui_dep.module(cimgui_conf.module_name));
|
||||||
exe_mod.addImport("shaders", shaders_mod);
|
exe_mod.addImport("shaders", shaders_mod);
|
||||||
exe_mod.addImport("sokol", sokol_mod);
|
exe_mod.addImport("sokol", sokol_mod);
|
||||||
|
exe_mod.addImport("zstbi", zstbi_mod);
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "voxel-game",
|
.name = "voxel-game",
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
.url = "git+https://github.com/floooh/dcimgui.git#d5fb4e3d27b79062dc5981db3631dadc4f204654",
|
.url = "git+https://github.com/floooh/dcimgui.git#d5fb4e3d27b79062dc5981db3631dadc4f204654",
|
||||||
.hash = "cimgui-0.1.0-44Clkd6YlAAYULKHDwsDX9EPmka-VAVEjUl-o6ve307E",
|
.hash = "cimgui-0.1.0-44Clkd6YlAAYULKHDwsDX9EPmka-VAVEjUl-o6ve307E",
|
||||||
},
|
},
|
||||||
|
.zstbi = .{
|
||||||
|
.url = "git+https://github.com/zig-gamedev/zstbi#2c4b3100ccb7aed90ecc9439030899764e2a8d47",
|
||||||
|
.hash = "zstbi-0.11.0-dev-L0Ea_yaWBwAHwFoCuyjkFyaiSsbjt4UOrkntR0c_nmzz",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|||||||
163
src/asset_pipeline.zig
Normal file
163
src/asset_pipeline.zig
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const main = @import("main.zig");
|
||||||
|
const zstbi = @import("zstbi");
|
||||||
|
|
||||||
|
pub const Texture = struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
depth: u32,
|
||||||
|
data: []u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Texture, allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.data);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetMap = std.hash_map.StringHashMapUnmanaged;
|
||||||
|
|
||||||
|
pub fn visitTextures(allocator: std.mem.Allocator) std.hash_map.StringHashMapUnmanaged(Texture) {
|
||||||
|
zstbi.init(allocator);
|
||||||
|
defer zstbi.deinit();
|
||||||
|
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
const sub_path = "assets/textures";
|
||||||
|
|
||||||
|
var ret: AssetMap(Texture) = .empty;
|
||||||
|
|
||||||
|
var textures_dir = cwd.openDir(sub_path, .{ .iterate = true }) catch |err| {
|
||||||
|
std.log.err("Could not open \"{s}\" directory: {s}", .{ sub_path, @errorName(err) });
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
defer textures_dir.close();
|
||||||
|
|
||||||
|
var it = textures_dir.iterate();
|
||||||
|
while (it.next() catch |err| blk: {
|
||||||
|
std.log.err("Directory iteration interrupted due to an error: {s}", .{@errorName(err)});
|
||||||
|
break :blk null;
|
||||||
|
}) |entry| {
|
||||||
|
if (entry.kind != .file) {
|
||||||
|
std.log.warn("Skipping \"{s}\", which is not a file.", .{entry.name});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var texture = visitTexture(allocator, textures_dir, entry.name) catch {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const name = allocator.dupe(u8, std.fs.path.stem(entry.name)) catch {
|
||||||
|
std.log.err("Ran out of memory while trying to allocate asset name", .{});
|
||||||
|
texture.deinit(allocator);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
ret.putNoClobber(allocator, name, texture) catch {
|
||||||
|
std.log.err("Ran out of memory while trying to save asset to map", .{});
|
||||||
|
texture.deinit(allocator);
|
||||||
|
allocator.free(name);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visitTexture(allocator: std.mem.Allocator, dir: std.fs.Dir, filename: []const u8) !Texture {
|
||||||
|
std.log.info("Processing \"{s}\"...", .{filename});
|
||||||
|
|
||||||
|
const file = dir.openFile(filename, .{}) catch |err| {
|
||||||
|
std.log.err("Could not open \"{s}\" file: {s}", .{ filename, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
const file_buf = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
|
||||||
|
std.log.err("Could not read \"{s}\" file contents due to an error: {s}", .{ filename, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
defer allocator.free(file_buf);
|
||||||
|
|
||||||
|
var img = zstbi.Image.loadFromMemory(file_buf, 4) catch |err| {
|
||||||
|
std.log.err("Error reading \"{s}\" as an image file: {s}", .{ filename, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
defer img.deinit();
|
||||||
|
|
||||||
|
std.log.debug("size: {}×{} | components: {}", .{ img.width, img.height, img.num_components });
|
||||||
|
|
||||||
|
const grid_w = 4;
|
||||||
|
const grid_h = 4;
|
||||||
|
const tile_count = grid_w * grid_h;
|
||||||
|
|
||||||
|
const tile_w = std.math.divExact(u32, img.width, grid_w) catch |err| {
|
||||||
|
std.log.err("Cannot divide image width ({}) by {}: {s}", .{ img.width, grid_w, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
const tile_h = std.math.divExact(u32, img.height, grid_h) catch |err| {
|
||||||
|
std.log.err("Cannot divide image height ({}) by {}: {s}", .{ img.height, grid_h, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.log.debug("tile size: {}×{}", .{ tile_w, tile_h });
|
||||||
|
|
||||||
|
const data = allocator.alloc(u8, 4 * tile_w * tile_h * tile_count) catch |err| {
|
||||||
|
std.log.err("Ran out of memory while trying to allocate output buffer", .{});
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
errdefer allocator.free(data);
|
||||||
|
|
||||||
|
rearrange(grid_w, grid_h, tile_w, tile_h, img.data, data);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.width = tile_w,
|
||||||
|
.height = tile_h,
|
||||||
|
.depth = tile_count,
|
||||||
|
.data = data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rearrange(grid_w: u32, grid_h: u32, tile_w: u32, tile_h: u32, inbuf: []const u8, outbuf: []u8) void {
|
||||||
|
std.log.debug("rearrange: {}×{} grid of {}×{} tiles", .{ grid_w, grid_h, tile_w, tile_h });
|
||||||
|
|
||||||
|
const row_size = 4 * tile_w;
|
||||||
|
const row_stride = row_size * grid_w;
|
||||||
|
const tile_stride = row_stride * tile_h;
|
||||||
|
|
||||||
|
std.debug.assert(inbuf.len == tile_stride * grid_h);
|
||||||
|
std.debug.assert(outbuf.len == tile_stride * grid_h);
|
||||||
|
|
||||||
|
var outptr: usize = 0;
|
||||||
|
|
||||||
|
var tile_y: u32 = 0;
|
||||||
|
while (tile_y < grid_h) : (tile_y += 1) {
|
||||||
|
const tile_byte_offset = tile_y * tile_stride;
|
||||||
|
|
||||||
|
var tile_x: u32 = 0;
|
||||||
|
while (tile_x < grid_w) : (tile_x += 1) {
|
||||||
|
const column_byte_offset = tile_x * row_size;
|
||||||
|
|
||||||
|
var row: u32 = 0;
|
||||||
|
while (row < tile_h) : (row += 1) {
|
||||||
|
const row_byte_offset = row * row_stride + tile_byte_offset;
|
||||||
|
const byte_offset = row_byte_offset + column_byte_offset;
|
||||||
|
|
||||||
|
@memcpy(
|
||||||
|
outbuf[outptr .. outptr + row_size],
|
||||||
|
inbuf[byte_offset .. byte_offset + row_size],
|
||||||
|
);
|
||||||
|
|
||||||
|
outptr += row_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinitTextures(allocator: std.mem.Allocator, map: AssetMap(Texture)) void {
|
||||||
|
var it = map.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
allocator.free(entry.key_ptr.*);
|
||||||
|
entry.value_ptr.deinit(allocator);
|
||||||
|
}
|
||||||
|
map.deinit(allocator);
|
||||||
|
}
|
||||||
33
src/game.zig
33
src/game.zig
@@ -8,6 +8,7 @@ const sapp = sokol.app;
|
|||||||
const sg = sokol.gfx;
|
const sg = sokol.gfx;
|
||||||
const sglue = sokol.glue;
|
const sglue = sokol.glue;
|
||||||
|
|
||||||
|
const ap = @import("asset_pipeline.zig");
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
const Samplers = @import("Samplers.zig");
|
const Samplers = @import("Samplers.zig");
|
||||||
|
|
||||||
@@ -28,7 +29,15 @@ var normal_texture: sg.Image = undefined;
|
|||||||
|
|
||||||
var bindings: sg.Bindings = undefined;
|
var bindings: sg.Bindings = undefined;
|
||||||
|
|
||||||
|
var textures: ap.AssetMap(ap.Texture) = undefined;
|
||||||
|
|
||||||
pub fn init() void {
|
pub fn init() void {
|
||||||
|
textures = ap.visitTextures(main.allocator);
|
||||||
|
|
||||||
|
const base_color = textures.getPtr("BaseColor").?;
|
||||||
|
const normal = textures.getPtr("Normal").?;
|
||||||
|
const occlusion_roughness_metallic = textures.getPtr("OcclusionRoughnessMetallic").?;
|
||||||
|
|
||||||
vertex_buffer = sg.makeBuffer(.{
|
vertex_buffer = sg.makeBuffer(.{
|
||||||
.data = sg.asRange(&[_]f32{
|
.data = sg.asRange(&[_]f32{
|
||||||
// positionOS texCoord normalOS tangentOS
|
// positionOS texCoord normalOS tangentOS
|
||||||
@@ -95,13 +104,13 @@ pub fn init() void {
|
|||||||
base_color_texture = sg.makeImage(.{
|
base_color_texture = sg.makeImage(.{
|
||||||
.type = .ARRAY,
|
.type = .ARRAY,
|
||||||
.usage = .{ .immutable = true },
|
.usage = .{ .immutable = true },
|
||||||
.width = 16,
|
.width = @intCast(base_color.width),
|
||||||
.height = 16,
|
.height = @intCast(base_color.height),
|
||||||
.num_slices = 16,
|
.num_slices = @intCast(base_color.depth),
|
||||||
.pixel_format = .RGBA8,
|
.pixel_format = .RGBA8,
|
||||||
.data = blk: {
|
.data = blk: {
|
||||||
var ret: sg.ImageData = .{};
|
var ret: sg.ImageData = .{};
|
||||||
ret.mip_levels[0] = sg.asRange(&[_]u8{ 255, 255, 255, 255 } ** (16 * 16 * 16));
|
ret.mip_levels[0] = sg.asRange(base_color.data);
|
||||||
break :blk ret;
|
break :blk ret;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -109,13 +118,13 @@ pub fn init() void {
|
|||||||
occlusion_roughness_metallic_texture = sg.makeImage(.{
|
occlusion_roughness_metallic_texture = sg.makeImage(.{
|
||||||
.type = .ARRAY,
|
.type = .ARRAY,
|
||||||
.usage = .{ .immutable = true },
|
.usage = .{ .immutable = true },
|
||||||
.width = 1,
|
.width = @intCast(occlusion_roughness_metallic.width),
|
||||||
.height = 1,
|
.height = @intCast(occlusion_roughness_metallic.height),
|
||||||
.num_slices = 16,
|
.num_slices = @intCast(occlusion_roughness_metallic.depth),
|
||||||
.pixel_format = .RGBA8,
|
.pixel_format = .RGBA8,
|
||||||
.data = blk: {
|
.data = blk: {
|
||||||
var ret: sg.ImageData = .{};
|
var ret: sg.ImageData = .{};
|
||||||
ret.mip_levels[0] = sg.asRange(&[_]u8{ 255, 255, 0, 255 } ** (1 * 1 * 16));
|
ret.mip_levels[0] = sg.asRange(occlusion_roughness_metallic.data);
|
||||||
break :blk ret;
|
break :blk ret;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -123,13 +132,13 @@ pub fn init() void {
|
|||||||
normal_texture = sg.makeImage(.{
|
normal_texture = sg.makeImage(.{
|
||||||
.type = .ARRAY,
|
.type = .ARRAY,
|
||||||
.usage = .{ .immutable = true },
|
.usage = .{ .immutable = true },
|
||||||
.width = 128,
|
.width = @intCast(normal.width),
|
||||||
.height = 128,
|
.height = @intCast(normal.height),
|
||||||
.num_slices = 16,
|
.num_slices = @intCast(normal.depth),
|
||||||
.pixel_format = .RGBA8,
|
.pixel_format = .RGBA8,
|
||||||
.data = blk: {
|
.data = blk: {
|
||||||
var ret: sg.ImageData = .{};
|
var ret: sg.ImageData = .{};
|
||||||
ret.mip_levels[0] = sg.asRange(&[_]u8{ 127, 127, 255, 255 } ** (128 * 128 * 16));
|
ret.mip_levels[0] = sg.asRange(normal.data);
|
||||||
break :blk ret;
|
break :blk ret;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user