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.