2.9.16
Coherent GT
A modern user interface library for games
Sample - Custom filters

This sample demonstrates how to use Coherent GT's custom effects feature which lets you apply arbitrary filters to your HTML elements.

Note
The sample is written for simplicity and to illustrate the feature.

Building the sample

The sample solution is based on a minimal game framework that provides basic functionality. The sample is already configured and you only have to compile and run it.

The output will be in the Coherent/bin directory.

Prerequisites

This sample builds upon the HelloGT sample and assumes that you understand it.

Key points

  1. The class CustomDx11Backend is an extension to the usually used Dx11Backend.
    • It initializes some constant buffers used for feeding the parameter data to the GPU.
    • Overrides DrawCustomEffect, finds the correct effect to use based on the effect's name, binds a new pixel shader and texture
    • Updates the currently bounded pixel shader which tells that the backend to invalidate it at the next draw call
    • Draws the texture.
  2. Currently, the only supported effect is EmphasizeColor which takes a target color and draws the texture by grayscaling any pixel whose color differs significantly from the target color. The shader the interprets the first 3 parameters as the target color in RGB, parameters 4-6 as the maximum allowed difference between the target color and the texture color.
  3. The Application::InitializeRendering method is overriden to create and initialize a new CustomDx11Backend instead of the standard Dx11Backend.

Sample walk-through

The sample shows a page with two identical <div>s. Each uses the famous Lena image as background and has a semi-transparent colored box on top of the image. The second <div> also has a custom effect.

<style>
.with-custom-effect {
-coherent-custom-effect: "EmphasizeColor";
/* Max Color Difference per channel in RGB*/
-coherent-custom-effect-param5: 0.25;
-coherent-custom-effect-param6: 0.25;
-coherent-custom-effect-param7: 0.25;
animation-duration: 5s;
animation-name: color-changer;
animation-iteration-count: infinite;
}
</style>
<body>
<div class="image-container">
<h1>Original image</h1>
<div class="colored-block"></div>
</div>
<div class="image-container with-custom-effect">
<h1>Custom effect image</h1>
<div class="colored-block"></div>
</div>
</body>

Next, the effect properties are set. They target two specific colors - the blueish color of feathers of the hat Lena is wearing and the yellow color of the hat's reflection in the mirror behind her.

<style>
.with-custom-effect {
-coherent-custom-effect: "EmphasizeColor";
/* Max Color Difference per channel in RGB*/
-coherent-custom-effect-param5: 0.25;
-coherent-custom-effect-param6: 0.25;
-coherent-custom-effect-param7: 0.25;
animation-duration: 5s;
animation-name: color-changer;
animation-iteration-count: infinite;
}
@keyframes color-changer {
0%
{
/* The color to emphasize in RGB */
-coherent-custom-effect-param1: 0.349;
-coherent-custom-effect-param2: 0.321;
-coherent-custom-effect-param3: 0.427;
}
50%
{
-coherent-custom-effect-param1: 0.914;
-coherent-custom-effect-param2: 0.617;
-coherent-custom-effect-param3: 0.396;
}
100%
{
-coherent-custom-effect-param1: 0.349;
-coherent-custom-effect-param2: 0.321;
-coherent-custom-effect-param3: 0.427;
}
}
</style>

Let's now take a look at the C++ side.

The key part of the sample is the class CustomDx11Backend which overrides DrawCustomEffect:

void CustomEffectDx11Backend::DrawCustomEffect(const DrawCustomEffectCmd* command)
{
// Use the new shader
// renoir guarantees that the current PSO will be for DrawImage so we can reuse the vertex shader
PSO effectPSO = m_CurrentPSO;
// Find the shader from its name
effectPSO.PixelShader = m_ExtraPixelShaders[EffectNameToEnum(command->Effect.Name)].Get();
// IMPORTANT: we call ApplyPSO here which updates the currently bound PSO
// If we don't, the backend won't know that we've replaced the current pixel shader
// and may use the custom filter shader for the next draw call which will break rendering
ApplyPSO(effectPSO);
// Feed the GPU the parameters
EffectData data;
data.TexturePosition.x = command->TargetRect.Position.x;
data.TexturePosition.y = command->TargetRect.Position.y;
data.TextureSize = command->TargetRect.Size;
std::memcpy(data.Params, command->Effect.Params, sizeof(EffectData::Params));
m_ImmediateContext->UpdateSubresource(m_EffectDataCB.Get(),
0,
nullptr,
&data,
0,
0);
m_ImmediateContext->PSSetConstantBuffers(0, 1, m_EffectDataCB.GetConstPP());
DrawIndexed(command);
}

The last key part is the pixel shader itself. It samples the texture but instead of directly drawing it, it uses the per-component difference between the target color and the texel color and scales it using the max allowed difference. This results in a blending factor which is used to blend between the texel's color and a grayscale version of it. As a result, only colors that are close to the target color get drawn and the other fade into grayscale.

Texture2D textureBuffer : register(t0);
SamplerState txBufferSampler : register(s0);
// Define the constant buffer containing the data for the effect
BEGIN_DEFINE_CBUFFER(EffectData, REGISTER_BUFFER(b0))
float4 TextureDimensions REGISTER(c0);
float3 DesiredColor REGISTER(c1);
float3 MaxAllowedDistance REGISTER(c2);
float2 __Padding REGISTER(c3);
END_DEFINE_CBUFFER
float4 EmphasizeColorMain(PS_INPUT input) : SV_Target
{
// We only care about rgb
float3 textureColor = SAMPLE2D(textureBuffer, input.Additional.xy).rgb;
float3 colorDistTo = DesiredColor - textureColor;
float blendingFactor = clamp(length(colorDistTo / MaxAllowedDistance), 0, 1);
float grayscaleComponent = dot(float3(0.21, 0.72, 0.07), textureColor);
float3 grayscale = float3(grayscaleComponent, grayscaleComponent, grayscaleComponent);
float3 finalColor = (1 - blendingFactor) * textureColor + blendingFactor * grayscale;
return float4(finalColor, 1);
}

Note: the sample project won't automatically compile the HLSL. If you'd like to make changes to the shader, you need to compile the shader and regenerate the header file for it - EmphasizeColor.h.