Introduce pipeline, shader sketch
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.zig-cache
|
.zig-cache
|
||||||
zig-out
|
zig-out
|
||||||
|
/library
|
||||||
|
|||||||
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.
22
build.zig
22
build.zig
@@ -21,6 +21,28 @@ pub fn build(b: *std.Build) void {
|
|||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
const pipeline_debug = b.option(bool, "pipeline_debug", "Build asset pipeline in debug mode") orelse false;
|
||||||
|
|
||||||
|
const pipeline_mod = b.createModule(.{
|
||||||
|
.root_source_file = b.path("pipeline/main.zig"),
|
||||||
|
.target = b.graph.host,
|
||||||
|
.optimize = if (pipeline_debug) .Debug else .ReleaseSafe,
|
||||||
|
});
|
||||||
|
|
||||||
|
pipeline_mod.addImport("zstbi", zstbi.module("root"));
|
||||||
|
|
||||||
|
const pipeline = b.addExecutable(.{
|
||||||
|
.name = "pipeline",
|
||||||
|
.root_module = pipeline_mod,
|
||||||
|
});
|
||||||
|
|
||||||
|
pipeline.linkLibrary(zstbi.artifact("zstbi"));
|
||||||
|
|
||||||
|
const pipeline_cmd = b.addRunArtifact(pipeline);
|
||||||
|
pipeline_cmd.setCwd(b.path("."));
|
||||||
|
|
||||||
|
const pipeline_step = b.step("pipeline", "Run the asset pipeline");
|
||||||
|
pipeline_step.dependOn(&pipeline_cmd.step);
|
||||||
|
|
||||||
const exe_mod = b.createModule(.{
|
const exe_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
|||||||
124
pipeline/main.zig
Normal file
124
pipeline/main.zig
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const zstbi = @import("zstbi");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
zstbi.init(allocator);
|
||||||
|
defer zstbi.deinit();
|
||||||
|
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
|
||||||
|
var assets_dir = openDirOrExit(cwd, "assets", .{ .iterate = true });
|
||||||
|
defer assets_dir.close();
|
||||||
|
|
||||||
|
var library_dir = openDirOrExit(cwd, "library", .{});
|
||||||
|
defer library_dir.close();
|
||||||
|
|
||||||
|
visit(assets_dir, library_dir, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit(assets_dir: std.fs.Dir, library_dir: std.fs.Dir, allocator: std.mem.Allocator) void {
|
||||||
|
var it = assets_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 == .directory) {
|
||||||
|
var assets_subdir = assets_dir.openDir(entry.name, .{ .iterate = true }) catch |err| {
|
||||||
|
std.log.warn("Skipping directory \"{s}\" due to an error: {s}", .{ entry.name, @errorName(err) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer assets_subdir.close();
|
||||||
|
|
||||||
|
library_dir.makeDir(entry.name) catch |err| switch (err) {
|
||||||
|
error.PathAlreadyExists => {
|
||||||
|
// This is fine
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
std.log.warn("Skipping directory \"{s}\" due to an error while creating corresponding library directory: {s}", .{ entry.name, @errorName(err) });
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var library_subdir = library_dir.openDir(entry.name, .{}) catch |err| {
|
||||||
|
std.log.warn("Skipping directory \"{s}\" due to an error while opening corresponding library directory: {s}", .{ entry.name, @errorName(err) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer library_subdir.close();
|
||||||
|
|
||||||
|
visit(assets_subdir, library_subdir, allocator);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.kind != .file) {
|
||||||
|
std.log.warn("Skipping \"{s}\", which is not a file nor a directory.", .{entry.name});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std.log.info("Processing \"{s}\"...", .{entry.name});
|
||||||
|
|
||||||
|
const infile = assets_dir.openFile(entry.name, .{}) catch |err| {
|
||||||
|
std.log.err("Could not open \"{s}\" file: {s}", .{ entry.name, @errorName(err) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer infile.close();
|
||||||
|
|
||||||
|
const inbuf = infile.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
|
||||||
|
std.log.err("Could not read \"{s}\" file contents due to an error: {s}", .{ entry.name, @errorName(err) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer allocator.free(inbuf);
|
||||||
|
|
||||||
|
var img = zstbi.Image.loadFromMemory(inbuf, 4) catch |err| {
|
||||||
|
std.log.err("Error reading \"{s}\" as an image file: {s}", .{ entry.name, @errorName(err) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
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) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
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) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.log.debug("tile size: {}×{}", .{ tile_w, tile_h });
|
||||||
|
|
||||||
|
const outbuf = allocator.alloc(u8, 4 * tile_w * tile_h * tile_count) catch {
|
||||||
|
std.log.err("Ran out of memory while trying to allocate output buffer", .{});
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
defer allocator.free(outbuf);
|
||||||
|
|
||||||
|
@memcpy(outbuf, img.data);
|
||||||
|
|
||||||
|
const out_name = std.fs.path.stem(entry.name);
|
||||||
|
|
||||||
|
library_dir.writeFile(.{
|
||||||
|
.data = outbuf,
|
||||||
|
.sub_path = out_name,
|
||||||
|
}) catch |err| {
|
||||||
|
std.log.err("Couldn't write \"{s}\" file corresponding to \"{s}\" asset file: {s}", .{ out_name, entry.name, @errorName(err) });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openDirOrExit(dir: std.fs.Dir, sub_path: []const u8, args: std.fs.Dir.OpenOptions) std.fs.Dir {
|
||||||
|
return dir.openDir(sub_path, args) catch |err| {
|
||||||
|
std.log.err("Could not open \"{s}\" directory: {s}", .{ sub_path, @errorName(err) });
|
||||||
|
std.posix.exit(1);
|
||||||
|
};
|
||||||
|
}
|
||||||
201
shaders/block.wgsl
Normal file
201
shaders/block.wgsl
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
struct Vertex {
|
||||||
|
@location(0) positionOS: vec3<f32>,
|
||||||
|
@location(1) texCoord: vec2<f32>,
|
||||||
|
@location(2) normalOS: vec3<f32>,
|
||||||
|
@location(3) tangentOS: vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Varyings {
|
||||||
|
@builtin(position) positionCS: vec4<f32>,
|
||||||
|
@location(0) positionVS: vec3<f32>,
|
||||||
|
@location(1) texCoord: vec2<f32>,
|
||||||
|
@location(2) normalVS: vec3<f32>,
|
||||||
|
@location(3) tangentVS: vec3<f32>,
|
||||||
|
@location(4) bitangentVS: vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PointLight {
|
||||||
|
positionWS: vec3<f32>,
|
||||||
|
color: vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DirectionalLight {
|
||||||
|
directionWS: vec3<f32>,
|
||||||
|
color: vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlobalUniforms {
|
||||||
|
matrixWStoVS: mat4x4<f32>,
|
||||||
|
matrixVStoCS: mat4x4<f32>,
|
||||||
|
ambientLight: vec3<f32>,
|
||||||
|
pointLightCount: u32,
|
||||||
|
directionalLightCount: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectUniforms {
|
||||||
|
matrixOStoWS: mat4x4<f32>,
|
||||||
|
matrixOStoWSNormal: mat4x4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GROUP 0 --- GLOBAL ------------------------------------------------------
|
||||||
|
|
||||||
|
@group(0) @binding(0) var<uniform> _Global: GlobalUniforms;
|
||||||
|
|
||||||
|
@group(0) @binding(1) var<storage> _PointLights: array<PointLight>;
|
||||||
|
@group(0) @binding(2) var<storage> _DirectionalLights: array<DirectionalLight>;
|
||||||
|
|
||||||
|
@group(0) @binding(3) var _Sampler: sampler;
|
||||||
|
@group(0) @binding(4) var _BaseColorTexture: texture_2d_array<f32>;
|
||||||
|
@group(0) @binding(5) var _OcclusionRoughnessMetallicTexture: texture_2d_array<f32>;
|
||||||
|
@group(0) @binding(6) var _NormalTexture: texture_2d_array<f32>;
|
||||||
|
|
||||||
|
// --- GROUP 1 --- PER MATERIAL ------------------------------------------------
|
||||||
|
|
||||||
|
@group(1) @binding(0) var<uniform> _TextureIndex: u32;
|
||||||
|
|
||||||
|
// --- GROUP 2 --- PER OBJECT --------------------------------------------------
|
||||||
|
|
||||||
|
@group(2) @binding(0) var<uniform> _Object: ObjectUniforms;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const INV_PI: f32 = 0.31830987;
|
||||||
|
const IOR: f32 = 1.45;
|
||||||
|
const DIELECTRIC_F0: vec3<f32> = vec3(pow((IOR - 1.0) / (IOR + 1.0), 2.0));
|
||||||
|
const F90 = vec3(1.0);
|
||||||
|
|
||||||
|
fn fresnelSchlick(dotVH: f32, f0: vec3<f32>) -> vec3<f32> {
|
||||||
|
return mix(f0, F90, pow(1.0 - dotVH, 5.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visibilityGGX(dotNL: f32, dotNV: f32, alpha: f32) -> f32 {
|
||||||
|
let alphaSquared = alpha * alpha;
|
||||||
|
|
||||||
|
let vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared);
|
||||||
|
let lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared);
|
||||||
|
let GGX = vGGX + lGGX;
|
||||||
|
return select(0.0, 0.5 / GGX, GGX > 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn distributionGGX(dotNH: f32, alpha: f32) -> f32 {
|
||||||
|
let alphaSquared = alpha * alpha;
|
||||||
|
let tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0;
|
||||||
|
return alphaSquared * INV_PI / (tmp * tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toneMapAcesNarkowicz(color: vec3<f32>) -> vec3<f32> {
|
||||||
|
const A: f32 = 2.51;
|
||||||
|
const B: f32 = 0.03;
|
||||||
|
const C: f32 = 2.43;
|
||||||
|
const D: f32 = 0.59;
|
||||||
|
const E: f32 = 0.14;
|
||||||
|
return saturate((color * (A * color + B)) / (color * (C * color + D) + E));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lightOutgoingRadiance(
|
||||||
|
viewDirectionVS: vec3<f32>, normalVS: vec3<f32>, dotNV: f32,
|
||||||
|
baseColor: vec3<f32>, alpha: f32, metallic: f32, f0: vec3<f32>,
|
||||||
|
incomingRadiance: vec3<f32>, lightDirectionVS: vec3<f32>,
|
||||||
|
) -> vec3<f32> {
|
||||||
|
let halfVectorVS = normalize(lightDirectionVS + viewDirectionVS);
|
||||||
|
let dotVH = saturate(dot(viewDirectionVS, halfVectorVS));
|
||||||
|
let dotNH = saturate(dot(normalVS, halfVectorVS));
|
||||||
|
let dotNL = saturate(dot(normalVS, lightDirectionVS));
|
||||||
|
|
||||||
|
let fresnel = fresnelSchlick(dotVH, f0);
|
||||||
|
let visibility = visibilityGGX(dotNL, dotNV, alpha);
|
||||||
|
let distribution = distributionGGX(dotNH, alpha);
|
||||||
|
|
||||||
|
let scatteredFactor = (1.0 - fresnel) * (1.0 - metallic) * baseColor * INV_PI;
|
||||||
|
let reflectedFactor = fresnel * visibility * distribution;
|
||||||
|
|
||||||
|
return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vert(vertex: Vertex) -> Varyings {
|
||||||
|
let positionWS = (_Object.matrixOStoWS * vec4(vertex.positionOS, 1.0)).xyz;
|
||||||
|
let positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz;
|
||||||
|
let positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0);
|
||||||
|
|
||||||
|
let normalWS = normalize((_Object.matrixOStoWSNormal * vec4(vertex.normalOS, 0.0)).xyz);
|
||||||
|
let normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz);
|
||||||
|
|
||||||
|
let tangentWS = normalize((_Object.matrixOStoWS * vec4(vertex.tangentOS.xyz, 0.0)).xyz);
|
||||||
|
let tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz);
|
||||||
|
|
||||||
|
let bitangentVS = vertex.tangentOS.w * normalize(cross(normalVS, tangentVS));
|
||||||
|
|
||||||
|
var output: Varyings;
|
||||||
|
output.positionCS = positionCS;
|
||||||
|
output.positionVS = positionVS;
|
||||||
|
output.texCoord = vertex.texCoord;
|
||||||
|
output.normalVS = normalVS;
|
||||||
|
output.tangentVS = tangentVS;
|
||||||
|
output.bitangentVS = bitangentVS;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn frag(fragment: Varyings) -> @location(0) vec4<f32> {
|
||||||
|
let baseColorTexel = textureSample(_BaseColorTexture, _Sampler, fragment.texCoord, _TextureIndex);
|
||||||
|
let occlusionRoughnessMetallicTexel = textureSample(_OcclusionRoughnessMetallicTexture, _Sampler, fragment.texCoord, _TextureIndex);
|
||||||
|
let normalTextureTexel = textureSample(_NormalTexture, _Sampler, fragment.texCoord, _TextureIndex);
|
||||||
|
|
||||||
|
let baseColor = baseColorTexel.rgb;
|
||||||
|
let occlusion = occlusionRoughnessMetallicTexel.r;
|
||||||
|
let roughness = occlusionRoughnessMetallicTexel.g;
|
||||||
|
let metallic = occlusionRoughnessMetallicTexel.b;
|
||||||
|
|
||||||
|
let tangentVS = normalize(fragment.tangentVS);
|
||||||
|
let bitangentVS = normalize(fragment.bitangentVS);
|
||||||
|
let matrixTStoVS = mat3x3(tangentVS, bitangentVS, fragment.normalVS);
|
||||||
|
let normalTS = normalTextureTexel.xyz * 2.0 - 1.0;
|
||||||
|
let normalVS = normalize(matrixTStoVS * normalTS);
|
||||||
|
|
||||||
|
let positionVS = fragment.positionVS;
|
||||||
|
let viewDirectionVS = normalize(-positionVS);
|
||||||
|
let dotNV = saturate(dot(normalVS, viewDirectionVS));
|
||||||
|
let alpha = roughness * roughness;
|
||||||
|
|
||||||
|
let f0 = mix(DIELECTRIC_F0, baseColor, metallic);
|
||||||
|
|
||||||
|
var outgoingRadiance = vec3(0.0);
|
||||||
|
|
||||||
|
for (var i: u32 = 0; i < _Global.pointLightCount; i++) {
|
||||||
|
let light = _PointLights[i];
|
||||||
|
|
||||||
|
let lightPositionVS = (_Global.matrixWStoVS * vec4(light.positionWS, 1.0)).xyz;
|
||||||
|
let lightDirectionVS = normalize(lightPositionVS - positionVS);
|
||||||
|
let lightDistance = distance(positionVS, lightPositionVS);
|
||||||
|
let lightAttenuation = 1.0 / (lightDistance * lightDistance);
|
||||||
|
let incomingRadiance = light.color * lightAttenuation;
|
||||||
|
|
||||||
|
outgoingRadiance += lightOutgoingRadiance(
|
||||||
|
viewDirectionVS, normalVS, dotNV,
|
||||||
|
baseColor, alpha, metallic, f0,
|
||||||
|
incomingRadiance, lightDirectionVS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i: u32 = 0; i < _Global.directionalLightCount; i++) {
|
||||||
|
let light = _DirectionalLights[i];
|
||||||
|
|
||||||
|
let lightDirectionVS = normalize((_Global.matrixWStoVS * vec4(light.directionWS, 0.0)).xyz);
|
||||||
|
let incomingRadiance = light.color;
|
||||||
|
|
||||||
|
outgoingRadiance += lightOutgoingRadiance(
|
||||||
|
viewDirectionVS, normalVS, dotNV,
|
||||||
|
baseColor, alpha, metallic, f0,
|
||||||
|
incomingRadiance, lightDirectionVS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
outgoingRadiance += _Global.ambientLight * baseColor * occlusion;
|
||||||
|
|
||||||
|
let toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance);
|
||||||
|
let toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2));
|
||||||
|
|
||||||
|
return vec4(toneMappedSrgbColor, 1.0);
|
||||||
|
}
|
||||||
25
src/game.zig
25
src/game.zig
@@ -8,6 +8,31 @@ const main = @import("main.zig");
|
|||||||
var show_console: bool = false;
|
var show_console: bool = false;
|
||||||
var show_demo_window: bool = false;
|
var show_demo_window: bool = false;
|
||||||
|
|
||||||
|
const Vertex = extern struct {
|
||||||
|
position: [3]f32,
|
||||||
|
tex_coord: [2]f32,
|
||||||
|
normal: [3]f32,
|
||||||
|
tangent: [4]f32,
|
||||||
|
|
||||||
|
pub fn init(x: f32, y: f32, z: f32, u: f32, v: f32, nx: f32, ny: f32, nz: f32, tx: f32, ty: f32, tz: f32, tw: f32) Vertex {
|
||||||
|
return .{
|
||||||
|
.position = .{ x, y, z },
|
||||||
|
.tex_coord = .{ u, v },
|
||||||
|
.normal = .{ nx, ny, nz },
|
||||||
|
.tangent = .{ tx, ty, tw, tz },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertex_buffer = [_]Vertex{
|
||||||
|
Vertex.init(-0.5, -0.5, 0, 0, 1, 0, 0, 1, 1, 0, 0, -1),
|
||||||
|
Vertex.init(0.5, -0.5, 0, 1, 1, 0, 0, 1, 1, 0, 0, -1),
|
||||||
|
Vertex.init(-0.5, 0.5, 0, 0, 0, 0, 0, 1, 1, 0, 0, -1),
|
||||||
|
Vertex.init(0.5, 0.5, 0, 1, 0, 0, 0, 1, 1, 0, 0, -1),
|
||||||
|
};
|
||||||
|
|
||||||
|
const index_buffer = [_]u16{ 0, 1, 2, 2, 1, 3 };
|
||||||
|
|
||||||
pub fn init() void {}
|
pub fn init() void {}
|
||||||
|
|
||||||
pub fn update(dt: f32) void {
|
pub fn update(dt: f32) void {
|
||||||
|
|||||||
BIN
textures/OcclusionRoughnessMetallic.png
(Stored with Git LFS)
BIN
textures/OcclusionRoughnessMetallic.png
(Stored with Git LFS)
Binary file not shown.
Reference in New Issue
Block a user