From fac6acf7daf3efd872814af34a8faac11a0a5c9d Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 00:50:59 +0100 Subject: Skybox image: match HDR sky colour decode The HDR "sky" shader samples the compressed texture as rgb *= alpha, multiplies by 8, then converts linear->sRGB for display. Feeding it the raw sRGB image produced the blown-out / oversaturated sky. When the map uses the HDR sky shader, pre-convert the image to linear and set alpha to 1/8 so the 8x scale cancels and the final linear->sRGB conversion restores the original colours. For LDR (UnlitGeneric) materials, create the texture with TEXTUREFLAGS_SRGB so the normal sRGB sampling is used. The first sky material's shader name is logged for confirmation. --- src/world.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/world.cpp b/src/world.cpp index eac31d9..ddd047a 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -18,6 +18,7 @@ #include "hl2h.h" #include #include +#include #include #include #include @@ -386,7 +387,7 @@ int paeth(int a, int b, int c) { } bool load_png_rgba(const char *path, std::vector &pixels, - int &out_w, int &out_h) { + int &out_w, int &out_h, bool hdr_compensation) { FILE *f = fopen(path, "rb"); if (!f) { logf("skybox: cannot open %s", path); @@ -609,6 +610,24 @@ bool load_png_rgba(const char *path, std::vector &pixels, } } + // HDR sky materials use the "sky" shader. It samples the compressed HDR + // texture as rgb *= alpha, multiplies by 8, then converts linear->sRGB for + // display. Cancel the 8x with alpha = 1/8 and feed linear values so the + // final conversion restores the original sRGB image. + if (hdr_compensation) { + for (size_t i = 0; i + 3 < pixels.size(); i += 4) { + float rgb[3]; + for (int c = 0; c < 3; c++) { + float v = (float)pixels[i + (size_t)c] / 255.0f; + rgb[c] = (v <= 0.04045f) ? v / 12.92f + : powf((v + 0.055f) / 1.055f, 2.4f); + pixels[i + (size_t)c] = (unsigned char)(rgb[c] * 255.0f + 0.5f); + } + pixels[i + 3] = 32; // 1/8 * 255 + } + logf("skybox: HDR sky shader, using linear colours with 1/8 alpha"); + } + out_w = (int)w; out_h = (int)h; return true; @@ -826,6 +845,39 @@ void restore_sky_texture() { g_sky_applied = false; } +bool sky_uses_hdr_shader() { + void *ms = material_system(); + if (!ms) + return false; + auto invalid = vfunc(ms, MATSYS_InvalidMaterial); + auto first = vfunc(ms, MATSYS_FirstMaterial); + auto next = vfunc(ms, MATSYS_NextMaterial); + auto get = vfunc(ms, MATSYS_GetMaterial); + auto count = vfunc(ms, MATSYS_GetNumMaterials); + int total = count(ms); + if (total <= 0 || total > 200000) + return false; + using ShaderFn = const char *(*)(void *); + unsigned short inv = invalid(ms); + int seen = 0; + for (unsigned short h = first(ms); h != inv; h = next(ms, h)) { + if (++seen > total + 16) + break; + void *mat = get(ms, h); + if (!mat) + continue; + const char *name = vfunc(mat, MAT_GetName)(mat); + const char *group = + vfunc(mat, MAT_GetTextureGroupName)(mat); + if (!is_sky_material(name, group)) + continue; + const char *shader = vfunc(mat, 49)(mat); + if (shader && strcmp(shader, "sky") == 0) + return true; + } + return false; +} + void load_sky_texture() { g_sky_tex_tried = true; void *ms = material_system(); @@ -836,7 +888,8 @@ void load_sky_texture() { std::vector pixels; int w = 0, h = 0; const char *path = config_file_cstr("skybox.png"); - if (!load_png_rgba(path, pixels, w, h)) { + bool hdr = sky_uses_hdr_shader(); + if (!load_png_rgba(path, pixels, w, h, hdr)) { if (!g_sky_fail_logged) { g_sky_fail_logged = true; toastf(TOAST_ERROR, "Skybox image missing or unreadable: %s", path); @@ -851,9 +904,12 @@ void load_sky_texture() { // is called on Download() and again on device restore. using CreateProcFn = void *(*)(void *, const char *, const char *, int, int, int, int); - g_sky_tex = vfunc(ms, 81)( - ms, "hl2hake/skybox", GROUP_SKYBOX, w, h, 0 /* RGBA8888 */, - 0x10C /* CLAMPS|CLAMPT|NOMIP */); + // 0x40 = TEXTUREFLAGS_SRGB: the LDR sky path decodes sRGB from the texture + // flags, the HDR path forces sRGB reads off and does its own conversion. + int tex_flags = 0x10C /* CLAMPS|CLAMPT|NOMIP */ | (hdr ? 0 : 0x40); + g_sky_tex = vfunc(ms, 81)(ms, "hl2hake/skybox", + GROUP_SKYBOX, w, h, 0 /*RGBA8888*/, + tex_flags); if (!g_sky_tex) { logf("skybox: CreateProceduralTexture failed for %dx%d", w, h); return; -- cgit v1.2.3