Intento crear un efecto SSAO en mi motor de juego (DirectX 11, C ++), basado principalmente en el tutorial de gamedev.net de José María Méndez . Desafortunadamente, no cubre el problema de creación de textura (normales, posición).
En la primera pasada, creo la textura normal y luego también leo el búfer de profundidad como textura (es posible desde que creo la vista de recursos del sombreador y desenlazo el búfer de profundidad en la segunda pasada). Ambos deben estar a la vista del espacio.
Todavía no he implementado el desenfoque (lo haré más tarde), pero tengo algunos problemas en este momento. Creo que se debe al cálculo incorrecto de las texturas o al método incorrecto de transformación de datos a partir de ellas , en lugar de la fórmula incorrecta o los valores de los parámetros .
Mis conjeturas de lo que podría salir mal:
- cálculo de textura normal (en el espacio de la vista), pero se ve bien ,
- cálculo de textura de posición (en el espacio de visualización) y transformación de profundidad a posición,
- cálculo de matriz de proyección invertida,
- algo no está normalizado o saturado y debería ser,
- parámetros (sin embargo, no deberían tener tal impacto en la producción)?
Mis texturas
Difuso ( textures[0]
no se usa realmente aquí, solo para comparar):
Normal ( textures[1]
, debe estar en el espacio de la vista):
Los obtengo en el sombreador de vértices de primer paso (los sombreadores de píxeles solo lo hacen output.normal = input.normal
):
output.normal = float4(mul(input.normal, (float3x3)world), 1);
output.normal = float4(mul(output.normal, (float3x3)view), 1);
Textura del búfer de profundidad ( textures[2]
es difícil ver algo debido a las bajas diferencias en los valores, pero creo que está bien):
Para mostrarlo uso:
float depth = textures[2].Sample(ObjSamplerState, textureCoordinates).r;
depth = (depth + 1.0f) / 2.0f;
color.rgb = float3(depth, depth, depth);
Después de la corrección de contraste en un programa externo (no lo uso sombreador, pero es mejor para los ojos):
La descripción del búfer de profundidad:
//create depth stencil texture (depth buffer)
D3D11_TEXTURE2D_DESC descDepth;
ZeroMemory(&descDepth, sizeof(descDepth));
descDepth.Width = settings->getScreenSize().getX();
descDepth.Height = settings->getScreenSize().getY();
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = settings->isDepthBufferUsableAsTexture() ? DXGI_FORMAT_R24G8_TYPELESS : DXGI_FORMAT_D24_UNORM_S8_UINT; //true
descDepth.SampleDesc.Count = settings->getAntiAliasing().getCount(); //1
descDepth.SampleDesc.Quality = settings->getAntiAliasing().getQuality(); //0
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = settings->isDepthBufferUsableAsTexture() ? (D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE) : D3D11_BIND_DEPTH_STENCIL; //true
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;
Y el plano lejano / cercano (tienen un impacto en la textura del búfer de profundidad) se establece en nearPlane = 10.0f
( 1.0f
no cambia demasiado y los objetos no están tan cerca de la cámara) y farPlane = 1000.0f
.
En base a esto, creo la posición en el espacio de vista (no la guardo en la textura, pero si la envío a la pantalla se ve así):
Cada píxel aquí se calcula mediante:
float depth = textures[2].Sample(ObjSamplerState, textureCoordinates).r;
float3 screenPos = float3(textureCoordinates.xy* float2(2, -2) - float2(1, -1), 1 - depth);
float4 wpos = mul(float4(screenPos, 1.0f), projectionInverted);
wpos.xyz /= wpos.w;
return wpos.xyz;
Y projectionInverted
se transfiere al sombreador con:
DirectX::XMFLOAT4X4 projection = camera->getProjection();
DirectX::XMMATRIX camProjection = XMLoadFloat4x4(&projection);
camProjection = XMMatrixTranspose(camProjection);
DirectX::XMVECTOR det; DirectX::XMMATRIX projectionInverted = XMMatrixInverse(&det, camProjection);
projectionInverted = XMMatrixTranspose(projectionInverted);
cbPerObj.projectionInverted = projectionInverted;
....
context->UpdateSubresource(constantBuffer, 0, NULL, &cbPerObj, 0, 0);
context->VSSetConstantBuffers(0, 1, &constantBuffer);
context->PSSetConstantBuffers(0, 1, &constantBuffer);
Creo que eso camera->getProjection()
devuelve una buena matriz, ya que uso la misma para construir los viewProjectionMatrix
objetos, lo cual está bien (veo los vértices correctos en los lugares correctos). Tal vez algo con transposición?
Aleatorio ( textures[3]
normal): es la textura del tutorial, solo en .tga
formato:
La textura aleatoria no tiene mosaico (es repetible, cuando pones una textura al lado de otra). Si lo renderizo getRandom(uv)
para toda la pantalla que obtengo (el float2
resultado se muestra en rojo y verde):
Parece "algo de sombra" pero nada como el componente SSAO (de todos modos, todavía no está borroso o mezclado con luz / difusa). Supongo que es más similar al sombreado de phong que al SSAO real (sin sombras alrededor de las "esquinas profundas", etc.)
El sombreador SSAO (segundo paso):
//based on: http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/a-simple-and-practical-approach-to-ssao-r2753
Texture2D textures[4]; //color, normal (in view space), position (depth), random
SamplerState ObjSamplerState;
cbuffer cbPerObject : register(b0) {
float4 notImportant;
float4 notImportant2;
float2 notImportant3;
float4x4 projectionInverted;
};
cbuffer cbPerShader : register(b1) {
float4 parameters; //randomSize, sampleRad, intensity, scale
float4 parameters2; //bias
};
struct VS_OUTPUT {
float4 Pos : SV_POSITION;
float2 textureCoordinates : TEXCOORD;
};
float3 getPosition(float2 textureCoordinates) {
float depth = textures[2].Sample(ObjSamplerState, textureCoordinates).r;
float3 screenPos = float3(textureCoordinates.xy* float2(2, -2) - float2(1, -1), 1 - depth);
float4 wpos = mul(float4(screenPos, 1.0f), projectionInverted);
wpos.xyz /= wpos.w;
return wpos.xyz;
}
float3 getNormal(in float2 textureCoordinates) {
return normalize(textures[1].Sample(ObjSamplerState, textureCoordinates).xyz * 2.0f - 1.0f);
}
float2 getRandom(in float2 textureCoordinates) {
return normalize(textures[3].Sample(ObjSamplerState, float2(1980,1050)/*screenSize*/ * textureCoordinates / parameters[0]/*randomSize*/).xy * 2.0f - 1.0f);
}
float doAmbientOcclusion(in float2 tcoord, in float2 textureCoordinates, in float3 p, in float3 cnorm) {
float3 diff = getPosition(tcoord + textureCoordinates) - p;
const float3 v = normalize(diff);
const float d = length(diff)*parameters[3]/*scale*/;
return max(0.0, dot(cnorm, v) - 0.2f/*bias*/)*(1.0 / (1.0 + d))*parameters[2]/*intensity*/;
}
float4 main(VS_OUTPUT input) : SV_TARGET0{
float2 textureCoordinates = input.textureCoordinates;
//SSAO
const float2 vec[4] = { float2(1,0),float2(-1,0), float2(0,1),float2(0,-1) };
float3 p = getPosition(textureCoordinates);
float3 n = getNormal(textureCoordinates);
float2 rand = getRandom(textureCoordinates);
float ao = 0.0f;
float rad = parameters[1]/*sampleRad*/ / p.z;
//**SSAO Calculation**//
int iterations = 4;
for (int j = 0; j < iterations; ++j) {
float2 coord1 = reflect(vec[j], rand)*rad;
float2 coord2 = float2(coord1.x*0.707 - coord1.y*0.707, coord1.x*0.707 + coord1.y*0.707);
ao += doAmbientOcclusion(textureCoordinates, coord1*0.25, p, n);
ao += doAmbientOcclusion(textureCoordinates, coord2*0.5, p, n);
ao += doAmbientOcclusion(textureCoordinates, coord1*0.75, p, n);
ao += doAmbientOcclusion(textureCoordinates, coord2, p, n);
}
//ao /= (float)iterations*4.0;
//ao /= (float)parameters[1]/*sampleRad*/;
ao = 1 - (ao * parameters[2]/*intensity*/);
//ao = saturate(ao);
//**END**//
//Do stuff here with your occlusion value ??ao??: modulate ambient lighting, write it to a buffer for later //use, etc.
//SSAO end
color = ao; //let's just output the SSAO component for now
return float4(color.rgb, 1.0f);
}
Proporciono esos parámetros (estaba tratando de jugar con ellos, cambian la calidad del resultado, etc., pero no el efecto general):
getShader()->setParameter(0, 64.0f); //randomSize
getShader()->setParameter(1, 10.0f); //sampleRad
getShader()->setParameter(2, 1.0f); //intensity
getShader()->setParameter(3, 0.5f); //scale
getShader()->setParameter(4, 0.0f); //bias (also 0.2f sometimes)
Tenga en cuenta que he comentado ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/;
solo porque de esa manera puedo ver cualquier cosa (en otro caso, las diferencias en los tonos del componente SSAO son demasiado pequeñas, pero eso puede ser un problema con parámetros incorrectos).
editar # 1
Como @AndonM.Coleman
sugerí, saqué la textura normal a la pantalla en textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5f
lugar de solo textures[1].Sample(ObjSamplerState, textureCoordinates)
:
Además, intenté eliminar *2.0f - 1.0f
parte de getNormal(...)
y me dio otro resultado para el componente SSAO:
Todavía está mal, no sé si está más cerca o más lejos de "bueno". Tal vez, según @AndonM.Coleman
(si lo he entendido) ¿tiene algo que ver con los formatos de los buffers? Mis formatos son:
#define BUFFER_FORMAT_SWAP_CHAIN DXGI_FORMAT_R8G8B8A8_UNORM
//depth buffer
//I don't use it: #define BUFFER_FORMAT_DEPTH DXGI_FORMAT_D24_UNORM_S8_UINT
#define BUFFER_FORMAT_DEPTH_USABLE_AS_TEXTURE DXGI_FORMAT_R24G8_TYPELESS
//I don't use it: #define BUFFER_FORMAT_DEPTH_VIEW DXGI_FORMAT_D24_UNORM_S8_UINT
#define BUFFER_FORMAT_DEPTH_VIEW_AS_TEXTURE DXGI_FORMAT_D24_UNORM_S8_UINT
#define BUFFER_FORMAT_DEPTH_SHADER_RESOURCE_VIEW DXGI_FORMAT_R24_UNORM_X8_TYPELESS
#define BUFFER_FORMAT_TEXTURE DXGI_FORMAT_R8G8B8A8_UNORM
Realmente no quiero usar formatos más lentos si no es necesario.
editar # 2
Cambiar la salida 1 - depth
a depth - 1
una nueva (y extraña, supongo) textura de posición:
Y el componente SSAO cambia a:
Tenga en cuenta que todavía tengo el original ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/
comentado para ver algo.
editar # 3
Cambiar el formato de búfer para textura (y solo para ello) de DXGI_FORMAT_R8G8B8A8_UNORM
a DXGI_FORMAT_R32G32B32A32_FLOAT
me da una textura normal más agradable:
Además, cambiar el sesgo de 0.0f
a 0.2f
y el radio de muestra de 10.0f
a 0.2f
me dio:
Se ve mejor, ¿no? Sin embargo, tengo sombras alrededor de los objetos a la izquierda y la inversión de los mismos a la derecha. ¿Qué puede causar eso?
DXGI_FORMAT_R8G8B8A8_UNORM
. Necesitaría la SNORM
versión de eso, o la mitad de su espacio vectorial se fijará a 0 . De hecho, ahora veo que esto ya se solucionó multiplicando por 2 y restando 1 negativo tanto en el código normal como en el de posición. Sin embargo, sugiero encarecidamente que antes de enviar estos colores a la pantalla para su visualización, tenga la costumbre de hacerlo * 0.5 + 0.5
(para que pueda ver las partes negativas de su espacio de coordenadas). En cuanto a 0.5 + 1.0
eso no funcionaría, sus colores irían de 0.5 a 1.5 (sujeto a 1).
1 - depth
parte me parece extraña. Creo que debería ser depth - 1
, de lo contrario su rango de profundidad está invertido.