diff --git a/doc/client.asciidoc b/doc/client.asciidoc index b237c02d..549f415e 100644 --- a/doc/client.asciidoc +++ b/doc/client.asciidoc @@ -750,10 +750,41 @@ r_bloomBlurRadius:: defined in pixels at 1080p and scale with the framebuffer size. Default value is 12. -r_bloomThreshold:: +r_bloomBlurScale:: + Multiplier applied to the configured blur radius after it is scaled to the + current framebuffer size. Values greater than 1.0 widen the blur while + values below 1.0 tighten it. Default value is 1.0. + +r_bloomBlurFalloff:: + Controls how quickly the gaussian blur weights fall off from the center of + the kernel. Lower values spread bloom over a larger area; higher values + keep highlights tighter. Default value is 0.75. + +r_bloomBrightThreshold:: Minimum luminance for a fragment to contribute to bloom. Fragments below this threshold are ignored during the bright-pass filter. Default value is - 0.8. + 0.8. (The legacy ``r_bloomThreshold`` alias is kept for compatibility.) + +r_bloomIntensity:: + Scales the intensity of the bloom contribution before it is blended back + into the scene. Default value is 1.0. + +r_bloomPasses:: + Number of blur iterations applied to the bloom buffer. Higher values + produce a softer glow at the cost of performance. Default value is 2. + +r_bloomSaturation:: + Adjusts saturation of the bloom highlights prior to compositing. Default + value is 1.0. + +r_bloomSceneSaturation:: + Adjusts saturation of the final scene after bloom and post-processing are + applied. Default value is 1.0. + +r_colorCorrection:: + Strength of the ACES-inspired color correction applied during the final + post-processing pass. Set to 0 to disable the correction. Default value is + 0. gl_flarespeed:: Specifies flare fading effect speed. Default value is 8. Set this to 0 diff --git a/src/refresh/gl.hpp b/src/refresh/gl.hpp index 4fdccf0a..9638fb38 100644 --- a/src/refresh/gl.hpp +++ b/src/refresh/gl.hpp @@ -132,6 +132,7 @@ typedef struct { GLsync sync; float entity_modulate; float bloom_sigma; + float bloom_falloff; uint32_t inverse_intensity_33; uint32_t inverse_intensity_66; uint32_t inverse_intensity_100; @@ -399,7 +400,14 @@ extern cvar_t *gl_damageblend_frac; extern cvar_t *r_skipUnderWaterFX; extern cvar_t *r_bloom; extern cvar_t *r_bloomBlurRadius; -extern cvar_t *r_bloomThreshold; +extern cvar_t *r_bloomBlurScale; +extern cvar_t *r_bloomBlurFalloff; +extern cvar_t *r_bloomBrightThreshold; +extern cvar_t *r_bloomIntensity; +extern cvar_t *r_bloomPasses; +extern cvar_t *r_bloomSaturation; +extern cvar_t *r_bloomSceneSaturation; +extern cvar_t *r_colorCorrection; extern cvar_t *gl_dof; // development variables @@ -713,6 +721,7 @@ void GL_LoadWorld(const char *name); #define GLS_BOKEH_DOWNSAMPLE BIT_ULL(38) #define GLS_BOKEH_GATHER BIT_ULL(39) #define GLS_BOKEH_COMBINE BIT_ULL(40) +#define GLS_COLOR_CORRECTION BIT_ULL(41) #define GLS_BLEND_MASK (GLS_BLEND_BLEND | GLS_BLEND_ADD | GLS_BLEND_MODULATE) #define GLS_BOKEH_MASK (GLS_BOKEH_COC | GLS_BOKEH_INITIAL | GLS_BOKEH_DOWNSAMPLE | GLS_BOKEH_GATHER | GLS_BOKEH_COMBINE) @@ -726,9 +735,10 @@ void GL_LoadWorld(const char *name); #define GLS_SHADER_MASK (GLS_ALPHATEST_ENABLE | GLS_TEXTURE_REPLACE | GLS_SCROLL_ENABLE | \ GLS_LIGHTMAP_ENABLE | GLS_WARP_ENABLE | GLS_INTENSITY_ENABLE | \ GLS_GLOWMAP_ENABLE | GLS_SKY_MASK | GLS_DEFAULT_FLARE | GLS_MESH_MASK | \ - GLS_FOG_MASK | GLS_BLOOM_MASK | GLS_BLUR_MASK | GLS_DYNAMIC_LIGHTS | GLS_BOKEH_MASK) + GLS_FOG_MASK | GLS_BLOOM_MASK | GLS_BLUR_MASK | GLS_DYNAMIC_LIGHTS | GLS_BOKEH_MASK | GLS_COLOR_CORRECTION) #define GLS_UNIFORM_MASK (GLS_WARP_ENABLE | GLS_LIGHTMAP_ENABLE | GLS_INTENSITY_ENABLE | \ - GLS_SKY_MASK | GLS_FOG_MASK | GLS_BLOOM_BRIGHTPASS | GLS_BLUR_MASK | GLS_DYNAMIC_LIGHTS | GLS_BOKEH_MASK) + GLS_SKY_MASK | GLS_FOG_MASK | GLS_BLOOM_BRIGHTPASS | GLS_BLOOM_OUTPUT | \ + GLS_BLUR_MASK | GLS_DYNAMIC_LIGHTS | GLS_BOKEH_MASK | GLS_COLOR_CORRECTION) #define GLS_SCROLL_MASK (GLS_SCROLL_ENABLE | GLS_SCROLL_X | GLS_SCROLL_Y | GLS_SCROLL_FLIP | GLS_SCROLL_SLOW) typedef enum { @@ -829,6 +839,8 @@ typedef struct { vec4_t dof_params; vec4_t dof_screen; vec4_t dof_depth; + vec4_t bloom_params; + vec4_t bloom_color; vec4_t vieworg; } glUniformBlock_t; diff --git a/src/refresh/main.cpp b/src/refresh/main.cpp index dd15182c..349ace95 100644 --- a/src/refresh/main.cpp +++ b/src/refresh/main.cpp @@ -66,7 +66,41 @@ cvar_t *r_skipUnderWaterFX; cvar_t *r_enablefog; cvar_t *r_bloom; cvar_t *r_bloomBlurRadius; -cvar_t *r_bloomThreshold; +cvar_t *r_bloomBlurScale; +cvar_t *r_bloomBlurFalloff; +cvar_t *r_bloomBrightThreshold; +cvar_t *r_bloomIntensity; +cvar_t *r_bloomPasses; +cvar_t *r_bloomSaturation; +cvar_t *r_bloomSceneSaturation; +cvar_t *r_colorCorrection; + +static cvar_t *r_bloomThresholdLegacy; +static bool bloom_threshold_sync = false; + +static void r_bloom_threshold_primary_changed(cvar_t *self) +{ + (void)self; + + if (!r_bloomBrightThreshold || bloom_threshold_sync) + return; + + if (r_bloomThresholdLegacy) { + bloom_threshold_sync = true; + Cvar_SetValue(r_bloomThresholdLegacy, r_bloomBrightThreshold->value, FROM_CODE); + bloom_threshold_sync = false; + } +} + +static void r_bloom_threshold_alias_changed(cvar_t *self) +{ + if (!self || !r_bloomBrightThreshold || bloom_threshold_sync) + return; + + bloom_threshold_sync = true; + Cvar_SetValue(r_bloomBrightThreshold, self->value, FROM_CODE); + bloom_threshold_sync = false; +} cvar_t *gl_dof; cvar_t *gl_swapinterval; @@ -868,6 +902,10 @@ static void GL_DrawDepthOfField(pp_flags_t flags) glStateBits_t bits = GLS_DEFAULT; if (waterwarp) bits |= GLS_WARP_ENABLE; + if (R_ColorCorrectionActive()) { + bits |= GLS_COLOR_CORRECTION; + R_SetPostProcessUniforms(0.0f, 0.0f); + } GL_ForceTexture(TMU_TEXTURE, TEXNUM_PP_DOF_RESULT); @@ -878,12 +916,14 @@ static void GL_DrawDepthOfField(pp_flags_t flags) static int32_t r_skipUnderWaterFX_modified = 0; static int32_t r_bloom_modified = 0; static int32_t gl_dof_modified = 0; +static int32_t r_colorCorrection_modified = 0; static pp_flags_t GL_BindFramebuffer(void) { pp_flags_t flags = PP_NONE; bool resized = false; const bool dof_active = gl_dof->integer && glr.fd.depth_of_field; + const bool colorCorrection = R_ColorCorrectionActive(); if (!gl_static.use_shaders) return PP_NONE; @@ -891,7 +931,7 @@ static pp_flags_t GL_BindFramebuffer(void) if ((glr.fd.rdflags & RDF_UNDERWATER) && !r_skipUnderWaterFX->integer) flags |= PP_WATERWARP; - if (!(glr.fd.rdflags & RDF_NOWORLDMODEL) && r_bloom->integer) + if (!(glr.fd.rdflags & RDF_NOWORLDMODEL) && (r_bloom->integer || colorCorrection)) flags |= PP_BLOOM; if (dof_active) @@ -902,13 +942,16 @@ static pp_flags_t GL_BindFramebuffer(void) if (resized || r_skipUnderWaterFX->modified_count != r_skipUnderWaterFX_modified || r_bloom->modified_count != r_bloom_modified || - gl_dof->modified_count != gl_dof_modified) { + gl_dof->modified_count != gl_dof_modified || + (r_colorCorrection && r_colorCorrection->modified_count != r_colorCorrection_modified)) { glr.framebuffer_ok = GL_InitFramebuffers(); glr.framebuffer_width = glr.fd.width; glr.framebuffer_height = glr.fd.height; r_skipUnderWaterFX_modified = r_skipUnderWaterFX->modified_count; r_bloom_modified = r_bloom->modified_count; gl_dof_modified = gl_dof->modified_count; + if (r_colorCorrection) + r_colorCorrection_modified = r_colorCorrection->modified_count; if (flags & PP_BLOOM) gl_backend->update_blur(); } @@ -1269,8 +1312,25 @@ static void GL_Register(void) r_skipUnderWaterFX = Cvar_Get("r_skipUnderWaterFX", "0", 0); r_enablefog = Cvar_Get("r_enablefog", "1", 0); r_bloom = Cvar_Get("r_bloom", "1", 0); - r_bloomBlurRadius = Cvar_Get("r_bloomBlurRadius", "2.0", 0); - r_bloomThreshold = Cvar_Get("r_bloomThreshold", "0.1", 0); + r_bloomBlurRadius = Cvar_Get("r_bloomBlurRadius", "12", 0); + r_bloomBlurScale = Cvar_Get("r_bloomBlurScale", "1.0", 0); + r_bloomBlurFalloff = Cvar_Get("r_bloomBlurFalloff", "0.75", 0); + r_bloomBrightThreshold = Cvar_Get("r_bloomBrightThreshold", "0.8", 0); + r_bloomIntensity = Cvar_Get("r_bloomIntensity", "1.0", 0); + r_bloomPasses = Cvar_Get("r_bloomPasses", "2", 0); + r_bloomSaturation = Cvar_Get("r_bloomSaturation", "1.0", 0); + r_bloomSceneSaturation = Cvar_Get("r_bloomSceneSaturation", "1.0", 0); + r_colorCorrection = Cvar_Get("r_colorCorrection", "0", 0); + r_bloomThresholdLegacy = Cvar_Get("r_bloomThreshold", "0.8", 0); + + if (r_bloomBrightThreshold) + r_bloomBrightThreshold->changed = r_bloom_threshold_primary_changed; + if (r_bloomThresholdLegacy) + r_bloomThresholdLegacy->changed = r_bloom_threshold_alias_changed; + r_bloom_threshold_primary_changed(r_bloomBrightThreshold); + + if (r_colorCorrection) + r_colorCorrection_modified = r_colorCorrection->modified_count; gl_dof = Cvar_Get("gl_dof", "1", 0); gl_swapinterval = Cvar_Get("gl_swapinterval", "1", CVAR_ARCHIVE); gl_swapinterval->changed = gl_swapinterval_changed; diff --git a/src/refresh/postprocess/bloom.cpp b/src/refresh/postprocess/bloom.cpp index e7eac3e2..7fc8ee01 100644 --- a/src/refresh/postprocess/bloom.cpp +++ b/src/refresh/postprocess/bloom.cpp @@ -1,6 +1,7 @@ #include "bloom.hpp" #include +#include #include "../qgl.hpp" @@ -8,10 +9,48 @@ BloomEffect g_bloom_effect; namespace { - constexpr GLenum kColorAttachment = GL_COLOR_ATTACHMENT0; +constexpr GLenum kColorAttachment = GL_COLOR_ATTACHMENT0; +constexpr int kMaxBloomPasses = 8; +constexpr float kSceneSaturationEpsilon = 1e-4f; + +inline void SetBlurDirection(float x, float y) +{ + gls.u_block.bloom_params[0] = x; + gls.u_block.bloom_params[1] = y; + gls.u_block_dirty = true; +} + +inline int GetBloomPassCount() +{ + if (!r_bloomPasses) + return 1; + + return std::clamp(r_bloomPasses->integer, 1, kMaxBloomPasses); +} } +void R_SetPostProcessUniforms(float dirX, float dirY) +{ + const float threshold = std::max(r_bloomBrightThreshold ? r_bloomBrightThreshold->value : 0.0f, 0.0f); + const float intensity = std::max(r_bloomIntensity ? r_bloomIntensity->value : 0.0f, 0.0f); + const float bloomSaturation = std::max(r_bloomSaturation ? r_bloomSaturation->value : 0.0f, 0.0f); + const float sceneSaturation = std::max(r_bloomSceneSaturation ? r_bloomSceneSaturation->value : 0.0f, 0.0f); + const float colorStrength = std::max(r_colorCorrection ? r_colorCorrection->value : 0.0f, 0.0f); + + Vector4Set(gls.u_block.bloom_params, dirX, dirY, threshold, intensity); + Vector4Set(gls.u_block.bloom_color, bloomSaturation, sceneSaturation, colorStrength, 0.0f); + gls.u_block_dirty = true; +} + +bool R_ColorCorrectionActive(void) +{ + const float sceneSaturation = r_bloomSceneSaturation ? r_bloomSceneSaturation->value : 1.0f; + const float colorStrength = r_colorCorrection ? r_colorCorrection->value : 0.0f; + + return std::fabs(sceneSaturation - 1.0f) > kSceneSaturationEpsilon || colorStrength > 0.0f; +} + BloomEffect::BloomEffect() noexcept : textures_{}, framebuffers_{}, @@ -158,73 +197,87 @@ extern void GL_PostProcess(glStateBits_t bits, int x, int y, int w, int h); void BloomEffect::render(const BloomRenderContext& ctx) { - if (!initialized_ || downsampleWidth_ <= 0 || downsampleHeight_ <= 0) { - if (ctx.depthOfField && !ctx.showDebug && ctx.runDepthOfField) - ctx.runDepthOfField(); - - GL_Setup2D(); - - glStateBits_t bits = GLS_DEFAULT; - GL_ForceTexture(TMU_TEXTURE, ctx.depthOfField ? ctx.dofTexture : ctx.sceneTexture); - if (!ctx.showDebug && ctx.waterwarp) - bits |= GLS_WARP_ENABLE; - - qglBindFramebuffer(GL_FRAMEBUFFER, 0); - GL_PostProcess(bits, ctx.viewportX, ctx.viewportY, ctx.viewportWidth, ctx.viewportHeight); - return; - } - - qglViewport(0, 0, downsampleWidth_, downsampleHeight_); - GL_Ortho(0, downsampleWidth_, downsampleHeight_, 0, -1, 1); - - const float invW = 1.0f / downsampleWidth_; - const float invH = 1.0f / downsampleHeight_; - - gls.u_block.fog_color[0] = invW; - gls.u_block.fog_color[1] = invH; - gls.u_block.fog_color[2] = 0.0f; - GL_ForceTexture(TMU_TEXTURE, ctx.bloomTexture); - qglBindFramebuffer(GL_FRAMEBUFFER, framebuffers_[DownsampleFbo]); - GL_PostProcess(GLS_BLUR_BOX, 0, 0, downsampleWidth_, downsampleHeight_); - - gls.u_block.fog_color[0] = invW; - gls.u_block.fog_color[1] = invH; - gls.u_block.fog_color[2] = r_bloomThreshold->value; - GL_ForceTexture(TMU_TEXTURE, textures_[Downsample]); - GL_ForceTexture(TMU_LIGHTMAP, ctx.sceneTexture); - qglBindFramebuffer(GL_FRAMEBUFFER, framebuffers_[BrightPassFbo]); - GL_PostProcess(GLS_BLOOM_BRIGHTPASS, 0, 0, downsampleWidth_, downsampleHeight_); - - GLuint currentTexture = textures_[BrightPass]; - for (int axis = 0; axis < 2; ++axis) { - const bool horizontal = axis == 0; - gls.u_block.fog_color[0] = horizontal ? invW : 0.0f; - gls.u_block.fog_color[1] = horizontal ? 0.0f : invH; - GL_ForceTexture(TMU_TEXTURE, currentTexture); - qglBindFramebuffer(GL_FRAMEBUFFER, framebuffers_[BlurFbo0 + axis]); - GL_PostProcess(GLS_BLUR_GAUSS, 0, 0, downsampleWidth_, downsampleHeight_); - currentTexture = textures_[Blur0 + axis]; - } - - const GLuint bloomTexture = currentTexture; - - if (ctx.depthOfField && !ctx.showDebug && ctx.runDepthOfField) - ctx.runDepthOfField(); - - GL_Setup2D(); - - glStateBits_t bits = ctx.showDebug ? GLS_DEFAULT : GLS_BLOOM_OUTPUT; - if (ctx.showDebug) { - GL_ForceTexture(TMU_TEXTURE, bloomTexture); - } - else { - GL_ForceTexture(TMU_TEXTURE, ctx.depthOfField ? ctx.dofTexture : ctx.sceneTexture); - GL_ForceTexture(TMU_LIGHTMAP, bloomTexture); - if (ctx.waterwarp) - bits |= GLS_WARP_ENABLE; - } - - qglBindFramebuffer(GL_FRAMEBUFFER, 0); - GL_PostProcess(bits, ctx.viewportX, ctx.viewportY, ctx.viewportWidth, ctx.viewportHeight); + const bool bloomRequested = r_bloom && r_bloom->integer; + const bool dofActive = ctx.depthOfField && !ctx.showDebug && ctx.runDepthOfField; + const bool applyColorCorrection = !ctx.showDebug && R_ColorCorrectionActive(); + + if (!bloomRequested || !initialized_ || downsampleWidth_ <= 0 || downsampleHeight_ <= 0) { + if (dofActive) + ctx.runDepthOfField(); + + GL_Setup2D(); + + glStateBits_t bits = GLS_DEFAULT; + if (!ctx.showDebug) { + if (ctx.waterwarp) + bits |= GLS_WARP_ENABLE; + if (applyColorCorrection) + bits |= GLS_COLOR_CORRECTION; + R_SetPostProcessUniforms(0.0f, 0.0f); + GL_ForceTexture(TMU_TEXTURE, ctx.depthOfField ? ctx.dofTexture : ctx.sceneTexture); + } else { + GL_ForceTexture(TMU_TEXTURE, ctx.bloomTexture); + } + + qglBindFramebuffer(GL_FRAMEBUFFER, 0); + GL_PostProcess(bits, ctx.viewportX, ctx.viewportY, ctx.viewportWidth, ctx.viewportHeight); + return; + } + + qglViewport(0, 0, downsampleWidth_, downsampleHeight_); + GL_Ortho(0, downsampleWidth_, downsampleHeight_, 0, -1, 1); + + const float invW = 1.0f / downsampleWidth_; + const float invH = 1.0f / downsampleHeight_; + + R_SetPostProcessUniforms(invW, invH); + + GL_ForceTexture(TMU_TEXTURE, ctx.bloomTexture); + qglBindFramebuffer(GL_FRAMEBUFFER, framebuffers_[DownsampleFbo]); + SetBlurDirection(invW, invH); + GL_PostProcess(GLS_BLUR_BOX, 0, 0, downsampleWidth_, downsampleHeight_); + + GL_ForceTexture(TMU_TEXTURE, textures_[Downsample]); + GL_ForceTexture(TMU_LIGHTMAP, ctx.sceneTexture); + qglBindFramebuffer(GL_FRAMEBUFFER, framebuffers_[BrightPassFbo]); + SetBlurDirection(invW, invH); + GL_PostProcess(GLS_BLOOM_BRIGHTPASS, 0, 0, downsampleWidth_, downsampleHeight_); + + GLuint currentTexture = textures_[BrightPass]; + const int passes = GetBloomPassCount(); + for (int pass = 0; pass < passes; ++pass) { + for (int axis = 0; axis < 2; ++axis) { + const bool horizontal = axis == 0; + SetBlurDirection(horizontal ? invW : 0.0f, horizontal ? 0.0f : invH); + GL_ForceTexture(TMU_TEXTURE, currentTexture); + qglBindFramebuffer(GL_FRAMEBUFFER, framebuffers_[BlurFbo0 + axis]); + GL_PostProcess(GLS_BLUR_GAUSS, 0, 0, downsampleWidth_, downsampleHeight_); + currentTexture = textures_[Blur0 + axis]; + } + } + + const GLuint bloomTexture = currentTexture; + + if (dofActive) + ctx.runDepthOfField(); + + GL_Setup2D(); + + glStateBits_t bits = GLS_DEFAULT; + if (ctx.showDebug) { + GL_ForceTexture(TMU_TEXTURE, bloomTexture); + } else { + R_SetPostProcessUniforms(0.0f, 0.0f); + GL_ForceTexture(TMU_TEXTURE, ctx.depthOfField ? ctx.dofTexture : ctx.sceneTexture); + GL_ForceTexture(TMU_LIGHTMAP, bloomTexture); + bits |= GLS_BLOOM_OUTPUT; + if (ctx.waterwarp) + bits |= GLS_WARP_ENABLE; + if (applyColorCorrection) + bits |= GLS_COLOR_CORRECTION; + } + + qglBindFramebuffer(GL_FRAMEBUFFER, 0); + GL_PostProcess(bits, ctx.viewportX, ctx.viewportY, ctx.viewportWidth, ctx.viewportHeight); } diff --git a/src/refresh/postprocess/bloom.hpp b/src/refresh/postprocess/bloom.hpp index db8f3a8c..9ce2a1d5 100644 --- a/src/refresh/postprocess/bloom.hpp +++ b/src/refresh/postprocess/bloom.hpp @@ -63,3 +63,6 @@ class BloomEffect { extern BloomEffect g_bloom_effect; +void R_SetPostProcessUniforms(float dirX, float dirY); +bool R_ColorCorrectionActive(void); + diff --git a/src/refresh/shader.cpp b/src/refresh/shader.cpp index 64d90b21..52d59c21 100644 --- a/src/refresh/shader.cpp +++ b/src/refresh/shader.cpp @@ -119,6 +119,8 @@ static void write_block(sizebuf_t *buf, glStateBits_t bits) vec4 u_dof_params; vec4 u_dof_screen; vec4 u_dof_depth; + vec4 u_bloom_params; + vec4 u_bloom_color; vec4 u_vieworg; ) GLSF("};\n"); @@ -461,6 +463,7 @@ static void write_vertex_shader(sizebuf_t *buf, glStateBits_t bits) static void write_gaussian_blur(sizebuf_t *buf) { float sigma = gl_static.bloom_sigma; + float falloff = max(gl_static.bloom_falloff, 0.0001f); int radius = min(sigma * 2 + 0.5f, MAX_RADIUS); int samples = radius + 1; int raw_samples = (radius * 2) + 1; @@ -475,7 +478,7 @@ static void write_gaussian_blur(sizebuf_t *buf) float sum = 0; for (int i = -radius, j = 0; i <= radius; i++, j++) { - float w = expf(-(i * i) / (sigma * sigma)); + float w = expf(-(i * i) * falloff / (sigma * sigma)); weights[j] = w; sum += w; } @@ -778,9 +781,30 @@ static void write_fragment_shader(sizebuf_t *buf, glStateBits_t bits) else if (bits & GLS_BLUR_BOX) write_box_blur(buf); - if (bits & GLS_BLOOM_BRIGHTPASS) + if (bits & (GLS_BLOOM_BRIGHTPASS | GLS_BLOOM_OUTPUT | GLS_COLOR_CORRECTION)) GLSL(const vec3 bloom_luminance = vec3(0.2125, 0.7154, 0.0721);) + if (bits & GLS_COLOR_CORRECTION) { + GLSL( + vec3 aces_tonemap(vec3 x) { + const mat3 ACESInputMat = mat3( + 0.59719, 0.35458, 0.04823, + 0.07600, 0.90834, 0.01566, + 0.02840, 0.13383, 0.83777); + const mat3 ACESOutputMat = mat3( + 1.60475, -0.53108, -0.07367, + -0.10208, 1.10813, -0.00605, + -0.00327, -0.07276, 1.07602); + x = ACESInputMat * x; + vec3 a = x * (x + vec3(0.0245786)) - vec3(0.000090537); + vec3 b = x * (x * 0.983729 + vec3(0.4329510)) + vec3(0.238081); + x = a / b; + x = ACESOutputMat * x; + return clamp(x, 0.0, 1.0); + } + ) + } + GLSF("void main() {\n"); if (bits & GLS_CLASSIC_SKY) { GLSL( @@ -801,14 +825,14 @@ static void write_fragment_shader(sizebuf_t *buf, glStateBits_t bits) GLSL(tc += w_amp * sin(tc.ts * w_phase + u_time);) if (bits & GLS_BLUR_MASK) - GLSL(vec4 diffuse = blur(u_texture, tc, u_fog_color.xy);) + GLSL(vec4 diffuse = blur(u_texture, tc, u_bloom_params.xy);) else GLSL(vec4 diffuse = texture(u_texture, tc);) if (bits & GLS_BLOOM_BRIGHTPASS) { GLSL(vec4 scene = texture(u_scene, tc);) GLSL(float luminance = dot(scene.rgb, bloom_luminance);) - GLSL(float threshold = u_fog_color.z;) + GLSL(float threshold = u_bloom_params.z;) GLSL(luminance = max(0.0, luminance - threshold);) GLSL(diffuse.rgb *= sign(luminance);) GLSL(diffuse.a = 1.0;) @@ -905,8 +929,24 @@ static void write_fragment_shader(sizebuf_t *buf, glStateBits_t bits) if (bits & GLS_FOG_SKY) GLSL(diffuse.rgb = mix(diffuse.rgb, u_fog_color.rgb, u_fog_sky_factor);) - if (bits & GLS_BLOOM_OUTPUT) - GLSL(diffuse.rgb += texture(u_bloom, tc).rgb;) + if (bits & GLS_BLOOM_OUTPUT) { + GLSL(vec3 bloom = texture(u_bloom, tc).rgb;) + GLSL(float bloomSat = clamp(u_bloom_color.x, 0.0, 8.0);) + GLSL(vec3 bloomLuma = vec3(dot(bloom, bloom_luminance));) + GLSL(bloom = mix(bloomLuma, bloom, bloomSat);) + GLSL(diffuse.rgb += bloom * u_bloom_params.w;) + } + + if (bits & GLS_COLOR_CORRECTION) { + GLSL(float sceneSat = clamp(u_bloom_color.y, 0.0, 8.0);) + GLSL(vec3 sceneLuma = vec3(dot(diffuse.rgb, bloom_luminance));) + GLSL(diffuse.rgb = mix(sceneLuma, diffuse.rgb, sceneSat);) + GLSL(float ccStrength = clamp(u_bloom_color.z, 0.0, 1.0);) + GLSL(if (ccStrength > 0.0) {) + GLSL( vec3 graded = aces_tonemap(diffuse.rgb);) + GLSL( diffuse.rgb = mix(diffuse.rgb, graded, ccStrength);) + GLSL(}) + } if (bits & GLS_BLOOM_GENERATE) GLSL(o_bloom = bloom;) @@ -1279,6 +1319,9 @@ static void shader_setup_2d(void) gls.u_block.w_amp[1] = 0.0025f; gls.u_block.w_phase[0] = M_PIf * 10; gls.u_block.w_phase[1] = M_PIf * 10; + + Vector4Set(gls.u_block.bloom_params, 0.0f, 0.0f, 0.0f, 1.0f); + Vector4Set(gls.u_block.bloom_color, 1.0f, 1.0f, 0.0f, 0.0f); } static void shader_setup_fog(void) @@ -1352,21 +1395,27 @@ static void shader_clear_state(void) static void shader_update_blur(void) { float sigma = 1.0f; + float falloff = 1.0f; if (r_bloom->integer && glr.fd.height > 0) { float base_radius = Cvar_ClampValue(r_bloomBlurRadius, 1, MAX_RADIUS); - float scaled_radius = base_radius * glr.fd.height / 1080.0f; + float scale = r_bloomBlurScale ? r_bloomBlurScale->value : 1.0f; + scale = max(scale, 0.01f); + float scaled_radius = base_radius * scale * glr.fd.height / 1080.0f; if (scaled_radius > 0.0f) { sigma = scaled_radius * 0.5f; if (sigma < 1.0f) sigma = 1.0f; } + falloff = r_bloomBlurFalloff ? r_bloomBlurFalloff->value : 1.0f; + falloff = max(falloff, 0.0001f); } - if (gl_static.bloom_sigma == sigma) + if (gl_static.bloom_sigma == sigma && gl_static.bloom_falloff == falloff) return; gl_static.bloom_sigma = sigma; + gl_static.bloom_falloff = falloff; if (!gl_static.programs) return; @@ -1387,7 +1436,7 @@ static void shader_update_blur(void) shader_use_program(gls.state_bits & GLS_SHADER_MASK); } -static void r_bloom_blur_radius_changed(cvar_t *self) +static void r_bloom_blur_params_changed(cvar_t *self) { (void)self; shader_update_blur(); @@ -1396,7 +1445,11 @@ static void r_bloom_blur_radius_changed(cvar_t *self) static void shader_init(void) { if (r_bloomBlurRadius) - r_bloomBlurRadius->changed = r_bloom_blur_radius_changed; + r_bloomBlurRadius->changed = r_bloom_blur_params_changed; + if (r_bloomBlurScale) + r_bloomBlurScale->changed = r_bloom_blur_params_changed; + if (r_bloomBlurFalloff) + r_bloomBlurFalloff->changed = r_bloom_blur_params_changed; gl_static.programs = HashMap_TagCreate(glStateBits_t, GLuint, HashInt64, NULL, TAG_RENDERER); @@ -1437,6 +1490,10 @@ static void shader_shutdown(void) if (r_bloomBlurRadius) r_bloomBlurRadius->changed = NULL; + if (r_bloomBlurScale) + r_bloomBlurScale->changed = NULL; + if (r_bloomBlurFalloff) + r_bloomBlurFalloff->changed = NULL; if (gl_static.programs) { uint32_t map_size = HashMap_Size(gl_static.programs); diff --git a/src/refresh/texture.cpp b/src/refresh/texture.cpp index 25e583f4..44dca3b4 100644 --- a/src/refresh/texture.cpp +++ b/src/refresh/texture.cpp @@ -1212,8 +1212,9 @@ bool GL_InitFramebuffers(void) int bloom_w = 0, bloom_h = 0; int dof_w = 0, dof_h = 0; const bool dof_active = gl_dof->integer && glr.fd.depth_of_field; + const bool color_correction = R_ColorCorrectionActive(); - if (!r_skipUnderWaterFX->integer || r_bloom->integer || dof_active) { + if (!r_skipUnderWaterFX->integer || r_bloom->integer || color_correction || dof_active) { scene_w = glr.fd.width; scene_h = glr.fd.height; }