Le tecniche di post processing sono tutti quegli effetti che si basano sull'utilizzo di texture che contengono il rendering della scena. Un esempio è il blur (la sfocatura dell'immagine). Tutta la scena viene renderizzata su una texture che poi viene applicata su un rettangolo e renderizzata con uno shader che restituisce per ogni pixel la media dei pixel circostante. Questo è possibile solo usando una texture contenente la scena in quanto è l'unico modo per poter leggere i pixel intorno a quello che stiamo per renderizzare.
Molte tecniche comunque si basano sulla necessità di avere molteplici rendering contemporaneamente. Non è difficile nei giochi moderni avere scene che necessitano di 2, 4 o anche più versioni della scena (ad esempio contenenti solo le normali, la distanza dalla telecamera etc). Utilizzando il render target su una singola texture è necessario ripetere il rendering tante volte quante sono le scene processando la geometria tante volte. Per ovviare a questo fin da DirectX9 è stato introdotto il rendering su multi texture, ossia la possibilità di renderizzare la scena su 4 texture contemporaneamente.
Questo avveniva utilizzando un pixel shader che restituisse 4 colori, anzichè 1, ognuno da riempire a seconda delle proprie necessità.
Direct3D10 non solo potenzia questo processo portando ad 8 il numero di render target da poter utilizzare contemporaneamente ma supera anche il limite che c'era in DirectX9.
In DirectX9 infatti era possibile cambiare solo il colore del modello, ma non la forma o l'inquadratura. In DirectX10 invece è possibile mandare in ogni target una diversa geometria utilizzando il geometry shader per indirizzare la grafica. Potrete ad esempio in un'unico rendering inquadrare la scena da 6 punti differenti e mandarli a video in un'unico rendering.
In questo tutorial vi spiegherò come utilizzare il multi render target sia in maniera tradizionale che con la nuova funzionalità di "indicizzazione del target".
Multiple Render Target
Come avrete già visto nel precedente tutorial, ed in definitiva fin dal primo tutorial di DirectX10, al device va passato il bersaglio del suo rendering. Si nota subito che è possibile passare più di un elemento creando un array di RenderTargetView
Semplicemente dovrete creare da 1 ad 8 oggetti ID3D10RenderTargetView e ID3D10ShaderResourceView passandoli al device come
device->GetDevice()->OMSetRenderTargets( count, view,renderTargetDepth);
dove count è il numero di target e view l'array di RenderTargetView.
Il depthStencil invece va creato in maniera differente.
D3D10_TEXTURE2D_DESC descDepth;
descDepth.Width = width;
descDepth.Height = height;
descDepth.MipLevels = 1;
descDepth.ArraySize = count;
descDepth.Format = DXGI_FORMAT_D32_FLOAT;
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D10_USAGE_DEFAULT;
descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;
ID3D10Texture2D *pDepthBuffer;
device->CreateTexture2D( &descDepth, NULL, &pDepthBuffer );
D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
descDSV.Format = descDepth.Format;
descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2DARRAY;
descDSV.Texture2DArray.ArraySize=count;
descDSV.Texture2DArray.MipSlice = 0;
descDSV.Texture2DArray.FirstArraySlice=0;
descDSV.Texture2DArray.MipSlice=0;
device->CreateDepthStencilView( pDepthBuffer, &descDSV, &renderTargetDepth );
pDepthBuffer->Release();
La differenza è il tipo che diventa D3D10_DSV_DIMENSION_TEXTURE2DARRAY, ossia un array di texture, ed il valore arraySize che ora è uguale al numero di elementi. Gli array di texture sono speciali texture contenenti una serie di texture. In questo modo ci saranno N depth of field contemporaneamente.
Nel Pixel Shader dovrete a questo punto utilizzare una struttura di float4 contenenti i target che volete restituire.
struct Targets
{
float4 C0:SV_TARGET0;
float4 C1:SV_TARGET1;
float4 C2:SV_TARGET2;
};
Il pixel shader dovrà restituire una struttura come questa, valorizzando a piacere i vari elementi (in questo caso 3). Il resto avverrà come di consueto.
Indexed Multiple Render Target
Questa è la vera novità di DirectX10. Tramite questa tecnologia potrete duplicare ogni triangolo, modificarlo e mandarlo ad un target differente. In questo caso anche il renderTargetView va creato come array di texture
D3D10_TEXTURE2D_DESC desc;
ZeroMemory( &desc, sizeof(desc) );
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = count;
desc.Format = format;
desc.SampleDesc.Count = 1;
desc.Usage = D3D10_USAGE_DEFAULT;
desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
ID3D10Texture2D* targetTex;
device->CreateTexture2D(&desc,NULL,&targetTex);
D3D10_RENDER_TARGET_VIEW_DESC DescRT;
DescRT.Format = format;
DescRT.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2DARRAY;
DescRT.Texture2DArray.MipSlice = 0;
DescRT.Texture2DArray.ArraySize = count;
DescRT.Texture2DArray.FirstArraySlice = 0;
device->CreateRenderTargetView( targetTex, &DescRT, &view);
D3D10_SHADER_RESOURCE_VIEW_DESC srDesc;
srDesc.Format = desc.Format;
srDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2DARRAY;
srDesc.Texture2DArray.MostDetailedMip = 0;
srDesc.Texture2DArray.MipLevels = 1;
srDesc.Texture2DArray.ArraySize = count;
srDesc.Texture2DArray.FirstArraySlice = 0;
device->CreateShaderResourceView( targetTex, &srDesc, &resource );
Anche in questo caso la differenza è nel tipo (TEXTURE2DARRAY) e nella dimensione del target. Ora però nel momento in cui passerete questa risorsa al device tramite OMSetRenderTargets dovrete utilizzare come valore 1 anzichè il numero di target. Questo perchè in realtà la risorsa è solamente una, perchè sarà lei internamente a contenere i vari target.
All'interno dello shader sarete costretti ad utilizzare il geometry shader. Qui dovrete utilizzare come output la struttura di cui avrete bisogno (contenente comunque un vettore SV_POSITION contenente la posizione) a cui aggiungere un intero SV_RenderTargetArrayIndex. Sarà il valore di questo a stabilire su quale target mandare la scena.
struct PS_INPUT
{
float4 Pos : SV_POSITION;
float3 normal:NORMAL;
float2 tex:TEXCOORD;
uint RTIndex : SV_RenderTargetArrayIndex;
};
[maxvertexcount(24)]
void GS_CUBE( triangle GS_INPUT input[3], inout TriangleStream CubeMapStream )
{
for( int f = 0; f < 6; ++f )
{
PS_INPUT output;
output.RTIndex = f;
for( int v = 0; v < 3; v++ )
{
output.Pos = mul( input[v].Pos, transformMatrixArr[f] );
output.tex = input[v].tex;
output.normal = input[v].normal;
CubeMapStream.Append( output );
}
CubeMapStream.RestartStrip();
}
}
Il pixel shader invece utilizzerà come output un solo vettore float4 (è l'indice a stabilire dove va il rendering).
A questo punto cambia anche l'utilizzo del render target. Infatti non avrete N ShaderResourceView, ma una sola contenente un array di texture. Nello shader dovrete utilizzare come tipo Texture2DArray.
Gli array di texture vengono letti sempre con l'istruzione Sample ma passando come coordinate un float3 in cui la Z è l'indice del target (da 0 ad N-1). Da codice invece nessuna differenza, lo shader resource view sarà passato allo shader come di consueto.
Vi lascio ad un semplice demo che renderizza su un cubo il risultato di un render target multiplo su 6 target, ognuno con una diversa inquadratura.
Demo
I commenti sono disabilitati.