Doppler Four
Last time I had the basic Windows Store DirectX Doppler app working, and now it's time to add the shader. To begin, effects are applied to images, which means I need to draw the waves on to some off-screen bitmap, and then render that via an effect. I'll add some new members to WaveRenderer for the necessary data objects:
Microsoft::WRL::ComPtr<ID2D1BitmapRenderTarget> m_renderTarget;
Microsoft::WRL::ComPtr<ID2D1Bitmap> m_bitmap;
Microsoft::WRL::ComPtr<ID2D1Effect> m_effect;
The first is the new off-screen context into which to draw; the second the bitmap that represents; and the third the effect object.
Changes to the program include initialising these objects, and updates to the Render method to draw. Last time I noted that initialisation is split into several phases - since the effect is resolution independent, its initialisation appears in CreateDeviceResources (to start, I'm using a built in Gaussian blur):
void WavesRenderer::CreateDeviceResources()
{
DirectXBase::CreateDeviceResources();
m_d2dContext->CreateSolidColorBrush(ColorF(ColorF::Red), &m_ellipseBrush);
m_d2dContext->CreateEffect(CLSID_D2D1GaussianBlur, &m_effect);
m_effect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 3.0f);
m_effect->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, D2D1_BORDER_MODE_HARD);
}
New code in bold (and I've removed error checking for brevity).
The bitmap size matches the display output, so initialisation for that is in the resolution dependent method:
void WavesRenderer::CreateWindowSizeDependentResources()
{
DirectXBase::CreateWindowSizeDependentResources();
// ... elided code from last time ...
// Don't apply transform to screen context, but to the back buffer
// m_d2dContext->SetTransform(m_outputTransform2D);
m_d2dContext->CreateCompatibleRenderTarget(D2D1::SizeF(m_windowBounds.Width, m_windowBounds.Height), &m_renderTarget);
m_renderTarget->SetTransform(m_outputTransform2D);
m_renderTarget->GetBitmap(&m_bitmap);
m_effect->SetInput(0, m_bitmap.Get());
}
Again, changes in bold. This creates the back buffer render target into which I'll draw later, then connects the effect input to that bitmap.
The drawing routine is where most of the changes are:
void WavesRenderer::Render()
{
m_renderTarget->BeginDraw();
m_renderTarget->Clear(ColorF(0));
for(auto i = m_waves.begin(), end = m_waves.end(); i != end; ++i)
i->Render(m_renderTarget.Get());
m_renderTarget->EndDraw();
m_d2dContext->BeginDraw();
m_d2dContext->DrawImage(m_effect.Get());
D2D1_ELLIPSE ellipse = D2D1::Ellipse(m_outputTransform2D.TransformPoint(D2D1::Point2F(m_x, 0.0f)), 0.01f * m_scale, 0.01f * m_scale);
m_d2dContext->FillEllipse(&ellipse, m_ellipseBrush.Get());
m_d2dContext->EndDraw();
}
First, draw the waves into the back buffer, then draw the effect output into the main context. Because I want the red spot to be drawn with a sharp outline, I don't draw that in the back buffer but, instead, draw it separately into the main drawing context. However, there's a render transformation from my 2x1 canvas to screen space, and I've applied that to the back buffer. Perhaps I should use the screen size as the canvas instead of normalising, but the quick fix here is to apply the transformation manually to the ellipse before I draw it.
Putting that all together I get something close to the original Doppler application, but need to add my contrast enhancing blur effect instead of the Gaussian. Following the instructions here and looking closely at the sample app, I created a ContrastEffect class which is almost identical to the RippleEffect class in that sample (I removed the paramter setting) and wired it into the application above. I did have a few problems with the pixel shader code itself: Visual Studio wanted to compile it as Shader Model 4 and the code from the original article uses tex2D, which isn't a level 4 construct. Being lazy, my first thought was to tell VS to treat it as Shader Model 3 - that all built, but the compiled shader wouldn't load. OK, next tack was to switch it back to Shader Model 4, but supply the /Gec legacy compiler option - again, compiled but failed to load. Sigh, I had to actually work out what the replacement for tex2D is and update the HLSL to Shader Model 4 - the whole file is shown below, with changed areas in bold:
Texture2D InputTexture : register(t0) ;
SamplerState InputSampler : register(s0) ;
float4 main(
float4 pos : SV_POSITION,
float4 posScene : SCENE_POSITION,
float4 uv : TEXCOORD0
) : SV_Target
{
float2 sampleOffsets[9] =
{
0,0, -1,0, 1,0, 0,-1, 0,1, -1,-1, -1,1, 1,-1, 1,1
};
float scales[9] =
{
1, 0.8, 0.8, 0.8, 0.8, 0.7, 0.7, 0.7, 0.7
};
// Very crude and simplistic blur
float4 c = 0;
for(int i = 0; i < 9; ++i)
c += scales[i] * InputTexture.Sample(InputSampler, uv.xy + sampleOffsets[i] * uv.zw);
c /= 7;
c = saturate(c);
// Increase the contrast greatly
float4 c0 = c * c * c;
c0.a = c.a;
return c0;
}
After all that, I've pretty much achieved my goal: a modern Windows 8 equivalent to the Doppler demo, harder work than I'd expected. The app's not complete (icons, state persistence, general tidiness!!) but this'll do for now.