Extraño efecto SSAO (¿posición incorrecta / texturas normales en el espacio visual?)

8

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): ingrese la descripción de la imagen aquí

Normal ( textures[1], debe estar en el espacio de la vista): ingrese la descripción de la imagen aquí

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):

ingrese la descripción de la imagen aquí

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): ingrese la descripción de la imagen aquí

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.0fno 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í): ingrese la descripción de la imagen aquí

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 projectionInvertedse 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 viewProjectionMatrixobjetos, 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 .tgaformato: ingrese la descripción de la imagen aquí

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 float2resultado se muestra en rojo y verde): ingrese la descripción de la imagen aquí

Y el resultado: ingrese la descripción de la imagen aquí

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.Colemansugerí, saqué la textura normal a la pantalla en textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5flugar de solo textures[1].Sample(ObjSamplerState, textureCoordinates):

ingrese la descripción de la imagen aquí

Además, intenté eliminar *2.0f - 1.0fparte de getNormal(...)y me dio otro resultado para el componente SSAO:

ingrese la descripción de la imagen aquí

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 - deptha depth - 1una nueva (y extraña, supongo) textura de posición:

ingrese la descripción de la imagen aquí

Y el componente SSAO cambia a:

ingrese la descripción de la imagen aquí

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_UNORMa DXGI_FORMAT_R32G32B32A32_FLOATme da una textura normal más agradable:

ingrese la descripción de la imagen aquí

Además, cambiar el sesgo de 0.0fa 0.2fy el radio de muestra de 10.0fa 0.2fme dio:

ingrese la descripción de la imagen aquí

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?

PolGraphic
fuente
Algo me parece extraño en getRandom (), cuando muestreas una textura no repetida, usarás [0-1] coordenadas UV. Dado que usa float2 (1980,1050), incluso si multiplica / divide, no veo cómo obtendrá valores [0-1] con tales fórmulas ... O su textura de ruido está configurada para estar en modo de repetición ?
VB_overflow
1
Pregunta estúpida: ¿sus objetivos de renderizado son de punto flotante? Las capturas de pantalla que ha mostrado con espacios de coordenadas en ellas tienen grandes espacios negros donde las tres coordenadas son 0 o negativas. Todos están sujetos a 0 utilizando objetivos de renderizado tradicionales de punto fijo (UNORM) y definitivamente no obtendrá un SSAO preciso cuando no pueda muestrear la posición / normal correctamente. Técnicamente, no necesita objetivos de renderizado de punto flotante si le preocupa el rendimiento: puede usar SNORM o cambiar la escala / compensar los colores manualmente.
Andon M. Coleman
1
No, no puede almacenar valores negativos en DXGI_FORMAT_R8G8B8A8_UNORM. Necesitaría la SNORMversió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.0eso no funcionaría, sus colores irían de 0.5 a 1.5 (sujeto a 1).
Andon M. Coleman
1
Todo eso está bien, en realidad. El código se encarga completamente de esto en este momento. Las normales se almacenan en el rango 0.0 - 1.0 en la textura, pero se reescalan a -1.0 - 1.0 después de la muestra; Tu posición es también. Sin embargo, la 1 - depthparte me parece extraña. Creo que debería ser depth - 1, de lo contrario su rango de profundidad está invertido.
Andon M. Coleman
1
No soy un experto en absoluto, pero ... ¿tu textura normal no contiene azul? AFAI: Comprenda que cada píxel de la textura normal debe satisfacer esta ecuación: r ^ 2 + g ^ 2 + b ^ 2 = 1 (es decir, longitud 1). Probé tu imagen y contiene píxeles negros puros.
Martijn Courteaux

Respuestas:

3

Que yo sepa, su salida de posición es incorrecta en ambos casos. Si debería verse así:ingrese la descripción de la imagen aquí

O esto: ingrese la descripción de la imagen aquí

Dependiendo de si está utilizando el sistema de coordenadas de la mano izquierda o derecha. Debe crear un búfer de posición y omitir el intento de calcularlo desde la profundidad hasta que sepa qué funciona y qué no. Cree un nuevo rendertarget en el pase GBuffer y simplemente muestre la posición de esta manera:

output.Target3 = float4(input.PosV.z, input.PosV.z, input.PosV.z, 1.0f);

Su última captura de pantalla parece un problema normal, ¿está seguro de que las normales están en el espacio de visualización? De lo contrario, la pared de la derecha debería permanecer oscura incluso si gira la cámara 180 grados y la pared está a la izquierda. ¿Está o todavía está oscuro en el lado derecho?

Si mover la vista solo hace que las partes oscuras aparezcan y desaparezcan, lo más probable es que sea un problema de proyección. Así: (es el mismo rincón de una habitación) ingrese la descripción de la imagen aquí

Intente cambiar su matriz de proyección de LH a RH o viceversa, es posible que la haya confundido.

Además, asegúrese de descomprimir las normales con "* 2.0f - 1.0f" si también las almacenó con "* 0.5f + 1.0f". Lo cubriste en los comentarios, pero vale la pena revisarlo una vez más.

Stefan Agartsson
fuente