Skip to content

Commit 7de6c9c

Browse files
authored
Merge pull request #21 from MrLixm/feat-obs-improvements
Feat(obs): add white-balance; code clean
2 parents 075ff03 + acf9ce0 commit 7de6c9c

23 files changed

+589
-225
lines changed

obs/README.md

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ many benefits applied on desktop capture.
1111
|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
1212
| ![screenshot of obs interface while camera is pointing at various colored objects like legos](doc/img/obs-c922-default.jpg) | ![screenshot of obs interface while camera is pointing at various colored objects like legos](doc/img/obs-c922-agx.jpg) |
1313

14-
> video device is a "standard" Logitech C922 webcam
14+
> video device is a "standard" Logitech C922 webcam for the above comparison
1515
1616
# Requirements
1717

1818
- This has been developed on OBS `28.1.2` for Windows but should work for lower
19-
versions, and other operating systems.
19+
and upper versions, and other operating systems.
2020

2121
- Nothing more than the content of this directory.
2222

@@ -36,50 +36,54 @@ The script is now active. A new filter has been created :
3636

3737
1. Go to your Scene/Source where you want to add the AgX filter and select it (the source)
3838
2. Click on the `Filters` button that should be a bit above the source.
39-
3. In the **Effect Filters** section, click the `+` button and choose AgX
39+
3. In the **Effect Filters** section, click the `+` button and choose `AgX`
4040

4141
All done ! You can now configure it.
4242

4343
# Configuration
4444

4545
![screenshot of OBS interface while in the Filters section](doc/img/obs-filter-options.png)
4646

47-
> ![NOTE]
47+
> [!IMPORTANT]
4848
> Reminder that AgX being a display transform it should be placed at
4949
> **the very end** of the image processing chain (= at the bottom in OBS).
5050
5151
The camera/video-source and your lighting setup will affect how much you need
5252
to tweak the parameters. There is no setup that work for all cases, but once
5353
configured for your camera/usual lighting, you should not need to touch it anymore.
5454

55-
## Recommended Options
55+
## Guidelines
5656

57-
I recommend to always start by :
58-
59-
- boosting the `Grading Exposure` by +1.0 stop.
60-
- boosting `Highligh Gain` by 2.0
57+
I recommend to :
58+
- `Pre-Grading`
59+
- boosting the `Grading Exposure` by +1.0 stop if you are in a dim environment.
60+
- boosting `Highligh Gain` by 2.0 (always better)
61+
- `White Balance`: `intensity` at 1.0 and use `tint` ~10.0 if your camera tend to
62+
have to produce pinkish tones (noticable on skin).
6163

6264
## Available Options
6365

6466
### Input Colorspace
6567

6668
Pick in which colorspace your source is encoded.
6769

68-
Passthrough means no decoding is applied.
70+
`Passthrough` means no decoding is applied.
6971

7072
### Output Colorspace
7173

7274
Target colorspace encoding. Must correspond to your monitor calibration.
7375

74-
> ![NOTE]
76+
> [!TIP]
7577
> You can request adding new colorspace by opening an issue on GitHub !
7678
7779
### DRT
7880

7981
Pick the DRT to use. Technically here we could include other DRT than AgX.
80-
But for now only None and AgX are available.
82+
But for now only None and AgX (with 2 variants) are available.
8183

82-
`None` will not apply AgX but still allow you to use the grading options.
84+
- `None` will not apply AgX but still allow you to use the grading options.
85+
- `AgX w/Outset` is AgX but with an additional transform to restore chroma. This
86+
can reintroduce shift and skews but might be acceptable.
8387

8488

8589
### Pre-Grading/...
@@ -96,7 +100,7 @@ Power function. 1.0 = neutral.
96100

97101
### Grading/Saturation
98102

99-
Saturation based on BT.709 coeff. 1.0 = neutral.
103+
Saturation with coefficient based on the working color-space. 1.0 = neutral.
100104

101105
### Grading/Highlight Gain
102106

@@ -109,32 +113,38 @@ via `Highlight Gain Threshold`.
109113

110114
See above.
111115

112-
### Post-Grading/...
116+
### Grading/White-Balance
113117

114-
Grading modifications applied after AgX on display encoded data. This will
115-
introduce skews, clipping and other artefacts.
118+
Shift white tones, applied before all operations.
119+
120+
- `Temperature`: in Kelvin. Lower=warmer, higher=colder.
121+
- `Tint`: shift the temperature: >0=greener, <0=pinker
122+
- `Intensity`: global multiplier for the effect. 0=disabled.
116123

117-
Not recommended to use or with very small values.
124+
The most "neutral values" when `Intensity` is 1.0 are `Temperature`=`~5600` and
125+
`Tint`=`~-15.5` which try to match the Illuminant E (those neutral values are not
126+
ideal and perhaps the white-balance implementation could be improved)
118127

119-
### Debug/Use OCIO Log Transform
128+
![Close up diagram of the CIE 1960 UCS with the planckian locus iso lines visble](https://upload.wikimedia.org/wikipedia/commons/d/d7/Planckian-locus.png)
120129

121-
Switch to use the HLSL transform being an exact match to the OCIO `log2Transform`.
122130

123-
Does not create any change visually.
131+
### Post-Grading/...
124132

125-
### Debug/Apply Outset
133+
Grading modifications applied after AgX on display encoded data. This will
134+
introduce skews, clipping and other artefacts.
126135

127-
Not originally included in the first AgX version but should be in the future.
128-
Restore chroma and avoid having to use Punchy saturation.
129-
Might bring back some hue skews so better left off.
136+
Not recommended to use or with very small values, but can help achieve a stronger
137+
artistic look.
138+
139+
All parameters are described the same as for the Pre-Grading section.
130140

131141
### Debug/CAT Method
132142

133-
Chromatic Adaptation Transform method to chose for whitepoint conversion.
143+
Chromatic Adaptation Transform method to chose for whitepoint conversion (when
144+
there is a whitepoint conversion between input and output colorspace.)
134145

135146
Default is Bradford and doesn't need to be changed.
136147

137-
138148
# Developer
139149

140150
Developer documentation can be found in [doc/DEV.md](doc/DEV.md).

obs/doc/DEV.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
# Introduction.
44

55
- `AgX.lua` : direct interface with OBS used for building the GUI
6-
- `AgX.hlsl` : GPU shader with the actual AgX code.
7-
- `colorspace.hlsl` : side-library imported in `AgX.hlsl`
8-
- this is actually the public interface of the `colorscience/` "package".
9-
- `colorscience/` library of hlsl modules for color manipulation. some of the modules
10-
are code-generated and not intended to be edited directly. Directly editable modules are :
11-
- `math.hlsl`
12-
- `cctf.hlsl`
6+
- `AgX.hlsl` : top-level GPU shader used in the lua script.
7+
8+
With that you will find additional hlsl modules that are all imported in `AgX.hlsl` :
9+
10+
- `lib_colorscience.hlsl`: library of hlsl modules for color manipulation
11+
- `_lib_colorscience/`: some of the modules are procedurally-generated and not
12+
intended to be edited directly (see header comment).
1313

1414
The "procedurally" generated code can be found in the [../src/](../src) directory.
1515

obs/doc/img/obs-filter-options.png

7.18 KB
Loading

obs/doc/img/obs-main.png

-505 KB
Loading

obs/obs-script/AgX.hlsl

Lines changed: 54 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
/*
2-
HLSL implementation of AgX (by Troy Sobotka) for OBS.
2+
HLSL implementation of AgX for OBS.
3+
4+
AgX is a Display Rendering Transform designed by Troy Sobotka.
5+
6+
The code is not AgX-specific and actually very flexible to integrate any other DRT.
37
48
author = "Liam Collod"
59
repository = "https://github.com/MrLixm/AgXc"
@@ -12,7 +16,6 @@ References:
1216
- [4] https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.postprocessing/PostProcessing/Shaders/Colors.hlsl#L574
1317
- [5] https://github.com/colour-science/colour/blob/develop/colour/models/rgb/transfer_functions/srgb.py#L99
1418
*/
15-
#include "colorspace.hlsl"
1619

1720
// OBS-specific syntax adaptation to HLSL standard to avoid errors reported by the code editor
1821
#define SamplerState sampler_state
@@ -34,16 +37,21 @@ colorspaceid_DCIP3_Display_2_6 = 5;
3437
colorspaceid_Apple_Display_P3 = 6;
3538
*/
3639
uniform int DRT = 1;
37-
uniform float INPUT_EXPOSURE = 0.75;
40+
uniform float INPUT_EXPOSURE = 0.5;
3841
uniform float INPUT_GAMMA = 1.0;
3942
uniform float INPUT_SATURATION = 1.0;
40-
uniform float INPUT_HIGHLIGHT_GAIN = 0.0;
43+
uniform float INPUT_HIGHLIGHT_GAIN = 1.5;
4144
uniform float INPUT_HIGHLIGHT_GAIN_GAMMA = 1.0;
45+
// temperature/tint values try to emulate whitepoint E for neutral result
46+
uniform float INPUT_WHITE_BALANCE_TEMPERATURE = 5400;
47+
uniform float INPUT_WHITE_BALANCE_TINT = -15.5;
48+
uniform float INPUT_WHITE_BALANCE_INTENSITY = 1.0;
4249
uniform float PUNCH_EXPOSURE = 0.0;
4350
uniform float PUNCH_SATURATION = 1.0;
44-
uniform float PUNCH_GAMMA = 1.3;
45-
uniform bool USE_OCIO_LOG = false;
46-
uniform bool APPLY_OUTSET = true;
51+
uniform float PUNCH_GAMMA = 1.0;
52+
uniform float PUNCH_WHITE_BALANCE_TEMPERATURE = 5400;
53+
uniform float PUNCH_WHITE_BALANCE_TINT = -15.5;
54+
uniform float PUNCH_WHITE_BALANCE_INTENSITY = 1.0;
4755

4856
// LUT AgX-default_contrast.lut.png
4957
uniform texture2d AgXLUT;
@@ -56,6 +64,7 @@ uniform texture2d AgXLUT;
5664

5765
uniform int drt_id_none = 0;
5866
uniform int drt_id_agx = 1;
67+
uniform int drt_id_agx_outset = 2;
5968

6069
/*=================
6170
OBS BOILERPLATE
@@ -87,6 +96,11 @@ struct PixelData
8796
float2 uv : TEXCOORD0; // UV coordinates in the source picture
8897
};
8998

99+
// order matters + need the constants defined above
100+
#include "lib_math.hlsl"
101+
#include "lib_colorscience.hlsl"
102+
#include "lib_grading.hlsl"
103+
#include "lib_agx.hlsl"
90104

91105
/*=================
92106
Main processes
@@ -101,97 +115,27 @@ float3 applyInputTransform(float3 Image)
101115
return convertColorspaceToColorspace(Image, INPUT_COLORSPACE, colorspaceid_working_space);
102116
}
103117

104-
float3 applyGrading(float3 Image)
118+
float3 applyOpenGrading(float3 Image)
105119
/*
106-
Apply creative grading operations (pre-display-transform).
107-
*/
108-
{
109-
110-
float ImageLuma = powsafe(get_luminance(Image), INPUT_HIGHLIGHT_GAIN_GAMMA);
111-
Image += Image * ImageLuma.xxx * INPUT_HIGHLIGHT_GAIN;
112-
113-
Image = saturation(Image, INPUT_SATURATION);
114-
Image = powsafe(Image, INPUT_GAMMA);
115-
Image *= powsafe(2.0, INPUT_EXPOSURE);
116-
return Image;
117-
}
120+
Apply creative grading on open-domain image state.
118121
119-
float3 applyAgXLog(float3 Image)
120-
/*
121-
Prepare the data for display encoding. Converted to log domain.
122+
:param Image: expected to be in a open-domain state.
122123
*/
123124
{
124-
125-
float3x3 agx_compressed_matrix = {
126-
0.84247906, 0.0784336, 0.07922375,
127-
0.04232824, 0.87846864, 0.07916613,
128-
0.04237565, 0.0784336, 0.87914297
129-
};
130-
131-
Image = max(0.0, Image); // clamp negatives
132-
// why this doesn't work ??
133-
// Image = mul(agx_compressed_matrix, Image);
134-
Image = apply_matrix(Image, agx_compressed_matrix);
135-
136-
if (USE_OCIO_LOG)
137-
Image = cctf_log2_ocio_transform(Image);
138-
else
139-
Image = cctf_log2_normalized_from_open_domain(Image, -10.0, 6.5);
140-
141-
Image = clamp(Image, 0.0, 1.0);
142-
return Image;
143-
}
144-
145-
float3 applyAgXLUT(float3 Image)
146-
/*
147-
Apply the AgX 1D curve on log encoded data.
148-
149-
The output is similar to AgX Base which is considered
150-
sRGB - Display, but here we linearize it.
151-
152-
-- ref[3] for LUT implementation
153-
*/
154-
{
155-
156-
float3 lut3D = Image * (AgXLUT_BLOCK_SIZE-1);
157-
158-
float2 lut2D[2];
159-
// Front
160-
lut2D[0].x = floor(lut3D.z) * AgXLUT_BLOCK_SIZE+lut3D.x;
161-
lut2D[0].y = lut3D.y;
162-
// Back
163-
lut2D[1].x = ceil(lut3D.z) * AgXLUT_BLOCK_SIZE+lut3D.x;
164-
lut2D[1].y = lut3D.y;
165-
166-
// Convert from texel to texture coords
167-
lut2D[0] = (lut2D[0]+0.5) * AgXLUT_PIXEL_SIZE;
168-
lut2D[1] = (lut2D[1]+0.5) * AgXLUT_PIXEL_SIZE;
169-
170-
// Bicubic LUT interpolation
171-
Image = lerp(
172-
AgXLUT.Sample(LUTSampler, lut2D[0]).rgb, // Front Z
173-
AgXLUT.Sample(LUTSampler, lut2D[1]).rgb, // Back Z
174-
frac(lut3D.z)
125+
Image = white_balance(
126+
Image,
127+
INPUT_WHITE_BALANCE_TEMPERATURE,
128+
INPUT_WHITE_BALANCE_TINT,
129+
INPUT_WHITE_BALANCE_INTENSITY
175130
);
176-
// LUT apply the transfer function so we remove it to keep working on linear data.
177-
Image = cctf_decoding_Power_2_2(Image);
178-
return Image;
179-
}
180131

181-
float3 applyOutset(float3 Image)
182-
/*
183-
Outset is the inverse of the inset applied during `applyAgXLog`
184-
and restore chroma.
185-
*/
186-
{
187-
188-
float3x3 agx_compressed_matrix_inverse = {
189-
1.1968790, -0.09802088, -0.09902975,
190-
-0.05289685, 1.15190313, -0.09896118,
191-
-0.05297163, -0.09804345, 1.15107368
192-
};
193-
Image = apply_matrix(Image, agx_compressed_matrix_inverse);
132+
float ImageLuma = get_luminance(Image, colorspaceid_working_space);
133+
ImageLuma = grade_gamma(ImageLuma, INPUT_HIGHLIGHT_GAIN_GAMMA);
134+
Image += Image * ImageLuma.xxx * INPUT_HIGHLIGHT_GAIN;
194135

136+
Image = grade_saturation(Image, INPUT_SATURATION, colorspaceid_working_space);
137+
Image = grade_gamma(Image, INPUT_GAMMA);
138+
Image = grade_exposure(Image, INPUT_EXPOSURE);
195139
return Image;
196140
}
197141

@@ -201,10 +145,10 @@ float3 applyDRT(float3 Image)
201145
*/
202146
{
203147
if (DRT == drt_id_agx){
204-
Image = applyAgXLog(Image);
205-
Image = applyAgXLUT(Image);
206-
if (APPLY_OUTSET)
207-
Image = applyOutset(Image);
148+
Image = applyAgX(Image);
149+
}
150+
if (DRT == drt_id_agx_outset){
151+
Image = applyAgXOutset(Image);
208152
}
209153
return Image;
210154

@@ -222,16 +166,23 @@ float3 applyODT(float3 Image)
222166
}
223167

224168

225-
float3 applyLookPunchy(float3 Image)
169+
float3 applyDisplayGrading(float3 Image)
226170
/*
227-
Applies the post "Punchy" look to display-encoded data.
171+
Apply creative grading on display-domain image state.
228172
229-
Input is expected to be in a display-state.
173+
:param Image: expected to be in a display-state.
230174
*/
231175
{
232-
Image = powsafe(Image, PUNCH_GAMMA);
233-
Image = saturation(Image, PUNCH_SATURATION);
234-
Image *= powsafe(2.0, PUNCH_EXPOSURE); // not part of initial cdl
176+
Image = white_balance(
177+
Image,
178+
PUNCH_WHITE_BALANCE_TEMPERATURE,
179+
PUNCH_WHITE_BALANCE_TINT,
180+
PUNCH_WHITE_BALANCE_INTENSITY
181+
);
182+
183+
Image = grade_gamma(Image, PUNCH_GAMMA);
184+
Image = grade_saturation(Image, PUNCH_SATURATION, OUTPUT_COLORSPACE);
185+
Image = grade_exposure(Image, PUNCH_EXPOSURE); // not part of initial cdl
235186
return Image;
236187

237188
}
@@ -250,10 +201,10 @@ float4 PIXELSHADER_AgX(PixelData pixel) : TARGET
250201
float4 OriginalImage = image.Sample(linear_clamp, pixel.uv);
251202
float3 Image = OriginalImage.rgb;
252203
Image = applyInputTransform(Image);
253-
Image = applyGrading(Image);
204+
Image = applyOpenGrading(Image);
254205
Image = applyDRT(Image);
255206
Image = applyODT(Image);
256-
Image = applyLookPunchy(Image);
207+
Image = applyDisplayGrading(Image);
257208

258209
Image = convertColorspaceToColorspace(Image, 1, 4);
259210

0 commit comments

Comments
 (0)