Home Page Twitter Facebook Feed RSS
NotJustCode
Apri

Environment Mapping 634 Visite) DirectX 11

Le cubemap sono particolari rappresentazioni di texture esistenti in DirectX ormai da molte versioni. Come dice il nome, una cubemap è una texture che contiene al sue interno 6 superfici quadrate disposte a cubo.

Il motivo per cui queste sono state create è sostanzialmente per simulare riflessioni dell'ambiente circostante gli oggetti (il cosidetto environment mapping). Come ripeto spesso nei miei tutorial, ogni volta che si renderizza un oggetto questo non può sapere cosa c'è intorno e l'unico modo per un oggetto di vedere ciò che c'è intorno a se è quello di renderizzare su una texture da utilizzare successivamente.

 

 http://www.notjustcode.it/public/RenderingsuCubemap_BD8A/environ_thumb1.gif

 

La comodità delle cubemap è che al momento dell'utilizzo all'interno dello shader utilizzerete come coordinata texture un vettore direzione che rappresenta la direzione dal centro della cubemap verso il pixel che vogliamo leggere. Si capisce facilmente che in questo modo basterà calcolare in ogni pixel l'angolo di riflessione ed utilizzarlo in fase di lettura delle texture per ottenere il punto esatto della cubemap.

Come nelle precedenti versioni di DirectX, la cubemap si può utilizzare sia leggendola da file (avrete bisogno di un programma come dxTex per generarle) oppure, cosa più comoda, come render target.

In directX9 si creava una cubemap e poi si renderizzava la scena 6 volte, una volta per ogni faccia, cambiando l'angolo della telecamera.

Se avete letto il precedente tutorial però avrete notato la bellissima funzionalità esistente in DirectX10, quella di poter utilizzare render target indicizzabili su cui mandare in rendering la grafica. Questo è proprio uno dei casi ideali.

Il geometry shader restituirà 6 triangoli per ogni triangolo in entrata, ognuno orientato verso una delle faccie.

A questo punto basterà passare 6 matrici transform allo shader in modo da inquadrare la scena dal centro dell'oggetto che deve riflettere verso i 6 lati ed avrete in un solo pass il rendering di 6 facce (risparmierete quindi l'esecuzione di 5 vertex shader, che non è poco).

L'unica differenza sarà la viewMatrix che sarà generata in quasto modo in base all'indice della faccia.


Matrix view1 = Matrix.LookAtLH(new Vector3(), new Vector3(1, 0, 0), Vector3.UnitY);

Matrix view2 = Matrix.LookAtLH(new Vector3(), new Vector3(-1, 0, 0), Vector3.UnitY);

Matrix view3 = Matrix.LookAtLH(new Vector3(), new Vector3(0, 1, 0), -Vector3.UnitZ);

Matrix view4 = Matrix.LookAtLH(new Vector3(), new Vector3(0, -1, 0), Vector3.UnitZ);

Matrix view5 = Matrix.LookAtLH(new Vector3(), new Vector3(0, 0, 1), Vector3.UnitY);

Matrix view6 = Matrix.LookAtLH(new Vector3(), new Vector3(0, 0, -1), Vector3.UnitY);

In questa porzione di codice la scena è inquadrata rispetto al centro. Basterà sommare la posizione dell'oggetto a ViewAt e ViewTo per spostare il centro.

 

 Per creare un render target che rappresenti una cubemap basterà settare il flags in questo modo

 

Texture2D target = new Texture2D(device.Device, new Texture2DDescription()

{

Format = format,

Width = size,

Height = size,

ArraySize = 6,

BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,

CpuAccessFlags = CpuAccessFlags.None,

MipLevels = 1,

OptionFlags = ResourceOptionFlags.TextureCube,

SampleDescription = new SampleDescription(1, 0),

Usage = ResourceUsage.Default,

});

RenderTargetView target = new RenderTargetView(Device, target);

ShaderResourceView resource = new ShaderResourceView(device.Device, target);

target.Dispose();

var _zbufferTexture = new Texture2D(Device.Device, new Texture2DDescription()

{

Format = Format.D16_UNorm,

ArraySize = 6,

MipLevels = 1,

Width = size,

Height = size,

SampleDescription = new SampleDescription(1, 0),

Usage = ResourceUsage.Default,

BindFlags = BindFlags.DepthStencil,

CpuAccessFlags = CpuAccessFlags.None,

OptionFlags = ResourceOptionFlags.TextureCube

});

DepthStencilView  zbuffer = new DepthStencilView(Device, _zbufferTexture);

_zbufferTexture.Dispose();

Sono stati così creati un render target ed un depthbuffer formati da 6 elementi disposti a cubo.

Nello shader la texture andrà dichiarata come TextureCube e in fase di lettura passare alla istruzione Sample un float3.

Qui potete vedere un esempio di geometry shader per renderizzare su una cubemap

 

struct GS_INPUT
{

float4 Pos : SV_POSITION;
float3 normal:NORMAL;
float3 worldPos:WORLDPOSITION;
float2 tex:TEXCOORD;

};

struct PS_INPUT
{

float4 Pos : SV_POSITION;
float3 normal:NORMAL;
float3 worldPos:WORLDPOSITION;
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.worldPos=input[v].worldPos;
output.tex = input[v].tex;
output.normal = input[v].normal;
CubeMapStream.Append( output );

}
CubeMapStream.RestartStrip();

}

}

 

transformMatrixArr è un array di 6 transform matrix.

Le cubemap sono utili oltre che per le riflessioni sugli oggetti anche per rappresentare il cielo, le parti di una scena intorno all'osservatore e via dicendo.

 

Vi rimando al tutorial numero 16 della serie (Github o in fondo dal primo tutorial)