Here’s a little demo to show off a technique that Farbs posted about earlier this week.
Download
Binaries : stencilrendering_bin.zip (3.6 Mb – Binaries)
Code : stencilrendering_src.zip (1.8 Mb – Source Only, get DLLs for TV3D and SlimDX from the Binaries)
Description
Every frame, a random color from the target image is sampled. This color will be used as a stencil, such that every pixel whole target color is close enough to that stencil’s color will be painted. It’s a constructive painting process; every frame paints a single color, but if you wait long enough in a single spot you’ll end up with the target image.
Actually, the post processing draw code is so concise that I can post it here :
public override void PostDraw() { targetBuffer.BltFromMainBuffer(); targetBuffer.SetSystemMemCopy(true, true); // Lolworkaround // 1) Pick a colour from target image var pickedColor = Globals.DecodeRGBA(TextureFactory.GetPixel(targetBuffer.GetTexture(), RandomHelper.Random.Next(0, targetBuffer.GetWidth()), RandomHelper.Random.Next(0, targetBuffer.GetHeight()))); stencilShader.SetEffectParamVector3("stencilColor", new TV_3DVECTOR(pickedColor.r, pickedColor.g, pickedColor.b)); Screen2DImmediate.Draw_FullscreenQuadWithShader(stencilShader, 0, 0, 1, 1, mainBuffer.GetTexture(), targetBuffer.GetTexture()); mainBuffer.BltFromMainBuffer(); }
“targetBuffer” and “mainBuffer” are just two TVRenderSurfaces as big as the viewport. Since I sample from targetBuffer, it needs to be flagged with “system memory copy”. I thought this would slow things down, but it runs at very interactive framerates (60 and more).
And uh… The “lolworkaround” is a bug in the current build of TV3D. Usually you only need to set this once at initialization time, but a BltFromMainBuffer does not flag the surface as dirty and it prevents updates to the pixels that I sample. Resetting the memory copy mode makes the changes effective. Sylvain tells me it’s fixed in the current development build. :)
A pixel shader does the rest :
float4 PS(PS_INPUT IN) : COLOR { float3 bufferSample = tex2D(MainBufferSampler, IN.TexCoord).rgb; float3 targetSample = tex2D(TargetSampler, IN.TexCoord).rgb; // 2) Calc difference between current screen and target (per channel subtraction, abs, then accumulate all three into one) float sampleDiff = distance(bufferSample, targetSample); // 3) As above, but between colour picked in step 1 and target image float stencilDiff = distance(stencilColor, targetSample); // 4) Where result of step 2 > result of step 3, draw colour picked in step 1 float4 color; if (stencilDiff < sampleDiff) color = float4(stencilColor, 1); else color = float4(bufferSample, 1); return color; }
I think it’s a lovely effect. It’s very dependent on how colorful and contrasted the scene is, and it works differently for sharply-defined shapes or gradients… And of course camera movement is a big factor. In the video it gets confusing, the effect is more “painterly” if you just rotate the camera in small circles and wait for the effect to accumulate.