Você está na página 1de 45

Deferred Shading in Callisto

Callisto ENGINE
Deferred Shading in Callisto

Tabla de contenido
INTRODUCCIÓN............................................................................. .................................4
SINGLE PASS – MULTIPLE LIGHTS.....................................................................................4
MULTIPLE PASSES – MULTIPLE LIGHTS................................................................................5
DEFERRED SHADING................................................................................................ .....6
HISTORIA........................................................................................... ...................7
G-BUFFER........................................................................................................ ...........9
MULTIPLE RENDER TARGETS....................................................................................... .....9
POSITION............................................................................. ..................................13
NORMAL........................................................................................ ........................13
ALBEDO......................................................................................................... ........15
GBUFFER PASS............................................................................................. ..............17
Deferred Shading in Callisto

VERTEX SHADER........................................................................................ ...............17


PIXEL SHADER.............................................................................. ...........................18
SHADING PASS.................................................................................................. ..........19
VERTEX SHADER........................................................................................ ...............20
PIXEL SHADER.............................................................................. ...........................21
COMPOSICIÓN FINAL...................................................................................................23
OPTIMIZACIONES..........................................................................................................24
DISEÑO DEL G-BUFFER.................................................................................... ...........24
EMPAQUETADO DE ATRIBUTOS................................................................................... ...26

36
EYE-SPACE NORMALS......................................................................................... .....26
POSITION FROM DEPTH............................................................................................28
VOLÚMENES DE LUZ.................................................................................................... 32
FASE DE ACTUALIZACIÓN..........................................................................................32
FASE DE RENDERIZADO.............................................................................. ..............35
Deferred Shading in Callisto

35
Introducción

El cálculo de iluminación de una escena es un aspecto que ha evolucionado de


una manera espectacular gracias al incesante progreso del hardware gráfico.

Hace algunos años la iluminación de un escenario consistía en el cálculo por


vértice de 1-2 luces que solían proyectar sombras planas y se complementaban
mediante el uso de lightmaps. Por el contrario los juegos actuales hacen uso de
todo el potencial gráfico donde cada objeto en escena es iluminado
simultáneamente por varias luces, dando lugar a una maraña de complejos
efectos de sombreado a nivel de pixel todo ello en tiempo real. (Ilustración 1 -
Neverwinter Nights (2002) VS Mass Effect (2008) )

I LUST RACIÓN 1 - N EVERWINTER N IGHT S (2002) VS M ASS E FFECT (2008)

Se puede afirmar que son dos las opciones especialmente utilizadas a la hora de
realizar todos los cálculos de iluminación en tiempo real para una escena:
ejecutar una única pasada y calcular todas las luces en la misma (single pass-
multiple lights) ó realizar el cálculo en varias pasadas (multiple pass-multiple
lights).

Estas técnicas se engloban con el sobrenombre de forward shading y cada una


Deferred Shading in Callisto

de ellas conlleva ciertos inconvenientes que se describen a continuación.

Single Pass – Multiple Lights

La primera opción de iluminación en tiempo real consiste en utilizar una única


pasada con el fin de calcular la aportación final de todas las luces sobre un
mismo objeto.

36
El siguiente seudocódigo ilustra el enunciado anterior:

for each object do


for each light do in a single shader
framebuffer = brdf(object,light);

Se trata de la aproximación más simple de las dos, por lo que posee algunas
limitaciones e inconvenientes:

o Ya que la iluminación es computada por objeto, puede llegar a ocurrir que


se realice un proceso de renderizado y sombreado de la superficie
correspondiente a un objeto y que posteriormente sea cubierta por la
superficie correspondiente a otro, por lo que se habrá realizado una
computación innecesaria.

o Otro problema se traduce en la cantidad de combinaciones resultantes al


intentar recrear en una única plantilla los distintos tipos de
luces/materiales resultantes. Como todo el proceso de sombreado se
realiza en un solo shader, que posee una cantidad limitada de
instrucciones, esta técnica solamente es viable para un número reducido
de luces.

o Por último, su integración con sombras es bastante complicada y la


utilización de shadow maps puede llegar a consumir bastante VRAM.

Multiple Passes – Multiple Lights

En ésta segunda opción todos los cálculos son realizados por fuente de
iluminación, de tal forma que en cada pasada, para cada luz existente en escena,
todos los objetos influenciados por la misma son sombreados.

Como en el ejemplo anterior se proporciona el siguiente seudocódigo:

for each light do


Deferred Shading in Callisto

for each object affected by light do


framebuffer += brdf(object,light);

Asimismo se detectan algunos problemas o limitaciones:

o Como en el caso anterior, se realizan cálculos innecesarios por redibujado


de pixeles.

35
o En cada pasada que se envía el mismo objeto (ya que puede ser iluminado
por varias luces a la vez), éste es procesado nuevamente por el vertex
shader por lo que se generan una y otra vez las mismas transformaciones.
De la misma forma, en cada una de las pasadas se vuelven a aplicar los
filtros anisotrópicos.

o Por último, esta técnica produce un elevado batching (número de draw


calls o llamadas a dibujado) que en el peor de los casos es del orden
O(num_lights * num_objects).

Deferred Shading

Para representar un efecto complejo, se requerirán varias pasadas para computar


el color final de cada pixel. Si se utilizan las dos técnicas descritas anteriormente,
se repetirá el envío de la geometría una vez por cada pasada necesaria.

A diferencia de las técnicas anteriores en las que se envía la geometría e


inmediatamente se aplican los shaders correspondientes, la técnica denominada
deferred shading consiste en enviar el grueso de geometría de la escena
únicamente una vez, almacenando en dicha pasada todos los atributos de
iluminación (posición, normales, color, etc.) en memoria de video local (G-Buffer)
de tal forma que se puedan usar en las siguientes pasadas.

En estas últimas pasadas mencionadas se creará un rectángulo alineado en


pantalla en el que, mediante un postproceso en 2D donde se utilizan los atributos
del G-Buffer como entrada, se calculará el color resultante para cada pixel.

De esta forma, la gran ventaja de esta técnica respecto a las comentadas con
anterioridad radica en el hecho de que se reduce sobremanera la complejidad
computacional, siendo en el peor de los casos del orden de O(num_lights +
num_objects).

El hecho de reducir toda esa tasa de transferencia de vértices que antes se


procesaba en cada pasada y ahora solamente es enviada en la correspondiente a
la creación del G-Buffer, podrá servir para incrementar la cantidad de polígonos
en la escena aumentando el realismo de la misma sin comprometer al
rendimiento.
Deferred Shading in Callisto

Tampoco existirá un sobre redibujado de pixeles (solo en la pasada en la que se


rellena el G-Buffer) ya que el color de cada pixel es calculado solamente una vez.

El siguiente seudocódigo ilustra de forma clara la naturaleza de la técnica:


for each ob jec tdo
render lighting properties of object to G-Buffer;
for each l i gh tdo
framebuffer += brdf (G-buffer,light);

36
Historia

La técnica fue inicialmente introducida en 1988 por Michael Deering et al en el


SIGGRAPH. En dicho trabajo, aparte de no mencionar en ningún caso el término
“deferred”, los autores proponían un sistema VLSI (Very Large Scale
Integration ) donde un pipeline procesaba la geometría mientras otro pipeline se
encargaba de aplicar Phong shading, con varias fuentes emisoras de luz, sobre
dicha geometría.

Posteriormente al estudio inicial de Deering et al, el


siguiente trabajo con relevancia dentro del ámbito
de la técnica deferred shading corresponde al
realizado por Saito and Takahashi en 1990. Los
autores proponen una técnica de renderizado que
generaba imágenes 3D y las mejoraba utilizando
líneas, patrones, discontinuidades, bordes,
obtenidos a partir de las diferentes propiedades de
la geometría (normales, profundidad, etc.)
almacenadas en buffers de geometría (G-Buffers).

Otro estudio relacionado con la técnica fue el


realizado por Ellsworth en 1991, que investigó
arquitecturas paralelas y algoritmos para síntesis
en tiempo real de imágenes en alta calidad usando
deferred shading.

I LUST RACIÓN 2 – R ENDER ING OF


3 D - SHAPES (S AITO AND T AKAHASHI )

Poco más tarde, en 1992, el grupo de investigación de gráficos por computador


UNC propuso la arquitectura PixelFlow para la generación de imágenes a alta
velocidad. En dicho artículo Molnar et al usaban deferred shading para reducir los
cálculos realizados en los complejos modelos de sombreado utilizados en su
arquitectura.

Despues de los artículos mencionados han existido otras investigaciones y


desarrollos que han utilizado la técnica deferred shading ( Nicolas Thibieroz,
Shawn Hargreaves, Randima Fernando, Ariel Brunetto, Frank Pluig entre otros).
Deferred Shading in Callisto

Recientemente, Oles
Shishkovtsov ha escrito un
capítulo en el libro “GPU Gems
2” en el que describe los
detalles de la técnica deferred
shading usada en el juego
S.T.A.L.K.E.R.

Aún más reciente es el artículo


escrito por Rusty Koonce en el
libro “GPU Gems 3”,

35
continuación obligada al escrito por Shishkovtsov. Mientras que éste último cubre
los aspectos fundamentales de la técnica, el artículo de Koonce enfatiza los
problemas, técnicas y soluciones encontradas mientras se trabajaba en la
realización del motor de renderizado del juego “Tabula Rasa”, basado en la
técnica deferred shading.
I LUSTRACIÓN 3 – S.T.A.L.K.E.R. S HADOW OF C HERNOBYL

Para finalizar existen


recientes técnicas (Light
Indexed Deferred Lighting
[Damien Trebilco 2007] y
Light Pre-Pass Rendering,
[Wolfgang Engel 2008] )
basadas en deferred
shading que intentan
solucionar varios
problemas derivados de
su naturaleza, como es el
caso de transparencias,
AA y poder gestionar
varios BRDF’s en la
escena. En ellas se utiliza el G-Buffer para almacenar otro tipo de información y
utilizarla en una pasada posterior usando forward rendering.

I LUSTRACIÓN 4 - T ABUL A R ASA

Deferred Shading in Callisto

36
G-Buffer

En contraposición al comportamiento de cualquier sistema basado en forward


rendering, en uno basado en deferred shading, tanto la iluminación como los
demás efectos no son calculados en la misma pasada en la que se envía y
procesa la geometría de la escena.

Existirá en este caso una primera pasada en la que la geometría es procesada y


todos los atributos asociados a la misma como posición, normales, albedo, etc.,
son almacenados en varias texturas formando un buffer auxiliar denominado G-
Buffer. Dichas texturas serán utilizadas en sucesivas pasadas para realizar todos
los cálculos correspondientes al color de cada pixel sin necesidad de volver a
enviar de nuevo la totalidad de geometría de la escena.

Para almacenar atributos como la posición, donde es necesario un elevado rango


de representación, se hará casi obligatorio el uso de texturas de punto flotante.
Aunque existen diferentes métodos de empaquetado para almacenar en texturas
de menor precisión, la mayoría de los sistemas actuales soportan este tipo de
texturas de punto flotante.

Multiple Render Targets

Anteriormente (DirectX 8) solo se podía escribir en una sola textura (render


target) utilizando un máximo de 32 bits consistentes en 4 componentes de color
(8 bits por componente).

RT R G B A RT

Por lo tanto si se opta por utilizar un único render target, se deberán realizar
múltiples pasadas con el fin de almacenar todos los atributos seleccionados:
Deferred Shading in Callisto

position, normals, albedo, etc., por lo que habría que enviar en cada pasada toda
la geometría resultando una técnica casi ineficiente, cercana al comportamiento
y rendimiento de un forward renderer.

DirectX 9 aportó una nueva característica denominada Multiple Render Targets


(MRT) en la que se permitía utilizar hasta cuatro diferentes render targets en los
que escribir en una única pasada, y aumentando la precisión anterior de 32
bits a 512 bits (varía respecto al dispositivo utilizado).

35
De esta forma se podrían empaquetar los atributos necesarios del G-Buffer en
MRTs, organizándolos de forma inteligente, de tal manera que se consiguiesen
escribir en una sola pasada y en el menor número de texturas posibles.

En general los MRTs poseen las siguientes limitaciones o características:

o Deben poseer el mismo tamaño. Esto no es del todo cierto, ya que se


podrá utilizar una profundidad de bits diferente si el dispositivo soporta
D3DPMISCCAPS_MRTINDEPENDENTBITDEPTHS. Prácticamente todas las
tarjetas del mercado soportan dicha característica.

o Se pueden mezclar RTs con diferente número de canales, es decir, se


podría realizar la siguiente configuración de MRTs:

 RT0 : G16R16F

 RT1 : A8R8G8B8

 RT2 : R32F

o Dithering, alpha testing, fogging, blending, o masking solamente podrán


ser utilizados si el dispositivo tiene marcado el bit
D3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING.

o No soportan MSAA (MultiSample Anti-Aliasing) por hardware. En DirectX


10, como se comentará más adelante, se solventa éste problema. Mientras
tanto en DirectX 9 se deberán utilizar otros métodos más intrusivos para
obtener una aproximación al AA.

El uso de todos estos MRTs da como resultado la utilización de gran cantidad de


memoria VRAM así como de ancho de banda del dispositivo. Cuando se diseña un
sistema basado en deferred shading habrá que tener especial cuidado en
seleccionar qué valores son almacenados en el G-Buffer y sobre todo de qué
forma serán almacenados para reducir tanto VRAM como ancho de banda.
Deferred Shading in Callisto

Existen varias técnicas para optimizar el empaquetado y diseño del G-Buffer,


todas ellas se comentarán en posteriores apartados (Ver Optimizaciones).

Un posible diseño del G-Buffer, bastante simple y sin optimizar, correspondería al


que se muestra a continuación:

RT0 POSITION.X POSITION.Y


G16R16F

36
RT1 POSITION.Z LIBRE
RT3
RT2 COLOR.R
LIBRE NORMAL.X
COLOR.G NORMAL.Y
COLOR.B NORMAL.Z
G16R16F

A2RGB1
0

LIBRE RGBA8

En total serían 128 bits por pixel. A una resolución de 1024x768 correspondería a
un total de 12 MB de memoria VRAM. Éste no sería el principal inconveniente
dada la gran cantidad de memoria que proporciona el hardware actual.

La mayor desventaja y la que proporciona un mayor riesgo de producir


bottlenecks en el rendimiento de la técnica corresponde al ancho de banda
requerido en cada fotograma aunque, como ocurre en el caso anterior, este
inconveniente queda mitigado gracias a la potencia que proporcionan los
dispositivos 3D actuales.

La siguiente tabla representa un cálculo aproximado del ancho de banda


necesario, es decir, la cantidad de memoria que es necesario transferir por el bus
del dispositivo en cada fotograma, para un escenario particular de 1024x768 y
diferentes opciones de bpp.

32 bpp 64 bpp 128 bpp


nMRTs = 2 14 GB/seg 18 GB/seg 26 GB/seg
nMRTs = 3 16 GB/seg 22 GB/seg 34 GB/seg
nMRTs = 4 18 GB/seg 26 GB/seg 42 GB/seg

Si estos valores son comparados con un listado de características


Deferred Shading in Callisto

correspondientes a tarjetas gráficas de la serie 8 de NVIDIA, podemos comprobar


cómo, para dispositivos de gama media/baja dentro de la serie, se hace patente
las limitaciones impuestas por el ancho de banda a utilizar.

35
A continuación se describirá el proceso de creación de cada una de las
superficies de almacenamiento que forman el G-Buffer final, siempre siguiendo el
ejemplo de configuración anterior donde los atributos que se mapean
corresponden a position, normals y diffuse albedo.

Deferred Shading in Callisto

36
Position

Corresponde a la posición del pixel y puede estar representada en diferentes


sistemas de coordenadas, aunque las más lógicas corresponden a view/world
space.

En el ejemplo se han utilizado coordenadas en view space, por lo que ha sido


necesario multiplicar cada vértice en el vertex shader por la matriz de
transformación correspondiente. Ya en el pixel shader se almacenará cada
coordenada (x,y,z) en la RT correspondiente.

Las siguientes imágenes muestran las texturas correspondientes a RT0 y RT1, en


las que se han mapeado (position.x, position.y) y (position.z) respectivamente:

I LUST RACIÓN 5 - RT0 ( POS . X , POS . Y ) Y RT1 ( POS . Z )

Aunque la textura en la que se empaqueta la coordenada z del pixel presente un


color constante, al tratarse de una textura de punto flotante estará almacenando
un mayor rango de valores que el aparentemente representado.

Normal
Deferred Shading in Callisto

Se trata del vector normal correspondiente a cada pixel almacenado en view


space.

Como es costumbre, se utilizarán mapas de


vectores normales definidos en tangent space (ó
texture space), que no viene a ser más que otro
sistema de coordenadas en el que se indica la
orientación de la superficie de la textura en cada
vértice.

35
A partir de dicho sistema de coordenadas se podrán realizar técnicas como bump
mapping ó incluso parallax mapping/relief mapping que serán comentadas en
apartados posteriores.

En la ilustración 6 se puede observar un ejemplo de mapa de normales en


tangent space en el que se representan los detalles que se van a añadir al
conjunto de polígonos con el fin de dar el aspecto final de una pared de ladrillos.
I LUSTRACIÓN 6 - M APA Volviendo al caso de ejemplo que se expone en el presente
DE NORM AL ES EN
T ANGENT S PACE
apartado, ya que el sistema utilizado corresponde a coordenadas
en view space, habrá que obtener los vectores normales en dicho sistema.

Para ello habrá que formar la matriz tangencial utilizando los vectores tangent,
binormal y normal que a su vez estarán en view space.

Por último habrá que multiplicar el vector normal obtenido de samplear el mapa
de normales por la matriz tangencial anterior, resultando el vector normal en el
sistema de coordenadas deseado:

Posteriormente, el vector resultante de la ecuación anterior será normalizado


para no perder precisión en los cálculos posteriores.

De la misma manera se suelen utilizar texturas de formato coma flotante (16F)


para almacenar con suficiente exactitud el valor obtenido, aunque es más
económico y la mayoría de las ocasiones resulta suficiente disponer de una
textura con formato entero de 32 bits (A2RGB10). De esta forma los canales
RGB, los tres de 10 bits cada uno, se utilizarán para almacenar las coordenadas
(x,y,z) del vector normal, quedando el canal A libre.
Deferred Shading in Callisto

La siguiente imagen muestra la RT2 correspondiente al G-Buffer, en la que se ha


mapeado N (normal.x, normal.y, normal.z) en los canales RGB, dejando libre el
canal Alfa:

36
I LUSTRACIÓN 7 - RT2 ( VIEW SPACE NORMAL S )

Albedo

El color de cada pixel correspondiente a la componente difusa (color propio del


objeto) es tomado de la textura denominada comúnmente albedo map asociada
al modelo.

Como cada componente de color RGBA se puede representar en un rango 0-255,


se utilizará una textura de 32 bits, con 8 bits por cada canal.

La siguiente imagen muestra la RT3 correspondiente al G-Buffer, en la que se ha


mapeado D (diffuse.x, diffuse.y, diffuse.z) en los canales RGB, dejando libre el
canal Alfa:
Deferred Shading in Callisto

35
I LUSTRACIÓN 8 - RT3 ( AL BEDO MAP )

De la misma forma se podrían almacenar otro tipo de atributos en los canales no


utilizados, como mapas que definan la componente especular (specular power /
specular intensity).

Una vez definida la composición y estructura del G-Buffer de forma teórica, se


procederá a mostrar en el siguiente apartado de una forma absolutamente
práctica el proceso de creación de cada una de las texturas que forman el FAT
Buffer.

Deferred Shading in Callisto

36
GBuffer Pass

La primera pasada se encargará de almacenar los atributos seleccionados


(position, normal, albedo) en cada una de las textura que forman el G-Buffer.

Vertex Shader

El vertex shader es realmente simple. Aparte de transformar la posición para


expresarla en clip space (AKA screen space), se propagan otra serie de vectores:

o Se transforma el vector posición en view space que corresponderá al valor


almacenado en la RT0/RT1.

o Normal, binormal, tangent se transforman de object space a view space


mediante la matriz de transformación correspondiente. Estos vectores,
como se comentó anteriormente, formarán la matriz utilizada en la
creación del vector normal (en view space) almacenado en RT2.

o Se propaga sin ningún tipo de transformación las coordenadas del albedo


map, que se escribirán en la RT3.

VS_OUTPUT_GBUFFERPASS VS_GBUFFER( VS_INPUT_GBUFFERPASS IN )


{
VS_OUTPUT_GBUFFERPASS Out;

Out.Pos = mul(IN.pos, g_mWorldViewProjection);

float4 pp = mul( IN.pos, g_mWorldView );


Out.WorldPos = pp;
Deferred Shading in Callisto

Out.interPos = mul(IN.pos, g_mWorldView).xyz;


Out.normal = mul(IN.normal, (float3x3)g_mWorldView);
Out.binormal = mul(IN.binormal, (float3x3)g_mWorldView);
Out.tangent = mul(IN.tangent, (float3x3)g_mWorldView);
Out.texCoord = IN.texcoord;

return Out;
}

35
Pixel Shader

Ya en el pixel shader, se generarán los colores que se van a propagar a los MRTs
configurados.

Para obtener el color de la componente difusa albedo (Out.Color) simplemente


habrá que samplear la textura correspondiente a partir de las coordenadas
propagadas desde el vertex shader.

El vector position se propagará sin ningún tipo de cálculo posterior hacia los RTs
correspondientes: Out.PosXY y Out.PosZ.

En cuanto a las normales, habrá que samplear el vector de la textura


correspondiente y transformarlo a view space utilizando la matriz
(objToTangentSpace). El resultado se propagará hacia el RT Out.Normal.

PS_OUTPUT_GBUFFERPASS PS_GBUFFER( VS_OUTPUT_GBUFFERPASS IN )


{
PS_OUTPUT_GBUFFERPASS Out;

float3 base = tex2D(Textura2D0, IN.texCoord);


float3 bump = tex2D(Textura2D1, IN.texCoord) * 2 - 1;
bump = normalize(bump);

float3x3 objToTangentSpace = float3x3( IN.tangent, IN.binormal,


IN.normal );

Out.PosXY = float4(IN.interPos.x, IN.interPos.y, 0, 1);


Out.PosZ = float4(IN.interPos.z, 0, 0, 1);

float3 normal = normalize( mul( bump, objToTangentSpace ) );

//view space bump  texture space ( [-1;+1]  [0;1] )


normal = normal * 0.5 + 0.5;

Out.Normal = float4(normal.x, normal.y, normal.z, 1);


Out.Color = float4(base, 1);

return Out;
}
Deferred Shading in Callisto

36
Shading Pass

Como se había comentado en anteriores apartados, el G-Buffer generado se


utilizará en ésta pasada para realizar el cálculo de iluminación sin necesidad de
enviar de nuevo todo el grueso de geometría de la escena.

Simplemente será necesario un rectángulo alineado en pantalla de tal forma que


cada uno de los pixeles de su superficie presente su homólogo en todas las
texturas del G-Buffer.

Para ello hay que tener en cuenta que los sistemas de coordenadas
correspondientes a pixels y texels en Direct3D 9 difieren en la situación de su
origen de coordenadas.

El primero considera el centro del pixel como el origen del sistema de


coordenadas:

I LUSTRACIÓN 9 - P IXEL C OORDINATE S YSTEM

Mientras que el segundo considera como origen de coordenadas la esquina


superior izquierda del pixel:
Deferred Shading in Callisto

35
I LUSTRACIÓN 10 - T EXEL C OORDINATE S YSTEM

Por lo tanto para alinear correctamente texels con pixels habrá que estipular un
desplazamiento proporcional al tamaño del texel, como se representa en la
siguiente ilustración:

H RECTÁNGULO ALINEADO
EN PANTALLA

W
Deferred Shading in Callisto

Vertex Shader

El vertex shader es tan simple como propagar la posición de los vértices que
forman el rectángulo alineado en pantalla y realizar la corrección de las
coordenadas de textura indicada en el apartado anterior.

36
VS_OUTPUT_SCREENQUAD VS_RenderScreenQuad( VS_INPUT_SCREENQUAD IN )
{
VS_OUTPUT_SCREENQUAD OUT;

OUT.Position = IN.Position;
OUT.TC0 = IN.TC0 + vTexelSize;

OUT.EyeScreenRay = IN.FrustumFar;

return OUT;
}

La variable vTexelSize se ha definido como un float2 de la siguiente forma:

float2 vTexelSize =

float2(1.0f / (2.0f * screenWidth), 1.0f / (2.0f * screenHeight));

Pixel Shader

Por último en el pixel shader se realizarán los cálculos necesarios para computar
la iluminación de cada pixel en pantalla.

La ejecución del pixel shader es individual para cada fuente de luz por lo que se
realizará tantas veces como fuentes de luz existan. El resultando de cada
iteración se irá acumulando en el framebuffer mediante transparencia aditiva
(additive blending).
Deferred Shading in Callisto

35
float4 PS_RenderLight(VS_OUTPUT_LIGHT IN) : COLOR0 {

half3 pos = float3(0,0,0);

pos.xy = tex2D(GBufferTexture1, IN.PosProj).xy;


pos.z = tex2D(GBufferTexture2, IN.PosProj).x;

half3 eyeVec = camPos - pos;


half3 lightVec = (1.0 / PointLightRange) * (PointLightPosition - pos);

half3 normal;
// normales en texture space [0,1] -> convertir a [-1,+1]
normal.xyz = tex2D(GBufferTexture4, IN.PosProj).xyz * 2 - 1;

half4 base = tex2D(GBufferTexture3, IN.PosProj);

half light_l = length(lightVec);


half atten = tex1D(AttenuationMap,light_l).r;

half diffuse = 0.0f;


half specular = 0.0f;

//ambient
half3 lighting = 0.1 * base;

diffuse = saturate(dot(normalize(lightVec), normal));

specular = pow(saturate(dot(reflect(normalize(-eyeVec), normal),


normalize(lightVec))), 16);

lighting = PointLightColor * (diffuse * base + 0.7 * specular) * atten;

return half4(lighting, 1.0f);


}

En el ejemplo se utiliza Phong Shading como modelo de iluminación teniendo en


cuenta una distancia de atenuación para cada fuente de luz.

Esta distancia de atenuación es obtenida de una textura en función de la posición


de la fuente de luz y del rango máximo de iluminación de la misma.

La textura de atenuación es similar a la siguiente: Deferred Shading in Callisto

En resumen, en el pixel shader se realizan las siguientes acciones:

o Se recuperan los atributos almacenados anteriormente en el G-Buffer.

o Se calcula la atenuación de la luz basada en la posición actual y en el


rango máximo de iluminación.

36
o Se realiza el cálculo de iluminación correspondiente al pixel que, a grandes
rasgos, se puede definir como:

En el pixel shader solamente se realiza el cálculo correspondiente a .


La componente se deberá calcular en una pasada diferente y
añadir posteriormente al resultado final.

Composición final

El resultado final generado como salida del pixel shader anterior, para una
escena iluminada por 11 fuentes de luz diferentes, es similar al que presenta la
siguiente figura:
Deferred Shading in Callisto

I LUSTRACIÓN 11 - R ESULTADO FINAL

35
Optimizaciones

El caso de ejemplo presentado en el apartado anterior se puede considerar el


acercamiento más simple a la técnica deferred shading, tanto por el número de
atributos almacenados en el G-Buffer como por el hecho de que no está
optimizado de ninguna manera.

A continuación se describen una serie de optimizaciones aplicables a lo largo del


ciclo de vida de la técnica, desde la creación del G-Buffer hasta el cálculo de la
iluminación, con el fin de mejorar el rendimiento final.

Diseño del G-Buffer

Si se decidiesen almacenar otros atributos en el G-Buffer (specular power,


specular intensity, occlusion terms, motion vectors, etc.), el número de canales
libres en los RTs no serían suficientes.

Tampoco existiría la posibilidad de añadir nuevos RTs, ya que solo se pueden


utilizar simultáneamente los cuatro ya presentes (esto para DX9, en DX10 es
aumentado hasta 8).

Por lo tanto, variar el tamaño de cada uno de los RTs sería una de las opciones a
contemplar. El inconveniente vendría en modo de penalización en cuanto a
memoria VRAM y ancho de banda que se verían aumentados de forma prohibitiva
para algunos dispositivos.

Supongamos que se desean almacenar los siguientes atributos en el G-Buffer:

o Position, 3 x FP16

o Normals, 3 x FP16

o Diffuse Albedo, 3 x I8

o Specular Power, 1 x I8

o Specular Intensity, 1 x I8
Deferred Shading in Callisto

o Motion Vectors, 2 x I8

o Material ID, 1 x I8

En total sumarian 160 bits, 32 bits mayor que el límite que proporciona el diseño
del G-Buffer anterior en el que cada RT era de 32 bits.

36
Si se aumenta el tamaño de cada RT a 64 bits se obtiene la cifra de 192 bits para
3 RTs. Se habría conseguido disminuir el número de texturas en memoria (menor
gasto de VRAM) pero el ancho de banda, que es el que produce mayor riesgo de
bottlenecks, se habría aumentado.

Las optimizaciones que se comentan a continuación consiguen reducir el número


de bits utilizados para almacenar los atributos, reduciendo el ancho de banda
utilizado en cada fotograma.

Hay que considerar que toda esta reducción de espacio conlleva un coste
computacional adicional, por lo que será conveniente analizar detenidamente
que solución merece ser aceptada como la más apropiada en cada caso.
Deferred Shading in Callisto

35
Empaquetado de atributos

A la hora de almacenar ciertos atributos se puede reducir el tamaño utilizado,


siempre teniendo en cuenta que a menor tamaño de almacenamiento, menor
precisión a la hora de realizar los cálculos de iluminación pertinentes y por lo
tanto menor calidad final.

Reducir el número de bits para almacenar un atributo se solía utilizar para


conseguir un G-Buffer de RTs enteros en dispositivos que no soportaban texturas
en coma flotante. Actualmente, la mayoría de los dispositivos presentes en el
mercado (si no todos) soportan este tipo de texturas, por lo que la acción de
empaquetar a menor precisión simplemente tiene como objetivo conseguir
mejorar el rendimiento global de la técnica.

Las siguientes funciones HLSL realizan un proceso de


empaquetado/desempaquetado de FP16  I8:

half2 PackFloat16( half depth )


{
depth /= 4;
half Integer = floor(depth);
half fraction= frac(depth);

return half2( Integer/256, fraction);


}

half UnpackFloat16( half2 depth )


{
const half2 unpack = {1024.0f, 4.0f};

return dot(unpack, depth);


}

Eye-Space Normals

Si se usan vectores unitarios como normales, se puede llegar a calcular un


Deferred Shading in Callisto

componente a partir de los otros dos utilizando la siguiente ecuación:

Como se puede observar el componente calculado puede ser positivo o negativo.


Sin embargo, si todo el cálculo de la iluminación es realizado en view space, en
casi todas las ocasiones los polígonos presentarán un valor positivo o negativo en

36
su componente Z, dependiendo del modo de representación utilizado (OpenGL 
positivo, Direct3D  negativo).

Solamente en contadas ocasiones, en las que el FOV es exageradamente amplio,


el valor de la componente Z puede cambiar de signo.

La siguiente figura ilustra lo expuesto:

N
-
+

EYE

Para evitar en estos casos la incorrecta iluminación producida por el cambio de


signo, se puede reservar un bit de uno de los dos canales que albergan las
componentes x e y para almacenar el signo correspondiente. El RT quedaría así:

o CANAL R (I8/FP16)  8-16 bits para almacenar normal.x

o CANAL G (I8/FP16)  7-15 bits para almacenar normal.y, 1 bit para


almacenar el signo
Deferred Shading in Callisto

La optimización expuesta reduce en un canal el tamaño final para almacenar el


vector normal, a costa de un coste computacional adicional.

Se podría evitar este coste computacional utilizando una textura en la que cada
texel albergase el valor de la componente z. De esta forma al samplear la textura
con los valores u y v correspondientes a las componentes x e y de la normal, se
obtendría la componente z.

35
Esta otra solución plantea el inconveniente de que se aumenta el coste del ancho
de banda utilizado por la aplicación, que es lo que se planteaba en un principio
reducir para mejorar el rendimiento final de la técnica.

Por último, a la hora de almacenar los valores se pueden utilizar los métodos de
empaquetado expuestos en el apartado anterior, indicando de nuevo que, en el
caso de las normales necesarias para el cálculo de iluminación y otras técnicas
como bump mapping, la pérdida de precisión se transforma en una pérdida de
calidad visual notable.

La siguiente tabla, sacada de Shishkovtsov[GPU GEMS 2], presenta todas las


posibles opciones de configuración del RT que alberga las normales:

A8R8G8B A16R16G16B1
A2R10G10B10 R16FG16F
8 6F

Geforce FX or Geforce FX or
Radeon 9500 or better better
All
better Radeon 9500 or Radeon 9500 or
Hardware Support better better

Deferring Cost One mad One mad None None

One mov

One mad One mad One dp2a

One nrm One nrm One rsq

Decoding Cost One rcp

Sampling and Storage 32 bits 32 bits 32 bits 64 bits

Good for rough Excellent


Poor Excellent
Quality surfaces

1 (2 bits, very 1
1 0
Free Components low precision)

Position from Depth

Si recordamos el diseño del G-Buffer del caso de ejemplo, para almacenar el


Deferred Shading in Callisto

vector position se han utilizado 3 canales de 16 bits cada uno (48 bits en total).

El tamaño por componente establecido puede ser suficiente cuando se utiliza un


sistema de coordenadas en view space, pero en world space las distancias
pueden hacerse tan grandes que sea necesario utilizar precisiones de 32 bits, por
lo que trastocarían la estructura del G-Buffer de forma considerable, así como se
aumentarían los dichosos ancho de banda y VRAM que tan de cabeza traen en la
fase de diseño.

36
La solución consiste en almacenar en el G-Buffer solamente la componente z del
vector position en view space, normalizada entre el rango [0.0, distancia al plano
más alejado del view frustum]. De esta forma, en vez de utilizar 48 bits para
almacenar las tres componentes del vector position, se utilizarán 32 bits para
almacenar la componente LESD (linear eye-space depth).

En fase de creación del G-Buffer, más concretamente en el Pixel Shader, la


componente LESD se calcula y propaga posteriormente de la siguiente manera:

float fDepth = IN.ViewPos.z / frustumCoord.z;


Out.Pos = float4(fDepth, 0, 0, 1);

La RT resultante tendrá el siguiente aspecto:

I LUSTRACIÓN 12 - L INEAR E YE -S PACE D EPTH


Deferred Shading in Callisto

Para obtener las componentes x e y a partir del LESD se procederá como se


comenta a continuación.

Cuando se envía el rectángulo alineado en pantalla hacia el vertex shader, aparte


de la información relativa a posición y coordenadas de textura, se pasará un
vector adicional correspondiente a una de las esquinas del plano más alejado del
view frustum.

La esquina seleccionada del view frustum se corresponderá con la esquina del


rectángulo alineado como indica la siguiente imagen:

35
FAR FRUSTUM
PLANE

RECTÁNGULO
ALINEADO EN
PANTALLA

Al propagar el vector desde el vertex shader hacia el pixel shader, se verá


interpolado de tal forma que se corresponderá con el pixel a calcular:

FAR FRUSTUM
PLANE

RECTÁNGULO
ALINEADO EN
PANTALLA

Por último para recuperar el vector position original, bastará con multiplicar el
Deferred Shading in Callisto

vector por la profundidad obtenida del G-Buffer como se muestra a


continuación:

36
El siguiente bloque de código corresponde a los cambios realizados tanto en el
vertex shader como en el pixel shader correspondientes a la segunda pasada
(cálculo de iluminación):

//VERTEX SHADER

//FrustumFar  esquina correspondiente al plano más alejado del view frustum


OUT.EyeScreenRay = IN.FrustumFar;

//PIXEL SHADER

//Se obtiene la profundidad correspondiente al pixel


float pDepth = tex2D(GBufferTexture1, IN.TC0).r;
//Vp = Pfp x Pdepth
pos = (IN.EyeScreenRay * pDepth);

Gracias a ésta optimización se ha reducido el número de bits utilizados en la


construcción del G-Buffer final del ejemplo, así como el número de RTs de
almacenamiento.

La estructura resultante es similar a la siguiente:

o RT0  Depth : R32F (32 bits, nada libre)

o RT1  Normals (x,y) + sign bit : G16R16F (32 bits, nada libre)

o RT2  Albedo : A8R8G8B8 (32 bits, 1 canal de 8 bits libre)

En total 96 bits organizados en 3 RTs, dando lugar a un diseño de G-Buffer de lo


más optimizado posible.

Si al final se adopta la estructura propuesta en el apartado Diseño del G-Buffer se


necesitaría solamente un RT más cuya composición sería la siguiente:
Deferred Shading in Callisto

o RT3  Specular Intensity (8 bits), Motion Vectors (16 bits), Material Id (8


bits) : A8R8G8B8 (32 bits, nada libre)

Y el canal libre de RT2 albergaría el atributo resultante, Specular Power, de 8 bits.

Se obtendría pues un diseño muy completo de G-Buffer compuesto por un gran


número de atributos, en solo 4 RTs y con un total de 128 bits por pixel, margen
adecuado para evitar un gasto desorbitado de VRAM o el consumo peligroso de
ancho de banda de sistema.

35
Volúmenes de luz

Se ha visto como optimizando el diseño del G-


Buffer se consigue reducir el ancho de banda
total en cada fotograma.

Una vez generado el G-Buffer, éste es


utilizado en cada pasada correspondiente al
cálculo de iluminación para computar el color de cada uno de los pixeles de la
pantalla.

Ya que cada fuente de luz posee una atenuación determinada, existirán pixeles
en pantalla que no estarán influenciados por ninguna luz, por lo tanto el cálculo
realizado para los mismos se transforma en una pérdida de rendimiento bastante
acusada.

De esta forma, en vez de aplicar el pixel shader para todos los pixeles en
pantalla, se aplicará a todos aquellos que estén influenciados al menos por un
volumen de luz.

Existen varios tipos de fuente de luz, cada uno de ellos se puede asociar con una
primitiva que representa su volumen final:

o Point Lights, que transmiten luz con la misma intensidad en todas


direcciones. Su volumen de luz corresponde a una esfera.

o Spot Lights, que transmiten luz en una dirección fija determinada mediante
un cono, que a su vez representa su volumen.

o Directional lights, cuya luz se transmite por la escena en una misma


dirección. Como ejemplo se puede considerar la luz del sol, que no sufre de
atenuación y cuyo volumen es un rectángulo que cubre la pantalla entera.
Deferred Shading in Callisto

Una vez descritos los diferentes volúmenes de luz, se pueden definir dos fases
diferentes a la hora de establecer el proceso de optimización en el periodo de
iluminación: fase de actualización y fase de renderizado.

Fase de Actualización

En la fase de actualización se intenta reducir el número de fuentes de luz que


afectan a la escena, aplicando uno a uno los siguientes enunciados:

36
 Se descartarán las luces cuya influencia no sea apreciable una vez
aplicados los algoritmos de visibilidad y oclusión apropiados. Los
volúmenes de luz que queden completamente ocultos por otros elementos
de la escena o que estén fuera del view frustum no entrarán a formar
parte del cálculo de iluminación.

 Se proyectarán los volúmenes de luz en pantalla (screen space).

 Todas aquellas fuentes de luz que afecten a una misma región de pixeles
se podrán combinar de tal forma que la fuente resultante proporcione una
intensidad que simule la existencia de las fuentes originales.
Deferred Shading in Callisto

 De la misma forma, las fuentes de luz cuyo volumen proyectado sea tan
pequeño que solamente influencia a pocos pixeles (bien sea porque la

35
fuente de luz sea pequeña o porque se encuentre alejada), podrá ser
descartada.

 Por último, se puede definir un número máximo predefinido de fuentes de


luz que pueden afectar al mismo tiempo a cada pixel, escogiendo las más
grandes, intensas y cercanas. De esta forma si el frame rate obtenido en el
fotograma es bastante elevado, se puede elevar el número de fuentes de
luz que pueden afectar a cada pixel. Por el contrario, si el frame rate es
bajo, se puede reducir este número en beneficio del rendimiento.

La siguiente imagen muestra un claro ejemplo de los volúmenes de luz


calculados en la fase de actualización, que serán utilizados individualmente en la
siguiente fase para aplicar las diferentes optimizaciones existentes.
Deferred Shading in Callisto

36
Fase de Renderizado

Se pueden definir dos grupos en los que clasificar los efectos especiales y de
iluminación: fuentes globales y fuentes locales.

Las fuentes globales afectarán a todos los pixeles, por lo que implica que los
efectos se ejecutarán procesando la escena con el típico rectángulo en pantalla.
Ejemplos de fuentes globales son las luces que iluminan todo el “mundo virtual”
como la del sol, luces que se encuentran demasiado tan cerca de la cámara que
la contienen dentro de su volumen ó efectos que se aplican a pantalla completa,
como Depth of Field, fog, etc.

Ya que en la técnica deferred shading el coste de procesamiento es directamente


proporcional al número de píxeles afectados, las fuentes globales son las más
costosas de todas. Afortunadamente no se suelen usar en cantidades que
reduzcan de forma alarmante el rendimiento del sistema.

Las fuentes locales, al contrario de las anteriores, solamente afectan a regiones


específicas de la escena. El beneficio en cuanto a las fuentes globales es
evidente: solamente se procesarán los píxeles de pantalla que sean afectados al
Deferred Shading in Callisto

menos por una región.

Existen diferentes métodos para determinar los pixeles que deberán ser
procesados: Scissor Test, Stencil Cull, Z-Cull.

Scissor Test

35
La técnica consiste en utilizar el volumen que engloba la fuente de luz, calculado
en la fase de actualización, para generar a partir del mismo un rectángulo que,
una vez proyectado en pantalla, contendrá los pixeles sobre los que se realizarán
los cálculos de iluminación pertinentes.

Obviamente esta técnica es solamente adecuada para fuentes de luz del tipo
point light o spot light ya que las directional light no tienen una posición en el
espacio, por lo que se considera que afectan a toda la pantalla.

El siguiente bloque de código [NVIDIA 2004], solamente aplicable a point lights,


se encarga de calcular el rectángulo a partir de la posición de la fuente de luz y
de su distancia de atenuación.

RECT DetermineClipRect(const D3DXVECTOR3& position, const float range)


{
//compute 3D bounding box of light in world space
D3DXVECTOR3 bbox3D[8];
bbox3D[0].x = position.x - range; bbox3D[0].y = position.y + range;
bbox3D[0].z = position.z - range;
bbox3D[1].x = position.x + range; bbox3D[1].y = position.y + range;
bbox3D[1].z = position.z - range;
bbox3D[2].x = position.x - range; bbox3D[2].y = position.y - range;
bbox3D[2].z = position.z - range;
bbox3D[3].x = position.x + range; bbox3D[3].y = position.y - range;
bbox3D[3].z = position.z - range;
bbox3D[4].x = position.x - range; bbox3D[4].y = position.y + range;
bbox3D[4].z = position.z + range;
bbox3D[5].x = position.x + range; bbox3D[5].y = position.y + range;
bbox3D[5].z = position.z + range;
bbox3D[6].x = position.x - range; bbox3D[6].y = position.y - range;
bbox3D[6].z = position.z + range;
bbox3D[7].x = position.x + range; bbox3D[7].y = position.y - range;
bbox3D[7].z = position.z + range;

Deferred Shading in Callisto

36
//project coordinates
D3DXMATRIX viewProjMat = m_View * m_Projection;
D3DXVECTOR2 projBox[8];
for (int i = 0; i < 8; ++i)
{
D3DXVECTOR4 projPoint;
D3DXVec3Transform(&projPoint, &bbox3D[i], &viewProjMat);
projBox[i].x = projPoint.x / projPoint.w;
projBox[i].y = projPoint.y / projPoint.w;

//clip to extents
if (projBox[i].x < -1.0f)
projBox[i].x = -1.0f;
else if (projBox[i].x > 1.0f)
projBox[i].x = 1.0f;
if (projBox[i].y < -1.0f)
projBox[i].y = -1.0f;
else if (projBox[i].y > 1.0f)
projBox[i].y = 1.0f;

//go to pixel coordinates


projBox[i].x = ((projBox[i].x + 1.0f) / 2.0f) * iScreenWidth;
projBox[i].y = ((-projBox[i].y + 1.0f) / 2.0f) * iScreenHeight;
}

//compute 2D bounding box of projected coordinates


unsigned int minX = 0xFFFFFFFF;
unsigned int maxX = 0x00000000;
unsigned int minY = 0xFFFFFFFF;
unsigned int maxY = 0x00000000;

for (int i = 0; i < 8; ++i)


{
unsigned int x = static_cast<unsigned int>(projBox[i].x);
unsigned int y = static_cast<unsigned int>(projBox[i].y);
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
if (y > maxY)
maxY = y;
}
RECT bbox2D;
bbox2D.top = minY;
bbox2D.bottom = maxY;
bbox2D.left = minX;
bbox2D.right = maxX;

return bbox2D;
}

En Direct3D se activará el scissor test de la siguiente manera:


Deferred Shading in Callisto

...

pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);

...

RECT rect = DetermineClipRect(light.Position, light.Range);


pd3dDevice->SetScissorRect(&rect);

35
La siguiente imagen ilustra el rectángulo resultante proyectado en pantalla:

En el caso de spot lights habrá que proceder de forma distinta ya que el volumen
de un cono difícilmente se pude aproximar mediante una esfera. En este caso se
utilizarán bounding boxes alineados hacia la dirección de la luz. De la misma
forma que en el caso anterior, el bounding box será proyectado en pantalla con
el fin de obtener el rectángulo deseado.

El algoritmo para calcular el rectángulo final será similar al siguiente:

o Crear un bounding box que encierre el cono de la forma más ajustada


posible.

o Proyectar las esquinas del bounding box al plano de pantalla.

o Determinar a partir de la proyección las esquinas absolutas, es decir, la


esquina superior izquierda, la esquina superior derecha, etc.
Deferred Shading in Callisto

o Si una de las esquinas se encuentra detrás del plano significará que la


cámara se encuentra dentro del volumen, por lo que la luz se aplicará a
toda la pantalla.

o Utilizar las esquinas para formar el rectángulo y posteriormente pasárselo


a Direct3D como en el caso anterior.

36
Z-Cull

La aproximación más sencilla y en la que se obtienen buenos resultados a favor


de un aceptable rendimiento consiste en proyectar el volumen de luz y utilizar el
Z-Buffer para determinar el conjunto de pixeles final en los que se va a realizar el
cómputo de iluminación.

El algoritmo es el siguiente:

o Si la cámara se encuentra dentro del volumen de luz, habrá que proyectar


las caras traseras (backfaces) del volumen de luz. En ese caso se utilizará
ZFunc= Greater para indicar en qué pixeles se realizará el cálculo de
iluminación.

GREATE

VIEW

o Si la cámara corta el plano más alejado del view frustum, habrá que
proyectar las caras delanteras (frontfaces) del volumen de luz. En ese caso
se utilizará ZFunc= Less para indicar en qué pixeles se realizará el cálculo
de iluminación.
Deferred Shading in Callisto

35
L

VIEW

o En los demás casos se puede utilizar el procedimiento del primer punto,


proyectando las caras traseras (backfaces) del volumen de luz y utilizando
ZFunc= GreaterEqual para indicar en qué pixeles se realizará el cálculo de
iluminación.

GREATE

VIEW

La siguiente imagen muestra la totalidad de pixeles que se procesarían en el


Deferred Shading in Callisto

pixel shader al no efectuar Z-Culling. Nótese que, aunque se eliminan del cálculo
los pixeles que no se encuentran dentro de la proyección del volumen de luz,
todavía se están calculando pixeles correspondientes a fragmentos que no se
encuentran físicamente dentro del volumen.

36
Cuando se activa el Z-Culling, todos los pixeles que fallen el depth test
establecido anteriormente no entrarán en el cómputo del pixel shader, por lo que
el rendimiento se verá favorecido. En la siguiente imagen se puede comprobar la
amplia región de pixeles dentro de la proyección del volumen de luz que se ha
excluido del cálculo de iluminación.
Deferred Shading in Callisto

35
Pero el Z-Cull realizado no excluye todos los pixeles dentro del volumen de luz. Su
principal inconveniente es que todos los pixeles cuyo índice Z es menor al de los
backfaces de la esfera son procesados por el pixel shader.

En la imagen anterior coincide que no se encuentra ningún elemento delante de


la esfera por lo que el Z-Cull trabajará de la forma deseada todos esos casos,
pero en la siguiente imagen se muestra como, aunque la columna se encuentra
fuera del volumen de luz, los pixeles que se encuentran dentro de la proyección
del volumen serán igualmente calculados.

I LUSTRACIÓN 13 - I NCONVENIENT E Z-C ULL

Stencil Cull

El siguiente método para descartar cálculos de iluminación innecesarios en


pixeles que no están siendo afectados por ninguna fuente de luz se denomina
Stencil Culling. A diferencia del anterior, este sí que excluye todos los pixeles
fuera del volumen de luz.

Se trata de un algoritmo que utiliza dos pasadas por cada fuente de luz:
Deferred Shading in Callisto

o La primera pasada no afecta mucho al rendimiento global ya que se


desactiva la escritura de color en el buffer. Se tendrán que proporcionar los
siguientes estados de renderizado:

 Depth-Func = LESS

36
 Stencil Func = ALWAYS

 Stencil Pass = REPLACE (con el valor X)

 Todos los demás operandos (Stencil Fail, Stencil Pass) a KEEP

 CullMode = (Front Faces)

En la imagen se pueden ver las caras (back-faces en esta pasada) del


volumen de luz que pasan el depth test.

El paralelepípedo P posee un z-index menor por lo que en la zona que corta


a la proyección del volumen (marcada con las líneas rojas) no se pasa el
depth test.

Todos los pixeles englobados entre las líneas azul-verdes serían calculados,
justo como ocurre en el método anterior Z-Cull correspondiente a la
Ilustración 13.

VIEW

o La segunda pasada se realiza con el shader de iluminación. Se tendrán que


proporcionar los siguientes estados de renderizado:
Deferred Shading in Callisto

 Depth-Func = GREATEREQUAL

 Stencil Func = EQUAL

 Stencil Ref = X

 Todos los demás operandos (Stencil Fail, Stencil Pass) a KEEP

 CullMode = (Back Faces)

35
De esta forma, solo a las caras que en la primera iteración pasaron el stencil test
(marcadas con un 1 en el mismo) se filtrarán a partir del segundo test.

La siguiente imagen muestra, una vez pasado el segundo depth test y filtrar por
el stencil buffer, la región final en la que se realizará el cálculo de iluminación
(marcada por líneas verdes y rojas):

VIEW

Como se puede observar en la siguiente captura de pantalla, la columna no se


procesará por el pixel shader ya que no se encuentra dentro del volumen de luz.

Deferred Shading in Callisto

Como inconvenientes, se pueden destacar dos de baja importancia:

36
o El cambio continuo de RenderStates por fuente de luz, por lo que no sería
posible procesar todas en una sola pasada.

La existencia de dos pasadas por fuente de luz, aunque en la primera se


desactive la escritura de color, baja el rendimiento. Sin embargo, en la mayoría
de los casos es con la que mejor rendimiento se obtiene.
Deferred Shading in Callisto

35