This sample demonstrates how to use Coherent GT's custom effects feature which lets you apply arbitrary filters to your HTML elements.
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.
This sample builds upon the HelloGT sample and assumes that you understand it.
CustomDx11Backend
is an extension to the usually used Dx11Backend
.DrawCustomEffect
, finds the correct effect to use based on the effect's name, binds a new pixel shader and textureApplication::InitializeRendering
method is overriden to create and initialize a new CustomDx11Backend
instead of the standard Dx11Backend
.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 shaderPSO effectPSO = m_CurrentPSO;// Find the shader from its nameeffectPSO.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 renderingApplyPSO(effectPSO);// Feed the GPU the parametersEffectData 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 effectBEGIN_DEFINE_CBUFFER(EffectData, REGISTER_BUFFER(b0))float4 TextureDimensions REGISTER(c0);float3 DesiredColor REGISTER(c1);float3 MaxAllowedDistance REGISTER(c2);float2 __Padding REGISTER(c3);END_DEFINE_CBUFFERfloat4 EmphasizeColorMain(PS_INPUT input) : SV_Target{// We only care about rgbfloat3 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.