Skip to content

Commit

Permalink
New Reinhard tone-mapping and some re-naming of files
Browse files Browse the repository at this point in the history
General tidying-up of GLSL
  • Loading branch information
jonathanhogg committed Aug 31, 2024
1 parent 33a81ae commit 2b8dd9b
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 53 deletions.
80 changes: 54 additions & 26 deletions docs/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,9 @@ the previous pass otherwise.

`uniform sampler2D first`
: If specified, this sampler allows access to the output of the *first pass* of
the shader (`pass` equal to `0`). In the first pass this sampler is undefined,
for the second pass it will be identical to `last`. Its utility comes in
shaders with more than 2 passes, where it allows later passes to refer to the
results of an initial processing step.
the shader (`pass` equal to `0`). During the first pass, `first` will contain
the output of the shader first pass from the last frame. Therefore, the `first`
texture can be used to retain state between frames.

In addition to these, the shader program may declare arbitrary numeric uniforms
that can be set using attributes with matching names on the shader node.
Expand Down Expand Up @@ -611,13 +610,19 @@ fade-out will occur, default `0.25`.

### `!adjust`

The `!adjust` node allows for basic color adjustments and takes the following
attributes:
The `!adjust` node applies color and luminance adjustments to the composited
input. It supports the following attributes:

`exposure=` *STOPS*
: Specifies an exposure adjustment in stops. This defaults to `0`. An exposure
adjustment of `1` will double the color value of each pixel, an adjustment of
`-1` will half the value of each pixel.
`color_matrix=` *MATRIX*
: Specifies a 3x3 matrix as a 9-vector to multiply each pixel by. The matrix
is given in column-major order, so the first 3 values are multiplied by the red
channel, the second 3 by the green channel and the last 3 values by the blue
channel. The resulting color will be the vector sum of the results. Default is
the matrix `1;0;0;0;1;0;0;0;1`, i.e., no adjustment.

`brightness=` *LEVEL*
: Specifies a brightness adjustment to be added to the color channels of each
pixel. Default is `0`.

`contrast=` *MULTIPLIER*
: Specifies a contrast adjustment as a multiplier. This defaults to `1`. A
Expand All @@ -626,18 +631,40 @@ midpoint, i.e., channels above 0.5 will become brighter and channels below 0.5
will become darker. A contrast adjustment below 1 will compress the dynamic
range around 0.5.

`brightness=` *LEVEL*
: Specifies an exposure adjustment in stops. This defaults to `0`. An exposure
adjustment of `1` will double the color value of each pixel, an adjustment of
`-1` will half the value of each pixel.

`color_matrix=` *MATRIX*
: Specifies a 3x3 matrix as a 9-vector (column major order) to multiply by each
color value.

These adjustments may be combined (e.g., adjusting contrast and brightness
together). Note that color values may become greater than 1 with these
adjustments, but will be clamped to zero to avoid negative values.
`exposure=` *STOPS*
: Specifies an exposure adjustment in stops. An exposure adjustment of `1` will
double the color value of each pixel, an adjustment of `-1` will half the value
of each pixel. Default is `0`.

`gamma=` *GAMMA*
: Specifies a gamma curve correction to be applied after other color
adjustments, Values less than 1 will lighten the output image and values
greater than 1 will darken it.

`tonemap=` [ `:reinhard` ]
: If specified, then a tone-mapping function will be applied to map high
luminance range images into the $[0,1]$ range. Currently, only the Reinhard
curve is supported. Default is no tone-mapping.

If `tonemap=:reinhard` then an additional attribute is supported:

`whitepoint=` *LUMINANCE*
: The Reinhard curve has an infinite upper limit for input luminance and so no
input luminance is able to result in a white output. If `whitepoint` is
greater than `0` then tone-mapping will use a modified Reinhard curve that maps
luminance values of `whitepoint` to a luminance of $1$. Default is `0`, i.e.,
no curve modification.

The `!adjust` filter works in the following order:

- un-premultiply alpha
- apply `color_matrix`
- apply `exposure`
- apply `brightness` and `contrast`
- clamp negative values to zero
- apply `gamma`
- apply `tonemap`
- pre-multiply alpha

### `!blur`

Expand All @@ -663,13 +690,14 @@ resource required to compute the blur.

A `!bloom` filter creates a soft glow around bright parts of the image to
recreate the bloom effect commonly produced by camera lenses. It works by
applying a lightness adjustment to darken the entire image, then applies a
applying an exposure adjustment to darken the entire image, then applies a
Gaussian blur and finally composites this together with the original image
with a *lighten* blend function.

The filter supports the same attributes as [`!adjust`](#adjust) – except with
`exposure` defaulting to `-1` – and [`!blur`](#blur). The `radius` attribute
must be specified and greater than zero for a bloom to be applied.
The filter supports the same `contrast`, `brightness` and `exposure` attributes
as [`!adjust`](#adjust) – except with `exposure` defaulting to `-1` - and the
same `radius` attribute as [`!blur`](#blur). The `radius` attribute must be
specified and be greater than zero for any bloom to be applied.

The default settings of the `!bloom` node assume that the input will contain
high dynamic range values, i.e., pixels with channel values much larger than 1.
Expand Down
18 changes: 15 additions & 3 deletions src/flitter/render/window/glsl/adjust.frag
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ uniform float exposure;
uniform float contrast;
uniform float brightness;
uniform mat3 color_matrix;
% if tonemap_function == 'reinhard':
uniform float whitepoint;
% endif
% for name in child_textures:
uniform sampler2D ${name};
% endfor

<%include file="color_functions.glsl"/>
<%include file="composite_functions.glsl"/>
<%include file="filter_functions.glsl"/>

Expand All @@ -24,9 +28,17 @@ void main() {
merged = composite_${composite}(texture(${name}, coord), merged);
% endif
% endfor
merged.rgb = color_matrix * merged.rgb;
merged = filter_adjust(merged, exposure, contrast, brightness);
color = gamma == 1.0 ? merged * alpha : pow(merged * alpha, vec4(gamma));
vec3 col = merged.a > 0.0 ? merged.rgb / merged.a : vec3(0.0);
col = color_matrix * col;
col = filter_adjust(col, exposure, contrast, brightness);
col = max(vec3(0.0), col);
% if gamma != 1:
col = pow(col, vec3(gamma));
% endif
% if tonemap_function == 'reinhard':
col = tonemap_reinhard(col, whitepoint);
% endif
color = vec4(col * merged.a, merged.a) * alpha;
% else:
color = vec4(0.0);
% endif
Expand Down
9 changes: 4 additions & 5 deletions src/flitter/render/window/glsl/backface_lighting.frag
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
${HEADER}

const vec3 greyscale = vec3(0.299, 0.587, 0.114);

in vec3 world_position;
in vec3 world_normal;
in vec2 texture_uv;
Expand All @@ -24,7 +22,8 @@ uniform sampler2D albedo_texture;
uniform sampler2D metal_texture;
uniform sampler2D roughness_texture;

<%include file="pbr_lighting.glsl"/>
<%include file="color_functions.glsl"/>
<%include file="lighting_functions.glsl"/>


void main() {
Expand All @@ -49,13 +48,13 @@ void main() {
float metal = fragment_properties.y;
if (use_metal_texture) {
vec4 texture_color = texture(metal_texture, texture_uv);
float mono = clamp(dot(texture_color.rgb, greyscale), 0.0, 1.0);
float mono = clamp(srgb_luminance(texture_color.rgb), 0.0, 1.0);
metal = metal * (1.0 - clamp(texture_color.a, 0.0, 1.0)) + mono;
}
float roughness = fragment_properties.z;
if (use_roughness_texture) {
vec4 texture_color = texture(roughness_texture, texture_uv);
float mono = clamp(dot(texture_color.rgb, greyscale), 0.0, 1.0);
float mono = clamp(srgb_luminance(texture_color.rgb), 0.0, 1.0);
roughness = roughness * (1.0 - clamp(texture_color.a, 0.0, 1.0)) + mono;
}

Expand Down
5 changes: 4 additions & 1 deletion src/flitter/render/window/glsl/bloom.frag
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ void main() {
}
% endif
case ${passes - 4}: {
color = filter_adjust(texture(${'last' if passes == 5 else 'texture0'}, coord), exposure, contrast, brightness);
vec4 merged = texture(${'last' if passes == 5 else 'texture0'}, coord);
vec3 col = merged.a > 0.0 ? merged.rgb / merged.a : vec3(0.0);
col = filter_adjust(col, exposure, contrast, brightness);
color = vec4(col * merged.a, merged.a);
break;
}
case ${passes - 3}: {
Expand Down
18 changes: 18 additions & 0 deletions src/flitter/render/window/glsl/color_functions.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

vec3 srgb_transfer(vec3 c) {
c = clamp(c, 0.0, 1.0);
return mix(12.92 * c, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, ceil(c - 0.0031308));
}

float srgb_luminance(vec3 c) {
return dot(c, vec3(0.2126, 0.7152, 0.0722));
}

vec3 tonemap_reinhard(vec3 c, float whitepoint) {
float l = srgb_luminance(c);
float ld = l / (1.0 + l);
if (whitepoint > 0.0) {
ld *= (1.0 + l / (whitepoint*whitepoint));
}
return c * vec3(ld);
}
5 changes: 0 additions & 5 deletions src/flitter/render/window/glsl/colorspace_functions.glsl

This file was deleted.

4 changes: 2 additions & 2 deletions src/flitter/render/window/glsl/filter_functions.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ vec4 filter_blur(sampler2D tex, vec2 coord, int radius, float sigma, vec2 delta)
return color_sum / weight_sum;
}

vec4 filter_adjust(vec4 color, float exposure, float contrast, float brightness) {
vec3 filter_adjust(vec3 color, float exposure, float contrast, float brightness) {
float offset = brightness + (1.0 - contrast) / 2.0;
return vec4(max((color.rgb / color.a * pow(2.0, exposure) * contrast + offset) * color.a, 0.0), color.a);
return color * pow(2.0, exposure) * contrast + offset;
}
16 changes: 7 additions & 9 deletions src/flitter/render/window/glsl/standard_lighting.frag
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
${HEADER}

const vec3 greyscale = vec3(0.299, 0.587, 0.114);

in vec3 world_position;
in vec3 world_normal;
in vec2 texture_uv;
Expand Down Expand Up @@ -38,7 +36,8 @@ uniform sampler2D ao_texture;
uniform sampler2D emissive_texture;
uniform sampler2D transparency_texture;

<%include file="pbr_lighting.glsl"/>
<%include file="color_functions.glsl"/>
<%include file="lighting_functions.glsl"/>


void main() {
Expand All @@ -61,7 +60,7 @@ void main() {
float transparency = fragment_albedo.a;
if (use_transparency_texture) {
vec4 texture_color = texture(transparency_texture, texture_uv);
float mono = clamp(dot(texture_color.rgb, greyscale), 0.0, 1.0);
float mono = clamp(srgb_luminance(texture_color.rgb), 0.0, 1.0);
transparency = transparency * (1.0 - clamp(texture_color.a, 0.0, 1.0)) + mono;
}
vec3 emissive = fragment_emissive.rgb;
Expand All @@ -74,19 +73,19 @@ void main() {
float metal = fragment_properties.y;
if (use_metal_texture) {
vec4 texture_color = texture(metal_texture, texture_uv);
float mono = clamp(dot(texture_color.rgb, greyscale), 0.0, 1.0);
float mono = clamp(srgb_luminance(texture_color.rgb), 0.0, 1.0);
metal = metal * (1.0 - clamp(texture_color.a, 0.0, 1.0)) + mono;
}
float roughness = fragment_properties.z;
if (use_roughness_texture) {
vec4 texture_color = texture(roughness_texture, texture_uv);
float mono = clamp(dot(texture_color.rgb, greyscale), 0.0, 1.0);
float mono = clamp(srgb_luminance(texture_color.rgb), 0.0, 1.0);
roughness = roughness * (1.0 - clamp(texture_color.a, 0.0, 1.0)) + mono;
}
float ao = fragment_properties.w;
if (use_ao_texture) {
vec4 texture_color = texture(ao_texture, texture_uv);
float mono = clamp(dot(texture_color.rgb, greyscale), 0.0, 1.0);
float mono = clamp(srgb_luminance(texture_color.rgb), 0.0, 1.0);
ao = ao * (1.0 - clamp(texture_color.a, 0.0, 1.0)) + mono;
}

Expand All @@ -102,8 +101,7 @@ void main() {
}
vec3 final_color = mix(diffuse_color, fog_color, fog_alpha) * opacity + specular_color * (1.0 - fog_alpha);
if (monochrome) {
float grey = dot(final_color, greyscale);
final_color = vec3(grey);
final_color = vec3(srgb_luminance(final_color.rgb));
}
fragment_color = vec4(final_color * tint, opacity);
}
2 changes: 1 addition & 1 deletion src/flitter/render/window/glsl/window.frag
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ out vec4 color;
uniform sampler2D ${name};
% endfor

<%include file="color_functions.glsl"/>
<%include file="composite_functions.glsl"/>
<%include file="colorspace_functions.glsl"/>

void main() {
% if child_textures:
Expand Down
7 changes: 6 additions & 1 deletion src/flitter/render/window/shaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,14 @@ def render(self, node, **kwargs):

class Adjust(Shader):
DEFAULT_FRAGMENT_SOURCE = TemplateLoader.get_template('adjust.frag')
TONEMAP_FUNCTIONS = {'reinhard'}

def render(self, node, **kwargs):
super().render(node, exposure=0, contrast=1, brightness=0, color_matrix=(1, 0, 0, 0, 1, 0, 0, 0, 1), **kwargs)
tonemap = node.get('tonemap', 1, str)
if tonemap not in self.TONEMAP_FUNCTIONS:
tonemap = None
super().render(node, exposure=0, contrast=1, brightness=0, color_matrix=(1, 0, 0, 0, 1, 0, 0, 0, 1),
gamma=1, tonemap_function=tonemap, **kwargs)


class Blur(Shader):
Expand Down

0 comments on commit 2b8dd9b

Please sign in to comment.