Skip to content
Merged
20 changes: 16 additions & 4 deletions assets/config/presets.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"pbr_enabled": false,
"pbr_quality": 0,
"msaa_samples": 1,
"taa_enabled": false,
"taa_blend_factor": 0.85,
"taa_velocity_rejection": 0.03,
"anisotropic_filtering": 1,
"max_texture_resolution": 64,
"cloud_shadows_enabled": false,
Expand All @@ -32,7 +35,10 @@
"shadow_cascade_blend": false,
"pbr_enabled": true,
"pbr_quality": 1,
"msaa_samples": 2,
"msaa_samples": 1,
"taa_enabled": true,
"taa_blend_factor": 0.9,
"taa_velocity_rejection": 0.02,
"anisotropic_filtering": 4,
"max_texture_resolution": 128,
"cloud_shadows_enabled": true,
Expand All @@ -45,7 +51,7 @@
"ssao_enabled": true,
"lod_enabled": true,
"render_distance": 12,
"fxaa_enabled": true,
"fxaa_enabled": false,
"bloom_enabled": true,
"bloom_intensity": 0.5
},
Expand All @@ -57,7 +63,10 @@
"shadow_cascade_blend": true,
"pbr_enabled": true,
"pbr_quality": 2,
"msaa_samples": 4,
"msaa_samples": 1,
"taa_enabled": true,
"taa_blend_factor": 0.93,
"taa_velocity_rejection": 0.015,
"anisotropic_filtering": 8,
"max_texture_resolution": 256,
"cloud_shadows_enabled": true,
Expand All @@ -82,7 +91,10 @@
"shadow_cascade_blend": true,
"pbr_enabled": true,
"pbr_quality": 2,
"msaa_samples": 4,
"msaa_samples": 1,
"taa_enabled": true,
"taa_blend_factor": 0.95,
"taa_velocity_rejection": 0.01,
"anisotropic_filtering": 16,
"max_texture_resolution": 512,
"cloud_shadows_enabled": true,
Expand Down
50 changes: 50 additions & 0 deletions assets/shaders/vulkan/taa.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#version 450

layout(location = 0) in vec2 outUV;
layout(location = 0) out vec4 outColor;

layout(set = 0, binding = 0) uniform sampler2D uCurrentHdr;
layout(set = 0, binding = 1) uniform sampler2D uHistory;
layout(set = 0, binding = 2) uniform sampler2D uVelocity;

layout(push_constant) uniform TAAPush {
float blend_factor;
float velocity_rejection;
float reset_history;
float _pad;
} taa;

vec3 sampleCurrent(vec2 uv) {
return texture(uCurrentHdr, uv).rgb;
}

void main() {
vec2 velocity = texture(uVelocity, outUV).xy;
vec2 history_uv = outUV - velocity;

vec3 current = sampleCurrent(outUV);

if (taa.reset_history > 0.5 || history_uv.x < 0.0 || history_uv.y < 0.0 || history_uv.x > 1.0 || history_uv.y > 1.0) {
outColor = vec4(current, 1.0);
return;
}

vec3 history = texture(uHistory, history_uv).rgb;

vec2 texel = 1.0 / vec2(textureSize(uCurrentHdr, 0));
vec3 c1 = sampleCurrent(clamp(outUV + vec2(texel.x, 0.0), 0.0, 1.0));
vec3 c2 = sampleCurrent(clamp(outUV + vec2(-texel.x, 0.0), 0.0, 1.0));
vec3 c3 = sampleCurrent(clamp(outUV + vec2(0.0, texel.y), 0.0, 1.0));
vec3 c4 = sampleCurrent(clamp(outUV + vec2(0.0, -texel.y), 0.0, 1.0));

vec3 min_color = min(current, min(min(c1, c2), min(c3, c4)));
vec3 max_color = max(current, max(max(c1, c2), max(c3, c4)));
vec3 clamped_history = clamp(history, min_color, max_color);

float speed = length(velocity);
float stable = 1.0 - smoothstep(taa.velocity_rejection, taa.velocity_rejection * 4.0, speed);
float history_weight = taa.blend_factor * stable;
vec3 resolved = mix(current, clamped_history, history_weight);

outColor = vec4(resolved, 1.0);
}
8 changes: 8 additions & 0 deletions assets/shaders/vulkan/taa.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#version 450

layout(location = 0) out vec2 outUV;

void main() {
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
}
4 changes: 4 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ pub fn build(b: *std.Build) void {
const validate_vulkan_ssao_frag = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/ssao.frag" });
const validate_vulkan_ssao_blur_frag = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/ssao_blur.frag" });
const validate_vulkan_g_pass_frag = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/g_pass.frag" });
const validate_vulkan_taa_vert = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/taa.vert" });
const validate_vulkan_taa_frag = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/taa.frag" });
const validate_vulkan_lpv_inject_comp = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/lpv_inject.comp" });
const validate_vulkan_lpv_propagate_comp = b.addSystemCommand(&.{ "glslangValidator", "-V", "assets/shaders/vulkan/lpv_propagate.comp" });

Expand All @@ -197,6 +199,8 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&validate_vulkan_ssao_frag.step);
test_step.dependOn(&validate_vulkan_ssao_blur_frag.step);
test_step.dependOn(&validate_vulkan_g_pass_frag.step);
test_step.dependOn(&validate_vulkan_taa_vert.step);
test_step.dependOn(&validate_vulkan_taa_frag.step);
test_step.dependOn(&validate_vulkan_lpv_inject_comp.step);
test_step.dependOn(&validate_vulkan_lpv_propagate_comp.step);
}
42 changes: 42 additions & 0 deletions src/engine/graphics/camera.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ const Key = @import("../core/interfaces.zig").Key;
const InputMapper = @import("../../game/input_mapper.zig").InputMapper;

pub const Camera = struct {
// 4-tap Halton(2,3) sequence centered to [-0.5, 0.5] pixel offsets.
// We keep this to 4 samples to keep temporal convergence fast while matching
// the current low-latency TAA target (minimal history lag and ghosting).
const JITTER_SEQUENCE = [_][2]f32{
.{ 0.0, -0.16666667 },
.{ -0.25, 0.16666667 },
.{ 0.25, -0.3888889 },
.{ -0.375, -0.055555556 },
};

position: Vec3,

/// Yaw in radians (rotation around Y axis)
Expand Down Expand Up @@ -36,6 +46,7 @@ pub const Camera = struct {
forward: Vec3,
right: Vec3,
up: Vec3,
jitter_index: usize,

pub const Config = struct {
position: Vec3 = Vec3.init(0, 0, 3),
Expand All @@ -61,11 +72,25 @@ pub const Camera = struct {
.forward = Vec3.zero,
.right = Vec3.zero,
.up = Vec3.zero,
.jitter_index = 0,
};
cam.updateVectors();
return cam;
}

pub fn resetJitter(self: *Camera) void {
self.jitter_index = 0;
}

pub fn advanceJitter(self: *Camera) void {
self.jitter_index = (self.jitter_index + 1) % JITTER_SEQUENCE.len;
}

fn currentJitterPixel(self: *const Camera, enabled: bool) [2]f32 {
if (!enabled) return .{ 0.0, 0.0 };
return JITTER_SEQUENCE[self.jitter_index];
}

/// Update camera from input (call once per frame)
pub fn update(self: *Camera, input: *const Input, mapper: *const InputMapper, delta_time: f32) void {
// Mouse look
Expand Down Expand Up @@ -126,6 +151,23 @@ pub const Camera = struct {
return Mat4.perspective(self.fov, aspect_ratio, self.near, self.far);
}

pub fn getProjectionMatrixReverseZ(self: *const Camera, aspect_ratio: f32) Mat4 {
return Mat4.perspectiveReverseZ(self.fov, aspect_ratio, self.near, self.far);
}

pub fn getJitteredProjectionMatrixReverseZ(self: *const Camera, aspect_ratio: f32, viewport_width: f32, viewport_height: f32, jitter_enabled: bool) Mat4 {
const base_projection = self.getProjectionMatrixReverseZ(aspect_ratio);
if (!jitter_enabled or viewport_width <= 0.0 or viewport_height <= 0.0) {
return base_projection;
}

const jitter = self.currentJitterPixel(jitter_enabled);
const jitter_x_ndc = (jitter[0] * 2.0) / viewport_width;
const jitter_y_ndc = (jitter[1] * 2.0) / viewport_height;
const jitter_matrix = Mat4.translate(Vec3.init(jitter_x_ndc, jitter_y_ndc, 0.0));
return jitter_matrix.multiply(base_projection);
}

/// Get view matrix centered at origin (for floating origin rendering)
/// Camera is conceptually at origin looking in the forward direction
pub fn getViewMatrixOriginCentered(self: *const Camera) Mat4 {
Expand Down
34 changes: 29 additions & 5 deletions src/engine/graphics/render_graph.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const World = @import("../../world/world.zig").World;
const shadow_scene = @import("shadow_scene.zig");
const RHI = @import("rhi.zig").RHI;
const rhi_pkg = @import("rhi.zig");
const Mat4 = @import("../math/mat4.zig").Mat4;
const Vec3 = @import("../math/vec3.zig").Vec3;
const log = @import("../core/log.zig");
const CSM = @import("csm.zig");
Expand All @@ -22,6 +21,9 @@ pub const SceneContext = struct {
aspect: f32,
sky_params: rhi_pkg.SkyParams,
cloud_params: rhi_pkg.CloudParams,
taa_enabled: bool,
viewport_width: f32,
viewport_height: f32,
main_shader: rhi_pkg.ShaderHandle,
env_map_handle: rhi_pkg.TextureHandle,
shadow: rhi_pkg.ShadowConfig,
Expand Down Expand Up @@ -212,7 +214,7 @@ pub const GPass = struct {
ctx.rhi.beginGPass();
const atlas = ctx.material_system.getAtlasHandles(ctx.env_map_handle);
ctx.rhi.bindTexture(atlas.diffuse, 1);
const view_proj = Mat4.perspectiveReverseZ(ctx.camera.fov, ctx.aspect, ctx.camera.near, ctx.camera.far).multiply(ctx.camera.getViewMatrixOriginCentered());
const view_proj = ctx.camera.getJitteredProjectionMatrixReverseZ(ctx.aspect, ctx.viewport_width, ctx.viewport_height, ctx.taa_enabled).multiply(ctx.camera.getViewMatrixOriginCentered());
ctx.world.render(view_proj, ctx.camera.position, false);
ctx.rhi.endGPass();
}
Expand All @@ -234,7 +236,7 @@ pub const SSAOPass = struct {
fn execute(ptr: *anyopaque, ctx: SceneContext) anyerror!void {
_ = ptr;
if (!ctx.ssao_enabled or ctx.disable_ssao) return;
const proj = Mat4.perspectiveReverseZ(ctx.camera.fov, ctx.aspect, ctx.camera.near, ctx.camera.far);
const proj = ctx.camera.getJitteredProjectionMatrixReverseZ(ctx.aspect, ctx.viewport_width, ctx.viewport_height, ctx.taa_enabled);
const inv_proj = proj.inverse();
ctx.rhi.ssao().compute(proj, inv_proj);
}
Expand Down Expand Up @@ -288,7 +290,7 @@ pub const OpaquePass = struct {
rhi.bindTexture(ctx.lpv_texture_handle, 11);
rhi.bindTexture(ctx.lpv_texture_handle_g, 12);
rhi.bindTexture(ctx.lpv_texture_handle_b, 13);
const view_proj = Mat4.perspectiveReverseZ(ctx.camera.fov, ctx.aspect, ctx.camera.near, ctx.camera.far).multiply(ctx.camera.getViewMatrixOriginCentered());
const view_proj = ctx.camera.getJitteredProjectionMatrixReverseZ(ctx.aspect, ctx.viewport_width, ctx.viewport_height, ctx.taa_enabled).multiply(ctx.camera.getViewMatrixOriginCentered());
ctx.world.render(view_proj, ctx.camera.position, true);
}
};
Expand All @@ -309,7 +311,7 @@ pub const CloudPass = struct {
fn execute(ptr: *anyopaque, ctx: SceneContext) anyerror!void {
_ = ptr;
if (ctx.disable_clouds) return;
const view_proj = Mat4.perspectiveReverseZ(ctx.camera.fov, ctx.aspect, ctx.camera.near, ctx.camera.far).multiply(ctx.camera.getViewMatrixOriginCentered());
const view_proj = ctx.camera.getJitteredProjectionMatrixReverseZ(ctx.aspect, ctx.viewport_width, ctx.viewport_height, ctx.taa_enabled).multiply(ctx.camera.getViewMatrixOriginCentered());
ctx.atmosphere_system.renderClouds(ctx.cloud_params, view_proj) catch |err| {
if (err != error.ResourceNotReady and
err != error.CloudPipelineNotReady and
Expand Down Expand Up @@ -386,6 +388,28 @@ pub const BloomPass = struct {
}
};

// TAA pass - reserved temporal AA stage between scene rendering and bloom/post.
pub const TAAPass = struct {
enabled: bool = true,
const VTABLE = IRenderPass.VTable{
.name = "TAAPass",
.needs_main_pass = false,
.execute = execute,
};
pub fn pass(self: *TAAPass) IRenderPass {
return .{
.ptr = self,
.vtable = &VTABLE,
};
}

fn execute(ptr: *anyopaque, ctx: SceneContext) anyerror!void {
const self: *TAAPass = @ptrCast(@alignCast(ptr));
if (!self.enabled or !ctx.taa_enabled) return;
ctx.rhi.computeTAA();
}
};

// FXAA pass - applies anti-aliasing to LDR output
pub const FXAAPass = struct {
enabled: bool = true,
Expand Down
16 changes: 16 additions & 0 deletions src/engine/graphics/rhi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ pub const IRenderContext = struct {
endFXAAPass: *const fn (ptr: *anyopaque) void,
// Bloom pass
computeBloom: *const fn (ptr: *anyopaque) void,
// TAA pass
computeTAA: *const fn (ptr: *anyopaque) void,
getEncoder: *const fn (ptr: *anyopaque) IGraphicsCommandEncoder,
getStateContext: *const fn (ptr: *anyopaque) IRenderStateContext,

Expand Down Expand Up @@ -347,6 +349,9 @@ pub const IRenderContext = struct {
pub fn computeBloom(self: IRenderContext) void {
self.vtable.computeBloom(self.ptr);
}
pub fn computeTAA(self: IRenderContext) void {
self.vtable.computeTAA(self.ptr);
}
pub fn getEncoder(self: IRenderContext) IGraphicsCommandEncoder {
return self.vtable.getEncoder(self.ptr);
}
Expand Down Expand Up @@ -482,6 +487,8 @@ pub const RHI = struct {
setFilmGrainIntensity: *const fn (ctx: *anyopaque, intensity: f32) void,
setColorGradingEnabled: *const fn (ctx: *anyopaque, enabled: bool) void,
setColorGradingIntensity: *const fn (ctx: *anyopaque, intensity: f32) void,
setTAABlendFactor: *const fn (ctx: *anyopaque, value: f32) void,
setTAAVelocityRejection: *const fn (ctx: *anyopaque, value: f32) void,
};

pub fn factory(self: RHI) IResourceFactory {
Expand Down Expand Up @@ -672,6 +679,9 @@ pub const RHI = struct {
pub fn computeBloom(self: RHI) void {
self.vtable.render.computeBloom(self.ptr);
}
pub fn computeTAA(self: RHI) void {
self.vtable.render.computeTAA(self.ptr);
}
pub fn setTextureUniforms(self: RHI, enabled: bool, handles: [SHADOW_CASCADE_COUNT]TextureHandle) void {
self.vtable.render.setTextureUniforms(self.ptr, enabled, handles);
}
Expand Down Expand Up @@ -734,4 +744,10 @@ pub const RHI = struct {
pub fn setColorGradingIntensity(self: RHI, intensity: f32) void {
self.vtable.setColorGradingIntensity(self.ptr, intensity);
}
pub fn setTAABlendFactor(self: RHI, value: f32) void {
self.vtable.setTAABlendFactor(self.ptr, value);
}
pub fn setTAAVelocityRejection(self: RHI, value: f32) void {
self.vtable.setTAAVelocityRejection(self.ptr, value);
}
};
3 changes: 3 additions & 0 deletions src/engine/graphics/rhi_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ const MockContext = struct {
.beginFXAAPass = undefined,
.endFXAAPass = undefined,
.computeBloom = undefined,
.computeTAA = undefined,
.getEncoder = MockContext.getEncoder,
.getStateContext = MockContext.getStateContext,
.setClearColor = undefined,
Expand Down Expand Up @@ -340,6 +341,8 @@ const MockContext = struct {
.setFilmGrainIntensity = undefined,
.setColorGradingEnabled = undefined,
.setColorGradingIntensity = undefined,
.setTAABlendFactor = undefined,
.setTAAVelocityRejection = undefined,
};

const MOCK_ENCODER_VTABLE = rhi.IGraphicsCommandEncoder.VTable{
Expand Down
Loading
Loading