From caac3716810a213a76a1c62105a0792ef66958eb Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 22 Dec 2023 14:23:22 -0500 Subject: [PATCH 1/6] vk: rt: tune emissive/additive blending so that it matches original MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To match original more closely it additive geometry should be added to final color in sRGB-γ space. You might not like that it is physically incorrect, but this is what peak compatibility looks like. Related to #668 --- ref/vk/NOTES.md | 8 ++++++++ ref/vk/shaders/denoiser.comp | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/ref/vk/NOTES.md b/ref/vk/NOTES.md index 3cff68af..a964da90 100644 --- a/ref/vk/NOTES.md +++ b/ref/vk/NOTES.md @@ -1079,3 +1079,11 @@ xash vk (remapped) +Y = -X +X = +Z +Z = +Y + +# 2023-12-22 E352 +## sRGB vs γ blending +Original: + `color = a + b` +Our: + `color = sqrt(a*a + b*b)` +There's nothing we can to do `a` only that would make it fake the "original" mixing result. diff --git a/ref/vk/shaders/denoiser.comp b/ref/vk/shaders/denoiser.comp index 520e3dee..554402e3 100644 --- a/ref/vk/shaders/denoiser.comp +++ b/ref/vk/shaders/denoiser.comp @@ -306,8 +306,18 @@ void main() { colour = diffuse + specular; } +// See issue https://github.com/w23/xash3d-fwgs/issues/668, map test_blendmode_additive_alpha. +// This macro enabled adding emissive to the final color in the *incorrect* sRGB-γ space. But it makes +// it look much more like the original. Adding emissive in the *correct* linear space differs +// from the original a lot, and looks perceptively worse. +#define ADD_EMISSIVE_IN_GAMMA_SPACE +#ifndef ADD_EMISSIVE_IN_GAMMA_SPACE + // Physically correct, but looks dull colour += imageLoad(emissive, pix).rgb; colour = LINEARtoSRGB(colour); +#else // INCORRECT + colour = LINEARtoSRGB(colour) + LINEARtoSRGB(imageLoad(emissive, pix).rgb); +#endif imageStore(out_dest, pix, vec4(colour, 0./*unused*/)); } From 180f3ed9cbf1fcd6f5ac8cbb0b12d48140ee4189 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 22 Dec 2023 15:14:27 -0500 Subject: [PATCH 2/6] vk: rt: allow per-model back face culling Make transparent studio models, e.g. holograms, single-sided. Related: #665 --- ref/vk/vk_brush.c | 2 ++ ref/vk/vk_ray_accel.c | 9 ++++++--- ref/vk/vk_ray_internal.h | 1 + ref/vk/vk_ray_model.c | 2 ++ ref/vk/vk_render.c | 1 + ref/vk/vk_render.h | 6 ++++++ ref/vk/vk_rtx.h | 2 ++ ref/vk/vk_sprite.c | 1 + ref/vk/vk_studio.c | 1 + 9 files changed, 22 insertions(+), 3 deletions(-) diff --git a/ref/vk/vk_brush.c b/ref/vk/vk_brush.c index 83d4ed3b..f8c7725f 100644 --- a/ref/vk/vk_brush.c +++ b/ref/vk/vk_brush.c @@ -700,6 +700,7 @@ static void brushDrawWater(r_brush_water_model_t *wmodel, const cl_entity_t *ent R_RenderModelDraw(&wmodel->render_model, (r_model_draw_t){ .render_type = render_type, .material_mode = material_mode, + .material_flags = kMaterialFlag_None, .color = (const vec4_t*)color, .transform = (const matrix4x4*)transform, .prev_transform = (const matrix4x4*)prev_transform, @@ -949,6 +950,7 @@ void R_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, con R_RenderModelDraw(&bmodel->render_model, (r_model_draw_t){ .render_type = render_type, .material_mode = material_mode, + .material_flags = kMaterialFlag_None, .color = &color, .transform = &transform, .prev_transform = &bmodel->prev_transform, diff --git a/ref/vk/vk_ray_accel.c b/ref/vk/vk_ray_accel.c index e3a1739e..86918621 100644 --- a/ref/vk/vk_ray_accel.c +++ b/ref/vk/vk_ray_accel.c @@ -270,13 +270,16 @@ vk_resource_t RT_VkAccelPrepareTlas(vk_combuf_t *combuf) { .instanceShaderBindingTableRecordOffset = 0, .accelerationStructureReference = instance->blas_addr, }; + + const VkGeometryInstanceFlagsKHR flags = (instance->material_flags & kMaterialFlag_CullBackFace_Bit) ? 0 : VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + switch (instance->material_mode) { case MATERIAL_MODE_OPAQUE: inst[i].mask = GEOMETRY_BIT_OPAQUE; inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR, // Force no-culling because there are cases where culling leads to leaking shadows, holes in reflections, etc // CULL_DISABLE_BIT disables culling even if the gl_RayFlagsCullFrontFacingTrianglesEXT bit is set in shaders - inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR | VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR | flags; break; case MATERIAL_MODE_OPAQUE_ALPHA_TEST: inst[i].mask = GEOMETRY_BIT_ALPHA_TEST; @@ -287,7 +290,7 @@ vk_resource_t RT_VkAccelPrepareTlas(vk_combuf_t *combuf) { inst[i].mask = GEOMETRY_BIT_REFRACTIVE; inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR, // Disable culling for translucent surfaces: decide what side it is based on normal wrt ray directions - inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR | VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR | flags; break; case MATERIAL_MODE_BLEND_ADD: case MATERIAL_MODE_BLEND_MIX: @@ -295,7 +298,7 @@ vk_resource_t RT_VkAccelPrepareTlas(vk_combuf_t *combuf) { inst[i].mask = GEOMETRY_BIT_BLEND; inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ADDITIVE, // Force no-culling because these should be visible from any angle - inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR | VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR | flags; break; default: gEngine.Host_Error("Unexpected material mode %d\n", instance->material_mode); diff --git a/ref/vk/vk_ray_internal.h b/ref/vk/vk_ray_internal.h index e191b2cc..85945406 100644 --- a/ref/vk/vk_ray_internal.h +++ b/ref/vk/vk_ray_internal.h @@ -20,6 +20,7 @@ typedef struct rt_draw_instance_s { matrix4x4 prev_transform_row; vec4_t color; uint32_t material_mode; // MATERIAL_MODE_ from ray_interop.h + uint32_t material_flags; // material_flag_bits_e } rt_draw_instance_t; typedef struct { diff --git a/ref/vk/vk_ray_model.c b/ref/vk/vk_ray_model.c index 2fb1fb34..e6588f4f 100644 --- a/ref/vk/vk_ray_model.c +++ b/ref/vk/vk_ray_model.c @@ -361,6 +361,7 @@ void RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ) { draw_instance->blas_addr = model->blas_addr; draw_instance->kusochki_offset = kusochki_offset; draw_instance->material_mode = args.material_mode; + draw_instance->material_flags = args.material_flags; sRGBtoLinearVec4(*args.color_srgb, draw_instance->color); Matrix3x4_Copy(draw_instance->transform_row, args.transform); Matrix4x4_Copy(draw_instance->prev_transform_row, args.prev_transform); @@ -453,6 +454,7 @@ void RT_DynamicModelProcessFrame(void) { draw_instance->blas_addr = dyn->blas_addr; draw_instance->kusochki_offset = kusochki_offset; draw_instance->material_mode = i; + draw_instance->material_flags = 0; Vector4Set(draw_instance->color, 1, 1, 1, 1); Matrix3x4_LoadIdentity(draw_instance->transform_row); Matrix4x4_LoadIdentity(draw_instance->prev_transform_row); diff --git a/ref/vk/vk_render.c b/ref/vk/vk_render.c index a6c3c669..722ca0ed 100644 --- a/ref/vk/vk_render.c +++ b/ref/vk/vk_render.c @@ -791,6 +791,7 @@ void R_RenderModelDraw(const vk_render_model_t *model, r_model_draw_t args) { ASSERT(model->rt_model); RT_FrameAddModel(model->rt_model, (rt_frame_add_model_t){ .material_mode = args.material_mode, + .material_flags = args.material_flags, .transform = (const matrix3x4*)args.transform, .prev_transform = (const matrix3x4*)args.prev_transform, .color_srgb = args.color, diff --git a/ref/vk/vk_render.h b/ref/vk/vk_render.h index afef8b2b..00e4ea19 100644 --- a/ref/vk/vk_render.h +++ b/ref/vk/vk_render.h @@ -123,9 +123,15 @@ void R_RenderModelDestroy( vk_render_model_t* model ); qboolean R_RenderModelUpdate( const vk_render_model_t *model ); qboolean R_RenderModelUpdateMaterials( const vk_render_model_t *model, const int *geom_indices, int geom_indices_count); +typedef enum { + kMaterialFlag_None = 0, + kMaterialFlag_CullBackFace_Bit = (1<<0), +} material_flag_bits_e; + typedef struct { vk_render_type_e render_type; // TODO rename legacy material_mode_e material_mode; + uint32_t material_flags; // material_flag_bits_e // These are "consumed": copied into internal storage and can be pointers to stack vars const vec4_t *color; diff --git a/ref/vk/vk_rtx.h b/ref/vk/vk_rtx.h index a009e701..48c17561 100644 --- a/ref/vk/vk_rtx.h +++ b/ref/vk/vk_rtx.h @@ -58,6 +58,8 @@ qboolean RT_ModelUpdateMaterials(struct rt_model_s *model, const struct vk_rende typedef struct { int material_mode; + uint32_t material_flags; + const matrix3x4 *transform, *prev_transform; const vec4_t *color_srgb; diff --git a/ref/vk/vk_sprite.c b/ref/vk/vk_sprite.c index 8f582b00..f85549cf 100644 --- a/ref/vk/vk_sprite.c +++ b/ref/vk/vk_sprite.c @@ -808,6 +808,7 @@ static void R_DrawSpriteQuad( const char *debug_name, const mspriteframe_t *fram R_RenderModelDraw(&g_sprite.quad.model, (r_model_draw_t){ .render_type = render_type, .material_mode = material_mode, + .material_flags = kMaterialFlag_None, .color = (const vec4_t*)color, .transform = &transform, .prev_transform = &transform, diff --git a/ref/vk/vk_studio.c b/ref/vk/vk_studio.c index 7091b8ae..96a12be7 100644 --- a/ref/vk/vk_studio.c +++ b/ref/vk/vk_studio.c @@ -2331,6 +2331,7 @@ static void R_StudioDrawPoints( void ) { R_RenderModelDraw(&render_submodel->model, (r_model_draw_t){ .render_type = render_type, .material_mode = material_mode, + .material_flags = kMaterialFlag_CullBackFace_Bit, // TODO for transparent only? .color = &color, .transform = &g_studio_current.entmodel->transform, .prev_transform = &g_studio_current.entmodel->prev_transform, From 310ecff585d017ace24ecafdab4e7f1a33145d19 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 22 Dec 2023 15:19:24 -0500 Subject: [PATCH 3/6] vk: rt: make additive brush models single-sided Fixes #665 --- ref/vk/vk_brush.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ref/vk/vk_brush.c b/ref/vk/vk_brush.c index f8c7725f..9b87d3b6 100644 --- a/ref/vk/vk_brush.c +++ b/ref/vk/vk_brush.c @@ -830,6 +830,7 @@ void R_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, con vec4_t color = {1, 1, 1, 1}; vk_render_type_e render_type = kVkRenderTypeSolid; + uint32_t material_flags = kMaterialFlag_None; switch (render_mode) { case kRenderNormal: Vector4Set(color, 1.f, 1.f, 1.f, 1.f); @@ -846,6 +847,7 @@ void R_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, con case kRenderTransAdd: Vector4Set(color, blend, blend, blend, 1.f); render_type = kVkRenderType_A_1_R; + material_flags |= kMaterialFlag_CullBackFace_Bit; break; case kRenderTransAlpha: if( gEngine.EngineGetParm( PARM_QUAKE_COMPATIBLE, 0 )) @@ -950,7 +952,7 @@ void R_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, con R_RenderModelDraw(&bmodel->render_model, (r_model_draw_t){ .render_type = render_type, .material_mode = material_mode, - .material_flags = kMaterialFlag_None, + .material_flags = material_flags, .color = &color, .transform = &transform, .prev_transform = &bmodel->prev_transform, From 46e95f125578b150899acbae849764cdf8ad1a87 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 28 Dec 2023 11:21:32 -0500 Subject: [PATCH 4/6] vk: rt: linearize kusochki.color/override_color --- ref/vk/NOTES.md | 29 +++++++++++++++++++++++++++++ ref/vk/TODO.md | 4 ++++ ref/vk/vk_ray_model.c | 11 ++++++++++- ref/vk/vk_render.c | 2 +- ref/vk/vk_rtx.h | 2 +- 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/ref/vk/NOTES.md b/ref/vk/NOTES.md index a964da90..9d8e8c4f 100644 --- a/ref/vk/NOTES.md +++ b/ref/vk/NOTES.md @@ -1087,3 +1087,32 @@ Original: Our: `color = sqrt(a*a + b*b)` There's nothing we can to do `a` only that would make it fake the "original" mixing result. + +# 2023-12-28 E353 +## Passing colors from all over the place into `trace_simple_blending.glsl` +- color = mm_color * texture_color * geom.vertex_color * alpha + - alpha = mm_color.a * texture_color.a * geom.vertex_color.a + - mm_color = model.color * kusok.material.base_color + - model.color -- already linearized + - kusok.material.base_color = mat->base_color * override_color + - mat->base_color -- specified in .mat files by hand + - [x] Which colorspace should it be specified in? + Currently it is passed in as is, which means that it's accidentally linear. + - override_color -- passed from the engine through `R_RenderDrawOnce()`, called from triapi + - [x] sRGB-γ, should linearize + - texture_color -- just texture sampled color. sRGB-γ vs linear is specified at VkImageView level at loading time + - geom.vertex_color -- barycentric-lerped from vk_vertex[].color + - vk_vertex[].color -- rgba8 + - [x] which colorspace? Should be sRGB-γ originally + - [x] Do we need to linearize it? YES + - [x] Before lerping or after? BEFORE -- already done + +- Should α be converted from gamma to linear? + - Doing so: + - seems kinda logical -- everything is gamma-space in engine, so it probably should be. + - fixes some 'background' sprites transparency + - makes too-brighs c0a0c beams darker (but kinda too dark imo) + - breaks sprite animation lerping -- now we need 2 native gamma-to-linear functions, wich alpha conv and w/o + +As usual -- original sRGB-specialized game art is painfully incompatible with modern linear PBR. +The best way to address it (hopefully w/o breaking too much linear rendering math) remains to be discovered. diff --git a/ref/vk/TODO.md b/ref/vk/TODO.md index 6c6c33c3..ab995154 100644 --- a/ref/vk/TODO.md +++ b/ref/vk/TODO.md @@ -1,3 +1,7 @@ +# 2023-12-28 E353 +- [x] track color spaces when passing colors into shaders +- [ ] validation failure at startup, #723 + Longer-term agenda for current season: - [ ] Better PBR math, e.g.: - [ ] Black metals: https://github.com/w23/xash3d-fwgs/issues/666 diff --git a/ref/vk/vk_ray_model.c b/ref/vk/vk_ray_model.c index e6588f4f..163abcd7 100644 --- a/ref/vk/vk_ray_model.c +++ b/ref/vk/vk_ray_model.c @@ -337,6 +337,15 @@ static void sRGBtoLinearVec4(const vec4_t in, vec4_t out) { out[3] = in[3]; } +static void sRGBAtoLinearVec4(const vec4_t in, vec4_t out) { + out[0] = sRGBtoLinearScalar(in[0]); + out[1] = sRGBtoLinearScalar(in[1]); + out[2] = sRGBtoLinearScalar(in[2]); + + // α also needs to be linearized. + out[3] = sRGBtoLinearScalar(in[3]); +} + void RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ) { if (!model || !model->blas) return; @@ -476,7 +485,7 @@ void RT_FrameAddOnce( rt_frame_add_once_t args ) { break; } - Vector4Copy(*args.color, dyn->colors[dyn->geometries_count]); + sRGBAtoLinearVec4(*args.color_srgb, dyn->colors[dyn->geometries_count]); dyn->geometries[dyn->geometries_count++] = args.geometries[i]; } } diff --git a/ref/vk/vk_render.c b/ref/vk/vk_render.c index 722ca0ed..16acc3fa 100644 --- a/ref/vk/vk_render.c +++ b/ref/vk/vk_render.c @@ -844,7 +844,7 @@ void R_RenderDrawOnce(r_draw_once_t args) { RT_FrameAddOnce((rt_frame_add_once_t){ .debug_name = args.name, .geometries = &geometry, - .color = args.color, + .color_srgb = args.color, .geometries_count = 1, .render_type = args.render_type, }); diff --git a/ref/vk/vk_rtx.h b/ref/vk/vk_rtx.h index 48c17561..9aa9cee0 100644 --- a/ref/vk/vk_rtx.h +++ b/ref/vk/vk_rtx.h @@ -78,7 +78,7 @@ void RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ); typedef struct { const char *debug_name; const struct vk_render_geometry_s *geometries; - const vec4_t *color; + const vec4_t *color_srgb; int geometries_count; int render_type; } rt_frame_add_once_t; From 1028564eec60ca8fe8327c5ede38b1ab5c4eddf8 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 28 Dec 2023 12:09:04 -0500 Subject: [PATCH 5/6] vk: rt: enable soft alpha depth globally Fixes #722 --- ref/vk/TODO.md | 2 +- ref/vk/shaders/trace_simple_blending.glsl | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ref/vk/TODO.md b/ref/vk/TODO.md index ab995154..cf41691e 100644 --- a/ref/vk/TODO.md +++ b/ref/vk/TODO.md @@ -1,6 +1,6 @@ # 2023-12-28 E353 - [x] track color spaces when passing colors into shaders -- [ ] validation failure at startup, #723 +- [ ] validation failure at startup, #723 -- seems like memory corruption Longer-term agenda for current season: - [ ] Better PBR math, e.g.: diff --git a/ref/vk/shaders/trace_simple_blending.glsl b/ref/vk/shaders/trace_simple_blending.glsl index 8d994e6a..7ccb72b6 100644 --- a/ref/vk/shaders/trace_simple_blending.glsl +++ b/ref/vk/shaders/trace_simple_blending.glsl @@ -32,8 +32,14 @@ void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout const Kusok kusok = getKusok(geom.kusok_index); const float hit_t = rayQueryGetIntersectionTEXT(rq, false); const float overshoot = hit_t - L; + +// Use soft alpha depth effect globally, not only for glow +#define GLOBAL_SOFT_DEPTH +#ifndef GLOBAL_SOFT_DEPTH if (overshoot > 0. && model.mode != MATERIAL_MODE_BLEND_GLOW) continue; +#endif + //#define DEBUG_BLEND_MODES #ifdef DEBUG_BLEND_MODES @@ -55,10 +61,17 @@ void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout float alpha = mm_color.a * texture_color.a * geom.vertex_color.a; vec3 color = mm_color.rgb * texture_color.rgb * geom.vertex_color.rgb * alpha; +#ifdef GLOBAL_SOFT_DEPTH + const float overshoot_factor = smoothstep(glow_soft_overshoot, 0., overshoot); + color *= overshoot_factor; +#endif + if (model.mode == MATERIAL_MODE_BLEND_GLOW) { // Glow is additive + small overshoot +#ifndef GLOBAL_SOFT_DEPTH const float overshoot_factor = smoothstep(glow_soft_overshoot, 0., overshoot); color *= overshoot_factor; +#endif alpha = 0.; } else if (model.mode == MATERIAL_MODE_BLEND_ADD) { // Additive doesn't attenuate what's behind From 3bc293d8aa053436b8a2ac7eec1d71a6fbc55f5a Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 29 Dec 2023 12:04:38 -0500 Subject: [PATCH 6/6] =?UTF-8?q?vk:=20rt:=20compute=20legacy=20blending=20i?= =?UTF-8?q?n=20sRGB-=CE=B3=20colorspace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes all blending look very close to the original, and fixes a whole class of blending-looking-wrong issues. Fixes #668 --- ref/vk/NOTES.md | 38 +++++++++++++++ ref/vk/TODO.md | 9 ++-- ref/vk/shaders/bounce.comp | 5 +- ref/vk/shaders/denoiser.comp | 23 +++++---- ref/vk/shaders/ray_primary.comp | 4 +- ref/vk/shaders/rt_geometry.glsl | 11 ++++- ref/vk/shaders/trace_simple_blending.glsl | 58 ++++++++++++----------- ref/vk/vk_ray_model.c | 35 ++++++++++++-- 8 files changed, 132 insertions(+), 51 deletions(-) diff --git a/ref/vk/NOTES.md b/ref/vk/NOTES.md index 9d8e8c4f..6c77c109 100644 --- a/ref/vk/NOTES.md +++ b/ref/vk/NOTES.md @@ -1116,3 +1116,41 @@ There's nothing we can to do `a` only that would make it fake the "original" mix As usual -- original sRGB-specialized game art is painfully incompatible with modern linear PBR. The best way to address it (hopefully w/o breaking too much linear rendering math) remains to be discovered. + +# 2023-12-29 E354 +## Sprite animation lerping woes +Problem: converting alpha from sRGB to linear fixes various blending glitches, but makes animation blink. + +Possible approaches: +1. Original math: pass and compute colors and alphas for simple blending in the original (sRGB-γ) colorspace. PBR-incorrect, but should give the original look. +Pro: +- original look +- should solve a whole class of issues. +- Relatively separate from physically-correct math, doesn't interfere that much. + Except for background + emissive part. +- Individual PRB-ized parts of blending could be extracted out from legacy mode gradually. +Cons: +- special legacy blending code. +- Passing these things around is obnoxious: needs lots of special code for model passing. +- Large amount of work. + +Possible implementation plan: +- `vk_ray_model.c`: sRGB-to-linear colorspace conversion should be made based on `material_mode`: + do not convert for legacy blending modes + - what to do with `mat->base_color`, which is assumed linear? Leaving it as-is for now. +- sRGB-γ-ize linear texture color (still a bit different from legacy. alt: specifically for sprites and beams textures mark them as UNORM) +- keep/lerp vertex colors in sRGB space + +2. Special code for sprite lerping: add second texture channel, add lerp parameter, etc. +Pro: should be relatively easy to do. +Cons: Fragile special code for special case. + +3. Track alpha channel with animation lerping in mind: only linearize it for no-animation case. +Pro: no additional parameters to pass to shaders. +Cons: math might not converge on a good solution. + +4. Generate intermediate textures. +Pro: no special code for shaders/model passing. +Cons: ridiculous texture explosion + +5. Hand-patch things that look weird. E.g. for known sprite/beam textures specify how their alphas should be mapped. diff --git a/ref/vk/TODO.md b/ref/vk/TODO.md index cf41691e..22a19c8f 100644 --- a/ref/vk/TODO.md +++ b/ref/vk/TODO.md @@ -1,6 +1,10 @@ +# 2023-12-29 E354 +- [x] Figure out why additive transparency differs visibly from raster +- [x] Implement special legacy-blending in sRGB-γ colorspace + # 2023-12-28 E353 - [x] track color spaces when passing colors into shaders -- [ ] validation failure at startup, #723 -- seems like memory corruption +- [-] validation failure at startup, #723 -- seems like memory corruption Longer-term agenda for current season: - [ ] Better PBR math, e.g.: @@ -9,13 +13,12 @@ Longer-term agenda for current season: - [ ] Just make sure that all the BRDF math is correct - [ ] Transparency/translucency: - [ ] Proper material mode for translucency, with reflections, refraction (index), fresnel, etc. - - [ ] Figure out why additive transparency differs visibly from raster - [ ] Extract and specialize effects, e.g. - [ ] Rays -> volumetrics - [ ] Glow -> bloom - [ ] Smoke -> volumetrics - [ ] Sprites/portals -> emissive volumetrics - - [ ] Holo models -> emissive additive + - [x] Holo models -> emissive additive - [ ] Some additive -> translucent - [ ] what else - [ ] Render-graph-ish approach to resources. diff --git a/ref/vk/shaders/bounce.comp b/ref/vk/shaders/bounce.comp index 7cbf9118..b7996b5c 100644 --- a/ref/vk/shaders/bounce.comp +++ b/ref/vk/shaders/bounce.comp @@ -178,9 +178,8 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula vec3 background = payload.base_color_a.rgb * ldiffuse; background += lspecular * mix(vec3(1.), payload.base_color_a.rgb, hit_material.metalness); - vec3 emissive = vec3(0.); - traceSimpleBlending(pos, bounce_direction, payload.hit_t.w, emissive, background); - lighting = emissive + background; + const vec4 blend = traceLegacyBlending(pos, bounce_direction, payload.hit_t.w); + lighting = SRGBtoLINEAR(blend.rgb) + background * blend.a; } else { lighting = texture(skybox, bounce_direction).rgb * ubo.ubo.skybox_exposure; //payload.emissive.rgb = texture(skybox, bounce_direction).rgb * ubo.ubo.skybox_exposure; diff --git a/ref/vk/shaders/denoiser.comp b/ref/vk/shaders/denoiser.comp index 554402e3..03e863f7 100644 --- a/ref/vk/shaders/denoiser.comp +++ b/ref/vk/shaders/denoiser.comp @@ -42,7 +42,9 @@ layout(set = 0, binding = 18) uniform sampler3D blue_noise_texture; #include "bluenoise.glsl" #endif -//layout(set = 0, binding = 19) uniform sampler2D textures[MAX_TEXTURES]; +layout(set = 0, binding = 19, rgba16f) uniform readonly image2D legacy_blend; + +//layout(set = 0, binding = 20) uniform sampler2D textures[MAX_TEXTURES]; const int INDIRECT_SCALE = 2; @@ -306,18 +308,19 @@ void main() { colour = diffuse + specular; } + const vec4 legacy_blend = imageLoad(legacy_blend, pix); + + colour += imageLoad(emissive, pix).rgb; + // Revealage. TODO: which colorspace? + colour *= legacy_blend.a; + + colour = LINEARtoSRGB(colour); + // See issue https://github.com/w23/xash3d-fwgs/issues/668, map test_blendmode_additive_alpha. -// This macro enabled adding emissive to the final color in the *incorrect* sRGB-γ space. But it makes +// Adding emissive_blend to the final color in the *incorrect* sRGB-γ space. It makes // it look much more like the original. Adding emissive in the *correct* linear space differs // from the original a lot, and looks perceptively worse. -#define ADD_EMISSIVE_IN_GAMMA_SPACE -#ifndef ADD_EMISSIVE_IN_GAMMA_SPACE - // Physically correct, but looks dull - colour += imageLoad(emissive, pix).rgb; - colour = LINEARtoSRGB(colour); -#else // INCORRECT - colour = LINEARtoSRGB(colour) + LINEARtoSRGB(imageLoad(emissive, pix).rgb); -#endif + colour += legacy_blend.rgb; imageStore(out_dest, pix, vec4(colour, 0./*unused*/)); } diff --git a/ref/vk/shaders/ray_primary.comp b/ref/vk/shaders/ray_primary.comp index 88f56809..14173c8f 100644 --- a/ref/vk/shaders/ray_primary.comp +++ b/ref/vk/shaders/ray_primary.comp @@ -23,6 +23,7 @@ layout(set = 0, binding = 12, rgba16f) uniform writeonly image2D out_normals_gs; layout(set = 0, binding = 13, rgba8) uniform writeonly image2D out_material_rmxx; layout(set = 0, binding = 14, rgba16f) uniform writeonly image2D out_emissive; layout(set = 0, binding = 15, rgba32f) uniform writeonly image2D out_geometry_prev_position; +layout(set = 0, binding = 16, rgba16f) uniform writeonly image2D out_legacy_blend; layout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers; layout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki; @@ -118,7 +119,8 @@ void main() { payload.emissive.rgb = texture(skybox, ray.direction).rgb * ubo.ubo.skybox_exposure; } - traceSimpleBlending(ray.origin, ray.direction, L, payload.emissive.rgb, payload.base_color_a.rgb); + const vec4 blend = traceLegacyBlending(ray.origin, ray.direction, L); + imageStore(out_legacy_blend, pix, blend); imageStore(out_position_t, pix, payload.hit_t); imageStore(out_base_color_a, pix, LINEARtoSRGB(payload.base_color_a)); diff --git a/ref/vk/shaders/rt_geometry.glsl b/ref/vk/shaders/rt_geometry.glsl index 2038a349..e13cd64c 100644 --- a/ref/vk/shaders/rt_geometry.glsl +++ b/ref/vk/shaders/rt_geometry.glsl @@ -134,7 +134,7 @@ Geometry readHitGeometry(vec2 bary, float ray_cone_width) { struct MiniGeometry { vec2 uv; uint kusok_index; - vec4 vertex_color; + vec4 vertex_color_srgb; }; MiniGeometry readCandidateMiniGeometry(rayQueryEXT rq) { @@ -156,16 +156,23 @@ MiniGeometry readCandidateMiniGeometry(rayQueryEXT rq) { const vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, false); const vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary); + /* const vec4 colors[3] = { SRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi1).color)), SRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi2).color)), SRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi3).color)), }; + */ + const vec4 colors_srgb[3] = { + unpackUnorm4x8(GET_VERTEX(vi1).color), + unpackUnorm4x8(GET_VERTEX(vi2).color), + unpackUnorm4x8(GET_VERTEX(vi3).color), + }; MiniGeometry ret; ret.uv = uv; ret.kusok_index = kusok_index; - ret.vertex_color = baryMix(colors[0], colors[1], colors[2], bary); + ret.vertex_color_srgb = baryMix(colors_srgb[0], colors_srgb[1], colors_srgb[2], bary); return ret; } #endif // #ifdef RAY_QUERY diff --git a/ref/vk/shaders/trace_simple_blending.glsl b/ref/vk/shaders/trace_simple_blending.glsl index 7ccb72b6..85146799 100644 --- a/ref/vk/shaders/trace_simple_blending.glsl +++ b/ref/vk/shaders/trace_simple_blending.glsl @@ -2,8 +2,11 @@ #define TRACE_SIMPLE_BLENDING_GLSL_INCLUDED // Traces geometry with simple blending. Simple means that it's only additive or mix/coverage, and it doesn't participate in lighting, and it doesn't reflect/refract rays. -void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout vec3 background) { +// Done in sRGB-γ space for legacy-look reasons. +// Returns vec4(emissive_srgb.rgb, revealage) +vec4 traceLegacyBlending(vec3 pos, vec3 dir, float L) { const float glow_soft_overshoot = 16.; + vec3 emissive = vec3(0.); // TODO probably a better way would be to sort only MIX entries. // ADD/GLOW are order-independent relative to each other, but not to MIX @@ -45,7 +48,7 @@ void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout #ifdef DEBUG_BLEND_MODES if (model.mode == MATERIAL_MODE_BLEND_GLOW) { emissive += vec3(1., 0., 0.); - //ret += color * smoothstep(glow_soft_overshoot, 0., overshoot); + //emissive += color * smoothstep(glow_soft_overshoot, 0., overshoot); } else if (model.mode == MATERIAL_MODE_BLEND_ADD) { emissive += vec3(0., 1., 0.); } else if (model.mode == MATERIAL_MODE_BLEND_MIX) { @@ -56,10 +59,12 @@ void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout emissive += vec3(1., 1., 1.); } #else - const vec4 texture_color = texture(textures[nonuniformEXT(kusok.material.tex_base_color)], geom.uv); + // Note that simple blending is legacy blending really. + // It is done in sRGB-γ space for correct legacy-look reasons. + const vec4 texture_color = LINEARtoSRGB(texture(textures[nonuniformEXT(kusok.material.tex_base_color)], geom.uv)); const vec4 mm_color = model.color * kusok.material.base_color; - float alpha = mm_color.a * texture_color.a * geom.vertex_color.a; - vec3 color = mm_color.rgb * texture_color.rgb * geom.vertex_color.rgb * alpha; + float alpha = mm_color.a * texture_color.a * geom.vertex_color_srgb.a; + vec3 color = mm_color.rgb * texture_color.rgb * geom.vertex_color_srgb.rgb * alpha; #ifdef GLOBAL_SOFT_DEPTH const float overshoot_factor = smoothstep(glow_soft_overshoot, 0., overshoot); @@ -98,34 +103,31 @@ void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout #endif // !DEBUG_BLEND_MODES } - if (entries_count == 0) - return; - - // Tyno O(N^2) sort - for (uint i = 0; i < entries_count; ++i) { - uint min_i = i; - for (uint j = i+1; j < entries_count; ++j) { - if (entries[min_i].depth > entries[j].depth) { - min_i = j; + float revealage = 1.; + if (entries_count > 0) { + // Tyno O(N^2) sort + for (uint i = 0; i < entries_count; ++i) { + uint min_i = i; + for (uint j = i+1; j < entries_count; ++j) { + if (entries[min_i].depth > entries[j].depth) { + min_i = j; + } + } + if (min_i != i) { + BlendEntry tmp = entries[min_i]; + entries[min_i] = entries[i]; + entries[i] = tmp; } } - if (min_i != i) { - BlendEntry tmp = entries[min_i]; - entries[min_i] = entries[i]; - entries[i] = tmp; + + // Composite everything in the right order + for (uint i = 0; i < entries_count; ++i) { + emissive += entries[i].add * revealage; + revealage *= 1. - entries[i].blend; } } - // Composite everything in the right order - float revealage = 1.; - vec3 add = vec3(0.); - for (uint i = 0; i < entries_count; ++i) { - add += entries[i].add * revealage; - revealage *= 1. - entries[i].blend; - } - - emissive = emissive * revealage + add; - background *= revealage; + return vec4(emissive, revealage); } #endif //ifndef TRACE_SIMPLE_BLENDING_GLSL_INCLUDED diff --git a/ref/vk/vk_ray_model.c b/ref/vk/vk_ray_model.c index 163abcd7..9977b3f1 100644 --- a/ref/vk/vk_ray_model.c +++ b/ref/vk/vk_ray_model.c @@ -323,6 +323,17 @@ rt_draw_instance_t *getDrawInstance(void) { return g_ray_model_state.frame.instances + (g_ray_model_state.frame.instances_count++); } +static qboolean isLegacyBlendingMode(int material_mode) { + switch (material_mode) { + case MATERIAL_MODE_BLEND_ADD: + case MATERIAL_MODE_BLEND_MIX: + case MATERIAL_MODE_BLEND_GLOW: + return true; + default: + return false; + } +} + static float sRGBtoLinearScalar(const float sRGB) { // IEC 61966-2-1:1999 const float linearLow = sRGB / 12.92f; @@ -334,17 +345,23 @@ static void sRGBtoLinearVec4(const vec4_t in, vec4_t out) { out[0] = sRGBtoLinearScalar(in[0]); out[1] = sRGBtoLinearScalar(in[1]); out[2] = sRGBtoLinearScalar(in[2]); + + // Historically: sprite animation lerping is linear + // To-linear conversion should not be done on anything with blending, therefore + // it's irrelevant really. out[3] = in[3]; } +/* static void sRGBAtoLinearVec4(const vec4_t in, vec4_t out) { out[0] = sRGBtoLinearScalar(in[0]); out[1] = sRGBtoLinearScalar(in[1]); out[2] = sRGBtoLinearScalar(in[2]); - // α also needs to be linearized. + // α also needs to be linearized for tau-cannon hit position sprite to look okay out[3] = sRGBtoLinearScalar(in[3]); } +*/ void RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ) { if (!model || !model->blas) @@ -371,7 +388,13 @@ void RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ) { draw_instance->kusochki_offset = kusochki_offset; draw_instance->material_mode = args.material_mode; draw_instance->material_flags = args.material_flags; - sRGBtoLinearVec4(*args.color_srgb, draw_instance->color); + + // Legacy blending is done in sRGB-γ space + if (isLegacyBlendingMode(args.material_mode)) + Vector4Copy(*args.color_srgb, draw_instance->color); + else + sRGBtoLinearVec4(*args.color_srgb, draw_instance->color); + Matrix3x4_Copy(draw_instance->transform_row, args.transform); Matrix4x4_Copy(draw_instance->prev_transform_row, args.prev_transform); } @@ -445,7 +468,6 @@ void RT_DynamicModelProcessFrame(void) { goto tail; } - // FIXME override color if (!RT_KusochkiUpload(kusochki_offset, dyn->geometries, dyn->geometries_count, NULL, dyn->colors)) { gEngine.Con_Printf(S_ERROR "Couldn't build blas for %d geoms of %s, skipping\n", dyn->geometries_count, group_names[i]); goto tail; @@ -485,7 +507,12 @@ void RT_FrameAddOnce( rt_frame_add_once_t args ) { break; } - sRGBAtoLinearVec4(*args.color_srgb, dyn->colors[dyn->geometries_count]); + // Legacy blending is done in sRGB-γ space + if (isLegacyBlendingMode(material_mode)) + Vector4Copy(*args.color_srgb, dyn->colors[dyn->geometries_count]); + else + sRGBtoLinearVec4(*args.color_srgb, dyn->colors[dyn->geometries_count]); + dyn->geometries[dyn->geometries_count++] = args.geometries[i]; } }