Come detto precedentemente, questo genere di effetti necessita di un rettangolo che verrà rasterizzato a tutto schermo per la visualizzazione dell'effetto stesso; anche se sembra un'operazione insignificante, il corretto posizionamento del rettangolo influisce notevolmente sulla resa di qualsiasi effetto. La corretta mappatura tra texels delle textures utilizzate ed i pixels dello schermo determina la corretta campionatura e preserva la nitidezza delle immagini.
Dato che il rettangolo verrà visualizzato sempre sul piano dello schermo, è utile conoscere come XNA e DirectX definiscono lo screen-space, dandoci l'opportunità di specificare i quattro vertici della geometria direttamente in questo spazio, in modo da ridurre al minimo il frammento di codice del Vertex Shader.

Come è possibile notare dall'immagine precedente, lo screen-space ha un range di valori compreso tra -1.0 e 1.0 in entrambi gli assi qualsiasi sia il rapporto tra larghezza e altezza del viewport corrente e indipendentemente dalla risoluzione in pixels. Questo ci permette di definire il rettangolo una sola volta per viewport di dimensioni e forme differenti:
// Definizione del vertice
public struct Vertex
{
public Vector4 pos;
public Vector2 texCoords;
public Vertex(Vector4 pos, Vector2 texCoords)
{
this.pos = pos;
this.texCoords = texCoords;
}
}
// Definizione del rettangolo
public static Vertex[] Quad =
{
new Vertex(new Vector4(-1.0f, -1.0f, 0.0f, 1.0f), new Vector2(0.0f, 1.0f)),
new Vertex(new Vector4( 1.0f, -1.0f, 0.0f, 1.0f), new Vector2(1.0f, 1.0f)),
new Vertex(new Vector4( 1.0f, 1.0f, 0.0f, 1.0f), new Vector2(1.0f, 0.0f)),
new Vertex(new Vector4(-1.0f, 1.0f, 0.0f, 1.0f), new Vector2(0.0f, 0.0f))
};
Come per il codice precedente anche quello successivo va eseguito in inzializzazione una sola volta. Qui dovremo specificare la dichiarazione del vertice specificando gli elementi che lo costituiscono in modo che XNA possa calcolare la dimensione del pacchetto di dati per un vertice:
// Definizione dell'array di elementi
VertexElement[] m_vElements =
{
new VertexElement(0, 0, VertexElementFormat.Vector4, VertexElementMethod.Default,
VertexElementUsage.Position, 0),
new VertexElement(0, 16, VertexElementFormat.Vector2, VertexElementMethod.Default,
VertexElementUsage.TextureCoordinate, 0)
};
// Definizione della dichiarazione di un tipo di vertice
VertexDeclaration VertexDecl = new VertexDeclaration(GraphicsDevice, m_vElements);
Quello che resta da trattare ora è il corretto offset delle coordinate di texture da applicare nel Vertex Shader. Conoscendo le dimensioni in pixel del viewport corrente, sarà sufficiente passare l'inverso di questi valori al Vertex Shader sotto forma di variabile float2 e sottrarla alle coordinate di texture; l'effetto prodotto è quello di allineare il centro dei texels con il centro di pixels (questa procedura è necessaria solamente per XNA e DirectX 9.0, OpenGL e DirectX 10 e successivi non risentono di tale problema di rasterizzazione).
Questo procedimento, oltre che permettere la corretta visualizzazione ed il corretto campionamento delle textures in ingresso al Pixel Shader, evita di dover agire sui samplers specificando per nei filtri (MINFILTER, MAGFILTER, MIPFILTER) NONE o NEAREST, rendendo disponibile il filtro LINEAR, utile nell'implementazione di alcuni effetti.
La scelta di calcolare l'offset delle coordinate di textures in tempo reale nel Vertex Shader è giustificato se si tiene in considerazione la possibilità di applicare un effetto in post-processo non solo a schermo intero, ma ad un viewport specifico (ad esempio lo specchietto retrovisore dell'automobile guidata dal giocatore).
1 uniform texture sourceTex;
2 uniform float2 texelShift;
3
4 sampler sourceSampler : register(s0) = sampler_state
5 {
6 Texture = (sourceTex);
7 AddressU = CLAMP;
8 AddressV = CLAMP;
9 AddressW = CLAMP;
10 MIPFILTER = NONE;
11 MINFILTER = LINEAR;
12 MAGFILTER = LINEAR;
13 };
14
15 struct VS_OUTPUT
16 {
17 float4 oPos : POSITION;
18 float2 oTexCoords : TEXCOORD0;
19 };
20
21 VS_OUTPUT screenQuadVS(
22 in float4 position : POSITION,
23 in float2 texCoords : TEXCOORD0)
24 {
25 VS_OUTPUT Out;
26
27 Out.oPos = position;
28 Out.oTexCoords = texCoords - texelShift;
29
30 return Out;
31 }
32
33 float4 screenQuadPS(
34 in float2 texCoord : TEXCOORD0) : COLOR0
35 {
36 return tex2D(sourceSampler, texCoord);
37 }
Per disegnare il rettangolo a schermo sarà ora sufficiente impostare la tecnica di shading specificando il texelShift e tutte le texture di cui l'effetto ha bisogno.
// Imposta il tipo di vertice per renderizzare il rettangolo
GraphicsDevice.VertexDeclaration = VertexDecl;
// Imposta lo spostamento per allineare texels e pixels
Vector2 texelShift = new Vector2(0.5f / GraphicsDevice.PresentationParameters.BackBufferWidth,
-0.5f / GraphicsDevice.PresentationParameters.BackBufferHeight);
// m_screenShader contiene il codice compilato dell'effetto allegato in questo capitolo
m_screenShader.Parameters["texelShift"].SetValue(texelShift);
m_screenShader.Parameters["sourceTex"].SetValue(texture);
// Renderizza il rettangolo come un trianglefan composto da due triangoli
m_screenShader.Begin(SaveStateMode.None);
foreach (EffectPass pass in m_screenShader.CurrentTechnique.Passes)
{
pass.Begin();
Globals.gd.DrawUserPrimitives<Vertex>(PrimitiveType.TriangleFan, Quad, 0, 2);
pass.End();
}
m_screenShader.End();
È importante sottolineare che il rettangolo è una geometria come le altre ed è quindi soggetto alle impostazione dei RenderStates correnti; per visualizzarlo correttamente ed evitare interferenze occorre aggiungere alle tecniche di shading i seguenti parametri:
40 technique opaqueScreenQuad
41 {
42 pass P0
43 {
44 AlphaBlendEnable = false;
45 ZEnable = false;
46 ZWriteEnable = false;
47 CullMode = None;
48 VertexShader = compile vs_2_0 screenQuadVS();
49 PixelShader = compile ps_2_0 screenQuadPS();
50 }
51 }
52
53 technique blendedScreenQuad
54 {
55 pass P0
56 {
57 AlphaBlendEnable = true;
58 ZEnable = false;
59 ZWriteEnable = false;
60 CullMode = None;
61 VertexShader = compile vs_2_0 screenQuadVS();
62 PixelShader = compile ps_2_0 screenQuadPS();
63 }
64 }
Ho deciso di corredare questo capitolo con quanto più codice esplicativo possibile principalmente perchè l'argomento non è intuitivo e per fugare qualsiasi dubbio riguardo ciò che viene spiegato nel testo. L'argomento corrente di discussione è anche la parte principale di qualsiasi effetto in post-processo ed è di vitale importanza averlo chiaro in mente quando si affronteranno i capitoli successivi.