###
**Vignette**

Vignetting is one of the most widespread postprocessing effects used in games. It's popular in photography as well. Subtly darker corners can produce nice looking effect. There are few types of vignetting. For instance, Unreal Engine 4 uses natural one.But let's back to The Witcher 3. Click here for interactive comparison to see difference between vignette on/off. It's from The Witcher 3 perf guide from NVIDIA.

Screenshot from The Witcher 3 with enabled vignette. |

I will back to this later.

**Implementation details**

**First of all, there is a minor difference in vignette used in the original version of The Witcher 3 (released May 19, 2015) and The Witcher 3: Blood and Wine. In the former, "inverse gradient" is calculated within pixel shader, while in the latter it was precalculated to 256x256 2d texture:**

256x256 texture used as "inverse gradient" in Blood & Wine. |

Like in most games, vignette in Witcher 3 is calculated in the final postprocess pixel shader. Let's take a look at assembly:

```
...
44: log r0.xyz, r0.xyzx
45: mul r0.xyz, r0.xyzx, l(0.454545, 0.454545, 0.454545, 0.000000)
46: exp r0.xyz, r0.xyzx
47: mul r1.xyz, r0.xyzx, cb3[9].xyzx
48: sample_indexable(texture2d)(float,float,float,float) r0.w, v1.zwzz, t2.yzwx, s2
49: log r2.xyz, r1.xyzx
50: mul r2.xyz, r2.xyzx, l(2.200000, 2.200000, 2.200000, 0.000000)
51: exp r2.xyz, r2.xyzx
52: dp3 r1.w, r2.xyzx, cb3[6].xyzx
53: add_sat r1.w, -r1.w, l(1.000000)
54: mul r1.w, r1.w, cb3[6].w
55: mul_sat r0.w, r0.w, r1.w
56: mad r0.xyz, -r0.xyzx, cb3[9].xyzx, cb3[7].xyzx
57: mad r0.xyz, r0.wwww, r0.xyzx, r1.xyzx
...
```

Interesting! Looks like vignette uses both gamma (line 46) and linear (line 51) spaces to calculate.

At line 48 we sample "inverse gradient" texture.

cb3[9].xyz is not related to vignette. In every tested frame it was set to float3(1.0, 1.0, 1.0) so this is probably final filter used in fade-in / fade-out effects.

There are three main parameters for TW3 vignette:

- Opacity ( cb3[6].w ) - Affects intensity of the vignette. 0 - no vignette, 1 - max vignette. From my observations it looks like in base The Witcher 3 is somewhere around 1.0, while in Blood & Wine it oscillates somewhere 0.15.
- Color ( cb3[7].xyz ) - The great thing about TW3 vignette is possibility to change color of it. It doesn't have to be black, but in practice.. It's usually set as float3( 3.0 / 255.0, 4.0 / 255.0, 5.0 / 255.0 ) and so on - in general multiplies of 0.00392156 = 1.0/255.0
- Weights ( cb3[6].xyz ) - This is very interesting parameter. I've always seen "flat" vignette, like this:

Typical vignette mask |

TW3 Vignette mask calculated using weights |

Weights are close to 1.0. Take a look at frame's constant buffer data from one of frames from Blood&Wine (magic world with rainbow): This is why bright pixels from previously mentioned sky were not really affected by vignette.

The calculated mask is used to interpolate values between image color and vignette's color.

The calculated mask is used to interpolate values between image color and vignette's color.

**Code**

Here is my implementation of TW3 vignette in HLSL.

GammaToLinear = pow(color, 2.2)

GammaToLinear = pow(color, 2.2)

```
/*
// The Witcher 3 vignette.
//
// Input color is in gamma space
// Output color is in gamma space as well.
*/
float3 Vignette_TW3( in float3 gammaColor, in float3 vignetteColor, in float3 vignetteWeights,
in float vignetteOpacity, in Texture2D texVignette, in float2 texUV )
{
// Calculate vignette amount based on color in *LINEAR* color space and vignette weights.
float vignetteWeight = dot( GammaToLinear( gammaColor ), vignetteWeights );
// We need to keep vignette weight in [0-1] range
vignetteWeight = saturate( 1.0 - vignetteWeight );
// Multiply by opacity
vignetteWeight *= vignetteOpacity;
// Obtain vignette mask (here is texture; you can also calculate your custom mask here)
float sampledVignetteMask = texVignette.Sample( samplerLinearClamp, texUV ).x;
// Final (inversed) vignette mask
float finalInvVignetteMask = saturate( vignetteWeight * sampledVignetteMask );
// final composite in gamma space
float3 Color = lerp( gammaColor, vignetteColor, finalInvVignetteMask );
// * uncomment to debug vignette mask:
// return 1.0 - finalInvVignetteMask;
// Return final color
return Color;
}
```

I hope you like it :) Feel free to comment. You can also try my HLSLexplorer which helped me greatly in understanding HLSL assembly and you can also check my previous posts about Witcher 3 rendering techniques.

As always, please take names of variables with grain of salt - TW3 shaders are processed with D3DStripShader so basically I know almost nothing, it's all about guessing. I am also not responsible for any damages done to you hardware due to this shader ;)

Thanks for reading!

**Bonus: Calculating gradient**

In The Witcher 3 from 2015 inverse gradient is calculated within pixel shader instead of sampling precalculated texture. Let's take a look at the assembly:

```
35: add r2.xy, v1.zwzz, l(-0.500000, -0.500000, 0.000000, 0.000000)
36: dp2 r1.w, r2.xyxx, r2.xyxx
37: sqrt r1.w, r1.w
38: mad r1.w, r1.w, l(2.000000), l(-0.550000)
39: mul_sat r2.w, r1.w, l(1.219512)
40: mul r2.z, r2.w, r2.w
41: mul r2.xy, r2.zwzz, r2.zzzz
42: dp4 r1.w, l(-0.100000, -0.105000, 1.120000, 0.090000), r2.xyzw
43: min r1.w, r1.w, l(0.940000)
```

Luckily for us, this is pretty easy. In HLSL this would be something like this:

```
float TheWitcher3_2015_Mask( in float2 uv )
{
float distanceFromCenter = length( uv - float2(0.5, 0.5) );
float x = distanceFromCenter * 2.0 - 0.55;
x = saturate( x * 1.219512 ); // 1.219512 = 100/82
float x2 = x * x;
float x3 = x2 * x;
float x4 = x2 * x2;
float outX = dot( float4(x4, x3, x2, x), float4(-0.10, -0.105, 1.12, 0.09) );
outX = min( outX, 0.94 );
return outX;
}
```

So we simply calculate distance from center to texel, doing some magic (multiply, saturate...) with it and then... we calculate polynomial! Awesome.

## Brak komentarzy:

## Prześlij komentarz