tone mapping

This commit is contained in:
jacob 2025-07-18 12:04:06 -05:00
parent 37796ea505
commit aa395bfd5a
15 changed files with 127 additions and 63 deletions

View File

@ -5,7 +5,7 @@
* ========================== */
#define ROOTSIG \
"RootConstants(num32BitConstants = 18, b0), " \
"RootConstants(num32BitConstants = 17, b0), " \
"DescriptorTable(SRV(t0, space = 0, numDescriptors = unbounded, flags = DESCRIPTORS_VOLATILE)), " \
\
"StaticSampler(s0, " \
@ -65,7 +65,7 @@ struct ps_output {
SH_ENTRY(ROOTSIG) struct ps_output ps(struct ps_input input)
{
struct ps_output output;
const float gamma = g_constants.gamma;
output.SV_Target = pow(abs(g_textures[g_constants.tex_urid].Sample(g_sampler, input.vs.uv)), 1/gamma);
float4 color = g_textures[g_constants.tex_urid].Sample(g_sampler, input.vs.uv);
output.SV_Target = color;
return output;
}

View File

@ -11,6 +11,16 @@
# define INLINE /* For intellisense */
#endif
INLINE float4 float4_norm_from_uint(uint v)
{
float4 res;
res.r = ((v >> 0) & 0xFF) / 255.0;
res.g = ((v >> 8) & 0xFF) / 255.0;
res.b = ((v >> 16) & 0xFF) / 255.0;
res.a = ((v >> 24) & 0xFF) / 255.0;
return res;
}
/* Linear color from normalized sRGB */
INLINE float4 linear_from_srgb(float4 srgb)
{
@ -20,10 +30,5 @@ INLINE float4 linear_from_srgb(float4 srgb)
/* Linear color from R8G8B8A8 sRGB */
INLINE float4 linear_from_srgb32(uint srgb32)
{
float4 res;
res.r = ((srgb32 >> 0) & 0xFF) / 255.0;
res.g = ((srgb32 >> 8) & 0xFF) / 255.0;
res.b = ((srgb32 >> 16) & 0xFF) / 255.0;
res.a = ((srgb32 >> 24) & 0xFF) / 255.0;
return linear_from_srgb(res);
return linear_from_srgb(float4_norm_from_uint(srgb32));
}

View File

@ -24,7 +24,7 @@ RWTexture2D<uint2> g_flood_textures[]: register(u0, space1);
SamplerState g_sampler : register(s0);
/* ========================== *
* Compute shader
* Entry point
* ========================== */
struct cs_input {

View File

@ -61,7 +61,7 @@ SH_ENTRY(ROOTSIG) struct vs_output vs(struct vs_input input)
output.grid_id = instance.grid_id;
output.uv = instance.uv0 + ((vert + 0.5) * (instance.uv1 - instance.uv0));
output.tint_lin = linear_from_srgb32(instance.tint_srgb);
output.emittance_lin = linear_from_srgb32(instance.emittance_srgb);
output.emittance_lin = linear_from_srgb(float4(instance.light_emittance_srgb, instance.is_light));
return output;
}

View File

@ -30,6 +30,12 @@ INLINE struct sh_float2 sh_float2_from_v2(struct v2 v)
return (struct sh_float2) { .v[0] = v.x, .v[1] = v.y };
}
struct sh_float3 { f32 v[3]; };
INLINE struct sh_float3 sh_float3_from_v3(struct v3 v)
{
return (struct sh_float3) { .v[0] = v.x, .v[1] = v.y, .v[2] = v.z };
}
struct sh_float4x4 { f32 v[4][4]; };
INLINE struct sh_float4x4 sh_float4x4_from_mat4x4(struct mat4x4 v)
{
@ -58,17 +64,6 @@ INLINE struct sh_float2x3 sh_float2x3_from_xform(struct xform v)
#endif
/* ========================== *
* Blit shader structures
* ========================== */
SH_STRUCT(sh_blit_constants {
SH_DECL(float4x4, projection);
SH_DECL(uint, tex_urid);
SH_DECL(float, gamma);
});
SH_ASSERT_32BIT(struct sh_blit_constants, 18); /* Expected 32bit root constant size in shader */
/* ========================== *
* Material shader structures
* ========================== */
@ -85,7 +80,8 @@ SH_STRUCT(sh_material_instance {
SH_DECL(float2, uv0);
SH_DECL(float2, uv1);
SH_DECL(uint, tint_srgb);
SH_DECL(uint, emittance_srgb);
SH_DECL(uint, is_light);
SH_DECL(float3, light_emittance_srgb);
});
SH_STRUCT(sh_material_grid {
@ -125,8 +121,10 @@ SH_STRUCT(sh_shade_constants {
SH_DECL(uint, write_tex_urid);
SH_DECL(uint, tex_width);
SH_DECL(uint, tex_height);
SH_DECL(float, exposure);
SH_DECL(float, gamma);
});
SH_ASSERT_32BIT(struct sh_shade_constants, 7); /* Expected 32bit root constant size in shader */
SH_ASSERT_32BIT(struct sh_shade_constants, 9); /* Expected 32bit root constant size in shader */
/* ========================== *
* Shape shader structures
@ -159,3 +157,13 @@ SH_STRUCT(sh_ui_instance {
SH_DECL(float2, uv1);
SH_DECL(uint, tint_srgb);
});
/* ========================== *
* Blit shader structures
* ========================== */
SH_STRUCT(sh_blit_constants {
SH_DECL(float4x4, projection);
SH_DECL(uint, tex_urid);
});
SH_ASSERT_32BIT(struct sh_blit_constants, 17); /* Expected 32bit root constant size in shader */

View File

@ -5,7 +5,7 @@
* ========================== */
#define ROOTSIG \
"RootConstants(num32BitConstants = 7, b0), " \
"RootConstants(num32BitConstants = 9, b0), " \
"DescriptorTable(SRV(t0, space = 0, numDescriptors = unbounded, flags = DESCRIPTORS_VOLATILE)), " \
"DescriptorTable(SRV(t0, space = 1, numDescriptors = unbounded, flags = DESCRIPTORS_VOLATILE)), " \
"DescriptorTable(UAV(u0, space = 2, numDescriptors = unbounded, flags = DESCRIPTORS_VOLATILE)), " \
@ -25,10 +25,6 @@ RWTexture2D<float4> g_write_textures[]: register(u0, space2);
SamplerState g_sampler : register(s0);
/* ========================== *
* Compute shader
* ========================== */
struct cs_input {
DECLS(uint3, SV_DispatchThreadID);
};
@ -36,19 +32,31 @@ struct cs_input {
#define SAMPLES 4
#define MARCHES 16
INLINE float4 get_light_in_dir(uint2 pos, float2 dir)
/* ========================== *
* Lighting
* ========================== */
#define AMBIENT float4(1, 1, 1, 1)
INLINE float4 get_light_in_dir(uint2 ray_start, float2 ray_dir)
{
float4 result = 0;
uint2 at = pos;
float4 result = AMBIENT;
float2 at_float = ray_start;
uint2 at_uint = ray_start;
for (uint i = 0; i < MARCHES; ++i) {
uint2 flood = g_emittance_flood_textures[g_constants.emittance_flood_tex_urid][at];
int2 dist_vec = (int2)at - (int2)flood;
uint2 flood = g_emittance_flood_textures[g_constants.emittance_flood_tex_urid][at_uint];
float2 dist_vec = at_float - (float2)flood;
float dist = length(dist_vec);
if (dist <= 2) {
if (dist <= 1) {
result = g_gbuff_textures[g_constants.emittance_tex_urid][flood];
break;
} else {
at += dir * dist;
at_float += ray_dir * dist;
at_uint = round(at_float);
if (at_uint.x < 0 || at_uint.x >= g_constants.tex_width || at_uint.y < 0 || at_uint.y >= g_constants.tex_height) {
/* Ambient lighting (ray hit edge of screen) */
break;
}
}
}
return result;
@ -63,7 +71,7 @@ INLINE float4 get_light_at_pos(uint2 pos)
float4 result = 0;
for (int i = 0; i < SAMPLES; ++i) {
float angle = TAU * (((float)i + rand_float_from_float2(pos + (float)i + sin(g_constants.time))) / ((float)SAMPLES - 1));
float2 dir = float2(sin(angle), cos(angle));
float2 dir = float2(cos(angle), sin(angle));
float4 light_in_dir = get_light_in_dir(pos, dir);
result += light_in_dir;
}
@ -72,6 +80,21 @@ INLINE float4 get_light_at_pos(uint2 pos)
return result;
}
/* ========================== *
* Tone map
* ========================== */
/* ACES approximation by Krzysztof Narkowicz
* https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ */
INLINE float3 tone_map(float3 v)
{
return saturate((v * (2.51f * v + 0.03f)) / (v * (2.43f * v + 0.59f) + 0.14f));
}
/* ========================== *
* Entry point
* ========================== */
[numthreads(8, 8, 1)]
SH_ENTRY(ROOTSIG) void cs(struct cs_input input)
{
@ -82,7 +105,16 @@ SH_ENTRY(ROOTSIG) void cs(struct cs_input input)
float4 albedo = g_gbuff_textures[g_constants.albedo_tex_urid][id];
float4 lighting = get_light_at_pos(id);
float4 final_color = albedo * lighting;
float4 color = albedo * lighting;
// float4 color = albedo + lighting;
g_write_textures[g_constants.write_tex_urid][id] = final_color;
/* Tonemap */
/* TODO: Dynamic exposure based on average scene luminance */
color *= g_constants.exposure;
color.rgb = tone_map(color.rgb);
/* Gamma correct */
color = pow(abs(color), 1/g_constants.gamma);
g_write_textures[g_constants.write_tex_urid][id] = color;
}

View File

@ -21,14 +21,14 @@ struct vs_input {
struct vs_output {
DECLS(float4, SV_Position);
DECLS(float4, color_lin);
DECLS(float4, color_srgb);
};
SH_ENTRY(ROOTSIG) struct vs_output vs(struct vs_input input)
{
struct vs_output output;
output.SV_Position = mul(g_constants.projection, float4(input.pos.xy, 0, 1));
output.color_lin = linear_from_srgb(input.color_srgb);
output.color_srgb = input.color_srgb;
return output;
}
@ -47,6 +47,6 @@ struct ps_output {
SH_ENTRY(ROOTSIG) struct ps_output ps(struct ps_input input)
{
struct ps_output output;
output.SV_Target = input.vs.color_lin;
output.SV_Target = input.vs.color_srgb;
return output;
}

View File

@ -34,7 +34,7 @@ struct vs_input {
struct vs_output {
nointerpolation DECLS(int, tex_nurid);
DECLS(float2, uv);
DECLS(float4, tint_lin);
DECLS(float4, tint_srgb);
DECLS(float4, SV_Position);
};
@ -55,7 +55,7 @@ SH_ENTRY(ROOTSIG) struct vs_output vs(struct vs_input input)
output.SV_Position = mul(g_constants.projection, float4(world_pos, 0, 1));
output.tex_nurid = instance.tex_nurid;
output.uv = instance.uv0 + ((vert + 0.5) * (instance.uv1 - instance.uv0));
output.tint_lin = linear_from_srgb32(instance.tint_srgb);
output.tint_srgb = float4_norm_from_uint(instance.tint_srgb);
return output;
}
@ -74,7 +74,7 @@ struct ps_output {
SH_ENTRY(ROOTSIG) struct ps_output ps(struct ps_input input)
{
struct ps_output output;
float4 color = input.vs.tint_lin;
float4 color = input.vs.tint_srgb;
/* Texture */
if (input.vs.tex_nurid >= 0) {

View File

@ -35,7 +35,8 @@ void draw_material(struct gp_render_sig *sig, struct draw_material_params params
cmd.material.texture = params.texture;
cmd.material.clip = params.clip;
cmd.material.tint = params.tint;
cmd.material.emittance = params.emittance;
cmd.material.is_light = params.is_light;
cmd.material.light_emittance = params.light_emittance;
gp_push_render_cmd(sig, &cmd);
}

View File

@ -25,7 +25,8 @@ struct draw_material_params {
struct sprite_tag sprite;
struct clip_rect clip;
u32 tint;
u32 emittance;
b32 is_light;
struct v3 light_emittance;
};
void draw_material(struct gp_render_sig *sig, struct draw_material_params params);

View File

@ -36,6 +36,7 @@ enum gp_texture_format {
GP_TEXTURE_FORMAT_NONE,
GP_TEXTURE_FORMAT_R8G8B8A8_UNORM,
GP_TEXTURE_FORMAT_R8G8B8A8_UNORM_SRGB,
GP_TEXTURE_FORMAT_R16G16B16A16_FLOAT,
NUM_GP_TEXTURE_FORMATS
};
@ -70,7 +71,8 @@ struct gp_render_cmd_desc {
struct gp_resource *texture;
struct clip_rect clip;
u32 tint;
u32 emittance;
b32 is_light;
struct v3 light_emittance;
i32 grid_cmd_id;
} material;
struct {

View File

@ -680,7 +680,7 @@ INTERNAL void dx12_init_pipelines(void)
desc->ps.func = LIT("ps");
desc->rtvs[0].format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc->rtvs[0].blending = 1;
desc->rtvs[1].format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc->rtvs[1].format = DXGI_FORMAT_R16G16B16A16_FLOAT;
desc->rtvs[1].blending = 1;
dict_set(G.pipelines_arena, G.pipeline_descs, hash_fnv64(HASH_FNV64_BASIS, desc->name), (u64)desc);
}
@ -710,7 +710,7 @@ INTERNAL void dx12_init_pipelines(void)
desc->ps.func = LIT("ps");
desc->ia[0] = (D3D12_INPUT_ELEMENT_DESC) { "pos", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 };
desc->ia[1] = (D3D12_INPUT_ELEMENT_DESC) { "color_srgb", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 };
desc->rtvs[0].format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc->rtvs[0].format = DXGI_FORMAT_R16G16B16A16_FLOAT;
desc->rtvs[0].blending = 1;
dict_set(G.pipelines_arena, G.pipeline_descs, hash_fnv64(HASH_FNV64_BASIS, desc->name), (u64)desc);
}
@ -722,7 +722,7 @@ INTERNAL void dx12_init_pipelines(void)
desc->ps.file = LIT("sh/ui.hlsl");
desc->vs.func = LIT("vs");
desc->ps.func = LIT("ps");
desc->rtvs[0].format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc->rtvs[0].format = DXGI_FORMAT_R16G16B16A16_FLOAT;
desc->rtvs[0].blending = 1;
dict_set(G.pipelines_arena, G.pipeline_descs, hash_fnv64(HASH_FNV64_BASIS, desc->name), (u64)desc);
}
@ -2251,7 +2251,8 @@ struct gp_resource *gp_texture_alloc(enum gp_texture_format format, u32 flags, s
struct dxgi_format_info { DXGI_FORMAT format; u32 size; };
LOCAL_PERSIST const struct dxgi_format_info formats[] = {
[GP_TEXTURE_FORMAT_R8G8B8A8_UNORM] = { DXGI_FORMAT_R8G8B8A8_UNORM, 4 },
[GP_TEXTURE_FORMAT_R8G8B8A8_UNORM_SRGB] = { DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, 4 }
[GP_TEXTURE_FORMAT_R8G8B8A8_UNORM_SRGB] = { DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, 4 },
[GP_TEXTURE_FORMAT_R16G16B16A16_FLOAT] = { DXGI_FORMAT_R16G16B16A16_FLOAT, 8 }
};
DXGI_FORMAT dxgi_format = ZI;
@ -2538,7 +2539,8 @@ struct material_instance_desc {
struct dx12_resource *texture;
struct clip_rect clip;
u32 tint;
u32 emittance;
b32 is_light;
struct v3 light_emittance;
i32 grid_id;
};
@ -2605,7 +2607,8 @@ i32 gp_push_render_cmd(struct gp_render_sig *render_sig, struct gp_render_cmd_de
instance_desc->texture = (struct dx12_resource *)cmd_desc->material.texture;
instance_desc->clip = cmd_desc->material.clip;
instance_desc->tint = cmd_desc->material.tint;
instance_desc->emittance = cmd_desc->material.emittance;
instance_desc->is_light = cmd_desc->material.is_light;
instance_desc->light_emittance = cmd_desc->material.light_emittance;
instance_desc->grid_id = cmd_desc->material.grid_cmd_id - 1;
ret = ++sig->num_material_instance_descs;
} break;
@ -2654,7 +2657,7 @@ void gp_run_render(struct gp_render_sig *render_sig, struct gp_render_params par
}
/* Alloc buffers */
sig->albedo = gbuff_alloc(DXGI_FORMAT_R8G8B8A8_UNORM, final_target_size, D3D12_RESOURCE_STATE_RENDER_TARGET);
sig->emittance = gbuff_alloc(DXGI_FORMAT_R8G8B8A8_UNORM, final_target_size, D3D12_RESOURCE_STATE_RENDER_TARGET);
sig->emittance = gbuff_alloc(DXGI_FORMAT_R16G16B16A16_FLOAT, final_target_size, D3D12_RESOURCE_STATE_RENDER_TARGET);
sig->emittance_flood_a = gbuff_alloc(DXGI_FORMAT_R16G16_UINT, final_target_size, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
sig->emittance_flood_b = gbuff_alloc(DXGI_FORMAT_R16G16_UINT, final_target_size, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
@ -2706,7 +2709,8 @@ void gp_run_render(struct gp_render_sig *render_sig, struct gp_render_params par
instance->uv0 = sh_float2_from_v2(desc->clip.p0);
instance->uv1 = sh_float2_from_v2(desc->clip.p1);
instance->tint_srgb = sh_uint_from_u32(desc->tint);
instance->emittance_srgb = sh_uint_from_u32(desc->emittance);
instance->is_light = sh_uint_from_u32(desc->is_light);
instance->light_emittance_srgb = sh_float3_from_v3(desc->light_emittance);
}
}
@ -2905,6 +2909,8 @@ void gp_run_render(struct gp_render_sig *render_sig, struct gp_render_params par
constants.write_tex_urid = sh_uint_from_u32(final_target->uav_descriptor->index);
constants.tex_width = sh_uint_from_u32(final_target_size.x);
constants.tex_height = sh_uint_from_u32(final_target_size.y);
constants.exposure = sh_float_from_f32(1.0);
constants.gamma = sh_float_from_f32(2.2);
/* Set parameters */
command_list_set_compute_root_constant(cl, &constants, sizeof(constants));
@ -3435,7 +3441,6 @@ INTERNAL void present_blit(struct swapchain_buffer *dst, struct dx12_resource *s
struct sh_blit_constants constants = ZI;
constants.projection = sh_float4x4_from_mat4x4(vp_matrix);
constants.tex_urid = sh_uint_from_u32(src->srv_descriptor->index);
constants.gamma = sh_float_from_f32(2.2);
/* Set parameters */
command_list_set_graphics_root_constant(cl, &constants, sizeof(constants));

View File

@ -270,6 +270,7 @@ struct sim_ent {
struct sprite_tag sprite;
struct string sprite_span_name;
u32 sprite_tint;
struct v3 sprite_emittance;
struct string sprite_collider_slice; /* Collider will sync to bounds of this slice if set */

View File

@ -254,9 +254,9 @@ INTERNAL void test_spawn_entities2(struct sim_ent *parent, struct v2 pos)
//struct sim_ent *e = sim_ent_alloc_local(parent);
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
f32 r = 0;
f32 rot = 0;
struct v2 size = V2(1, 0.5);
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
struct xform xf = XFORM_TRS(.t = pos, .r = rot, .s = size);
sim_ent_set_xform(e, xf);
e->sprite = sprite_tag_from_path(LIT("sprite/box.ase"));
@ -270,6 +270,15 @@ INTERNAL void test_spawn_entities2(struct sim_ent *parent, struct v2 pos)
sim_ent_enable_prop(e, SEPROP_LIGHT_TEST);
/* FIXME: Remove this */
{
static struct rand_state rand = ZI;
f32 r = rand_f64_from_state(&rand, 1, 5);
f32 g = rand_f64_from_state(&rand, 1, 5);
f32 b = rand_f64_from_state(&rand, 1, 5);
e->sprite_emittance = V3(r, g, b);
}
sim_ent_enable_prop(e, SEPROP_DYNAMIC);
e->mass_unscaled = 100;
e->inertia_unscaled = 50;

View File

@ -1200,10 +1200,10 @@ INTERNAL void user_update(struct sys_window *window)
/* TODO: Fade in placeholder if texture isn't loaded */
if (sheet->loaded) {
b32 is_light = sim_ent_has_prop(ent, SEPROP_LIGHT_TEST);
u32 emittance = is_light ? COLOR_WHITE : 0;
struct v3 emittance = ent->sprite_emittance;
u32 tint = ent->sprite_tint;
struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, ent->animation_frame);
struct draw_material_params params = DRAW_MATERIAL_PARAMS(.xf = sprite_xform, .sprite = sprite, .tint = tint, .clip = frame.clip, .emittance = emittance);
struct draw_material_params params = DRAW_MATERIAL_PARAMS(.xf = sprite_xform, .sprite = sprite, .tint = tint, .clip = frame.clip, .is_light = is_light, .light_emittance = emittance);
draw_material(G.world_render_sig, params);
}
}
@ -1223,7 +1223,7 @@ INTERNAL void user_update(struct sys_window *window)
struct v2i32 world_tile_index = sim_world_tile_index_from_local_tile_index(chunk_index, local_tile_index);
struct v2 pos = sim_pos_from_world_tile_index(world_tile_index);
struct xform tile_xf = xform_from_rect(RECT_FROM_V2(pos, V2(tile_size, tile_size)));
struct draw_material_params params = DRAW_MATERIAL_PARAMS(.xf = tile_xf, .sprite = tile_sprite, .emittance = COLOR_BLACK);
struct draw_material_params params = DRAW_MATERIAL_PARAMS(.xf = tile_xf, .sprite = tile_sprite, .is_light = 1, .light_emittance = V3(0, 0, 0));
draw_material(G.world_render_sig, params);
}
}
@ -2030,7 +2030,7 @@ INTERNAL void user_update(struct sys_window *window)
if (G.user_texture) {
gp_resource_release(G.user_texture);
}
G.user_texture = gp_texture_alloc(GP_TEXTURE_FORMAT_R8G8B8A8_UNORM, GP_TEXTURE_FLAG_TARGETABLE, user_resolution, 0);
G.user_texture = gp_texture_alloc(GP_TEXTURE_FORMAT_R16G16B16A16_FLOAT, GP_TEXTURE_FLAG_TARGETABLE, user_resolution, 0);
}
/* Draw world to user texture */