Ombreggiatura piastrellata differita, calcolo della piastrella in OpenGL


11

Sto provando a fare l'ombreggiatura piastrellata differita in OpenGL usando lo shader di calcolo, ma ho colpito un intoppo quando provavo a creare il frustum per ogni piastrella. Sto usando la demo di Forward + di AMD (scritta in D3D) come guida, ma le luci sembrano essere tagliate quando non dovrebbero.

AGGIORNARE

Leggi sotto per gli aggiornamenti.

Questo è il mio shader di calcolo (completo):

    #version 430 core

#define MAX_LIGHTS 1024
#define MAX_LIGHTS_PER_TILE 40

#define WORK_GROUP_SIZE 16

struct PointLight
{
    vec3 position;
    float radius;
    vec3 color;
    float intensity;
};

layout (binding = 0, rgba32f) uniform writeonly image2D outTexture;
layout (binding = 1, rgba32f) uniform readonly image2D normalDepth;
layout (binding = 2, rgba32f) uniform readonly image2D diffuse;
layout (binding = 3, rgba32f) uniform readonly image2D specular;
layout (binding = 4, rgba32f) uniform readonly image2D glowMatID;

layout (std430, binding = 5) buffer BufferObject
{
    PointLight pointLights[];
};

uniform mat4 view;
uniform mat4 proj;
uniform mat4 viewProj;
uniform mat4 invViewProj;
uniform mat4 invProj;
uniform vec2 framebufferDim;

layout (local_size_x = WORK_GROUP_SIZE, local_size_y = WORK_GROUP_SIZE) in;

shared uint minDepth = 0xFFFFFFFF;
shared uint maxDepth = 0;
shared uint pointLightIndex[MAX_LIGHTS];
shared uint pointLightCount = 0;

vec3 ReconstructWP(float z, vec2 uv_f)
{
    vec4 sPos = vec4(uv_f * 2.0 - 1.0, z, 1.0);
    sPos = invViewProj * sPos;

    return (sPos.xyz / sPos.w);
}

vec4 ConvertProjToView( vec4 p )
{
    p = invProj * p;
    p /= p.w;
    return p;
}

// calculate the number of tiles in the horizontal direction
uint GetNumTilesX()
{
    return uint(( ( 1280 + WORK_GROUP_SIZE - 1 ) / float(WORK_GROUP_SIZE) ));
}

// calculate the number of tiles in the vertical direction
uint GetNumTilesY()
{
    return uint(( ( 720 + WORK_GROUP_SIZE - 1 ) / float(WORK_GROUP_SIZE) ));
}


vec4 CreatePlaneEquation( vec4 b, vec4 c )
{
    vec4 n;

    // normalize(cross( b.xyz-a.xyz, c.xyz-a.xyz )), except we know "a" is the origin
     n.xyz = normalize(cross( b.xyz, c.xyz ));

    // -(n dot a), except we know "a" is the origin
    n.w = 0;

    return n;
}

float GetSignedDistanceFromPlane( vec4 p, vec4 eqn )
{
    // dot( eqn.xyz, p.xyz ) + eqn.w, , except we know eqn.w is zero 
    // (see CreatePlaneEquation above)
    return dot( eqn.xyz, p.xyz );
}

vec4 CalculateLighting( PointLight p, vec3 wPos, vec3 wNormal, vec4 wSpec, vec4 wGlow)
{
    vec3 direction = p.position - wPos;

    if(length(direction) > p.radius)
        return vec4(0.0f, 0.0f, 0.0f, 0.0f);

    float attenuation = 1.0f - length(direction) / (p.radius);
    direction = normalize(direction);
    float diffuseFactor = max(0.0f, dot(direction, wNormal)) * attenuation;
    return vec4(p.color.xyz, 0.0f) * diffuseFactor * p.intensity;
}


void main()
{
        ivec2 pixelPos = ivec2(gl_GlobalInvocationID.xy);
        vec2 tilePos = vec2(gl_WorkGroupID.xy * gl_WorkGroupSize.xy) / vec2(1280, 720);

        vec4 normalColor = imageLoad(normalDepth, pixelPos);

        float d = normalColor.w;

        uint depth = uint(d * 0xFFFFFFFF);

        atomicMin(minDepth, depth);
        atomicMax(maxDepth, depth);

        barrier();

        float minDepthZ = float(minDepth / float(0xFFFFFFFF));
        float maxDepthZ = float(maxDepth / float(0xFFFFFFFF));

        vec4 frustumEqn[4];
        uint pxm = WORK_GROUP_SIZE * gl_WorkGroupID.x;
        uint pym = WORK_GROUP_SIZE * gl_WorkGroupID.y;
        uint pxp = WORK_GROUP_SIZE * (gl_WorkGroupID.x + 1);
        uint pyp = WORK_GROUP_SIZE * (gl_WorkGroupID.y + 1);

        uint uWindowWidthEvenlyDivisibleByTileRes = WORK_GROUP_SIZE * GetNumTilesX();
        uint uWindowHeightEvenlyDivisibleByTileRes = WORK_GROUP_SIZE * GetNumTilesY();

        vec4 frustum[4];
        frustum[0] = ConvertProjToView( vec4( pxm / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pym) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f, 1.0f) );
        frustum[1] = ConvertProjToView( vec4( pxp / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pym) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f, 1.0f) );
        frustum[2] = ConvertProjToView( vec4( pxp / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pyp) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f ,1.0f) );
        frustum[3] = ConvertProjToView( vec4( pxm / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pyp) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f, 1.0f) );

        for (int i = 0; i < 4; i++)
            frustumEqn[i] = CreatePlaneEquation(frustum[i], frustum[(i+1) & 3]);

        barrier();

        int threadsPerTile = WORK_GROUP_SIZE * WORK_GROUP_SIZE;

        for (uint i = 0; i < MAX_LIGHTS; i+= threadsPerTile)
        {
            uint il = gl_LocalInvocationIndex + i;

            if (il < MAX_LIGHTS)
            {
                PointLight p = pointLights[il];

                vec4 viewPos = view * vec4(p.position, 1.0f);
                float r = p.radius;

                if (viewPos.z + minDepthZ < r && viewPos.z - maxDepthZ < r)
                {

                if( ( GetSignedDistanceFromPlane( viewPos, frustumEqn[0] ) < r ) &&
                    ( GetSignedDistanceFromPlane( viewPos, frustumEqn[1] ) < r ) &&
                    ( GetSignedDistanceFromPlane( viewPos, frustumEqn[2] ) < r ) &&
                    ( GetSignedDistanceFromPlane( viewPos, frustumEqn[3] ) < r) )

                    {
                        uint id = atomicAdd(pointLightCount, 1);
                        pointLightIndex[id] = il;
                    }
                }

            }
        }

        barrier();

        vec4 diffuseColor = imageLoad(diffuse, pixelPos);
        vec4 specularColor = imageLoad(specular, pixelPos);
        vec4 glowColor = imageLoad(glowMatID, pixelPos);

        vec2 uv = vec2(pixelPos.x / 1280.0f, pixelPos.y / 720.0f);

        vec3 wp = ReconstructWP(d, uv);
        vec4 color = vec4(0.0f, 0.0f, 0.0f, 1.0f);

        for (int i = 0; i < pointLightCount; i++)
        {
            color += CalculateLighting( pointLights[pointLightIndex[i]], wp, normalColor.xyz, specularColor, glowColor);
        }

        barrier();

        if (gl_LocalInvocationID.x == 0 || gl_LocalInvocationID.y == 0 || gl_LocalInvocationID.x == 16 || gl_LocalInvocationID.y == 16)
            imageStore(outTexture, pixelPos, vec4(.2f, .2f, .2f, 1.0f));
        else
        {
            imageStore(outTexture, pixelPos, color);
            //imageStore(outTexture, pixelPos, vec4(maxDepthZ));
            //imageStore(outTexture, pixelPos, vec4(pointLightCount / 128.0f));
            //imageStore(outTexture, pixelPos, vec4(vec2(tilePos.xy), 0.0f, 1.0f));
        }
}

Questa è la parte che penso sia il problema, la parte dell'abbattimento:

        barrier();

    float minDepthZ = float(minDepth / float(0xFFFFFFFF));
    float maxDepthZ = float(maxDepth / float(0xFFFFFFFF));

    vec4 frustumEqn[4];
    uint pxm = WORK_GROUP_SIZE * gl_WorkGroupID.x;
    uint pym = WORK_GROUP_SIZE * gl_WorkGroupID.y;
    uint pxp = WORK_GROUP_SIZE * (gl_WorkGroupID.x + 1);
    uint pyp = WORK_GROUP_SIZE * (gl_WorkGroupID.y + 1);

    uint uWindowWidthEvenlyDivisibleByTileRes = WORK_GROUP_SIZE * GetNumTilesX();
    uint uWindowHeightEvenlyDivisibleByTileRes = WORK_GROUP_SIZE * GetNumTilesY();

    vec4 frustum[4];
    frustum[0] = ConvertProjToView( vec4( pxm / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pym) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f, 1.0f) );
    frustum[1] = ConvertProjToView( vec4( pxp / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pym) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f, 1.0f) );
    frustum[2] = ConvertProjToView( vec4( pxp / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pyp) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f ,1.0f) );
    frustum[3] = ConvertProjToView( vec4( pxm / float(uWindowWidthEvenlyDivisibleByTileRes) * 2.0f - 1.0f, (uWindowHeightEvenlyDivisibleByTileRes - pyp) / float(uWindowHeightEvenlyDivisibleByTileRes) * 2.0f - 1.0f, 1.0f, 1.0f) );

    for (int i = 0; i < 4; i++)
        frustumEqn[i] = CreatePlaneEquation(frustum[i], frustum[(i+1) & 3]);

    barrier();

    int threadsPerTile = WORK_GROUP_SIZE * WORK_GROUP_SIZE;

    for (uint i = 0; i < MAX_LIGHTS; i+= threadsPerTile)
    {
        uint il = gl_LocalInvocationIndex + i;

        if (il < MAX_LIGHTS)
        {
            PointLight p = pointLights[il];

            vec4 viewPos = view * vec4(p.position, 1.0f);
            float r = p.radius;

            if (viewPos.z + minDepthZ < r && viewPos.z - maxDepthZ < r)
            {

            if( ( GetSignedDistanceFromPlane( viewPos, frustumEqn[0] ) < r ) &&
                ( GetSignedDistanceFromPlane( viewPos, frustumEqn[1] ) < r ) &&
                ( GetSignedDistanceFromPlane( viewPos, frustumEqn[2] ) < r ) &&
                ( GetSignedDistanceFromPlane( viewPos, frustumEqn[3] ) < r) )

                {
                    uint id = atomicAdd(pointLightCount, 1);
                    pointLightIndex[id] = il;
                }
            }

        }
    }

    barrier();

La cosa strana è che quando visualizzo il conteggio della luce per tessera, mostra tutte le tessere che hanno un qualche modo di luci (prima immagine).

La seconda immagine mostra l'output finale, una sottile linea di luci al centro dello schermo e niente sopra o sotto. La rimozione dell'abbattimento (GetSignedDistanceFromPlane ()) dà il risultato desiderato, anche se il mio framerate si abbassa come una roccia.

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

La mia ipotesi sarebbe che il frustum sia costruito male ma non sono sicuro della matematica che sta dietro e potrebbe usare un po 'di aiuto in questo momento.

Modifica: aggiunta un'altra immagine che mostra l'output previsto.

inserisci qui la descrizione dell'immagine

AGGIORNAMENTO 1

Abbiamo modificato il modo in cui viene eseguita la selezione, il codice ora appare così:

barrier();

float minDepthZ = float(minDepth / float(0xFFFFFFFF));
float maxDepthZ = float(maxDepth / float(0xFFFFFFFF));

//total tiles = tileScale * 2
vec2 tileScale = vec2(1280, 720) * (1.0f / float(2*WORK_GROUP_SIZE));
vec2 tileBias = tileScale - vec2(gl_WorkGroupID.xy);

vec4 c1 = vec4(-proj[0][0] * tileScale.x, 0.0f, tileBias.x, 0.0f);
vec4 c2 = vec4(0.0f, -proj[1][1] * tileScale.y, tileBias.y, 0.0f);
vec4 c4 = vec4(0.0f, 0.0f, 1.0f, 0.0f);

 // Derive frustum planes
vec4 frustumPlanes[6];
// Sides
//right
frustumPlanes[0] = c4 - c1;
//left
frustumPlanes[1] = c4 + c1;
//bottom
frustumPlanes[2] = c4 - c2;
//top
frustumPlanes[3] = c4 + c2;
// Near/far
frustumPlanes[4] = vec4(0.0f, 0.0f,  1.0f, -minDepthZ);
frustumPlanes[5] = vec4(0.0f, 0.0f, -1.0f,  maxDepthZ);

for(int i = 0; i < 4; i++)
{
    frustumPlanes[i] *= 1.0f / length(frustumPlanes[i].xyz);
}

//DO CULLING HERE
for (uint lightIndex = gl_LocalInvocationIndex; lightIndex < numActiveLights; lightIndex += WORK_GROUP_SIZE)
{
    PointLight p = pointLights[lightIndex];

    if (lightIndex < numActiveLights)
    {
        bool inFrustum = true;
        for (uint i = 0; i < 4; i++)
        {
            float dd = dot(frustumPlanes[i], view * vec4(p.position, 1.0f));
            inFrustum = inFrustum && (dd >= -p.radius_length);
        }

        if (inFrustum)
        {
            uint id = atomicAdd(pointLightCount, 1);
            pointLightIndex[id] = lightIndex;
        }
    }
}

barrier();

Funziona meglio, le nostre luci sono ora abbattute correttamente (tranne la profondità minima / massima perché non è ancora implementata correttamente) rispetto alle nostre piastrelle. Fin qui tutto bene, MA! Abbiamo un problema con i bordi delle luci, le piastrelle non coprono l'intero raggio di luce e le prestazioni sono straordinarie. 1024 luci danno al massimo 40 fps con tonnellate di balbuzie.

Questo video mostra cosa succede ai bordi, le piastrelle grigie sono quelle che sono influenzate dalla luce (singolo punto luce) e le parti rosse sono geometrie sfumate.

http://www.youtube.com/watch?v=PiwGcFb9rWk&feature=youtu.be

Ridimensionare il raggio in modo che sia più grande quando l'abbattimento "funziona" ma rende le prestazioni ancora più difficili.

Risposte:


5

Risposta finale, risolto il problema delle prestazioni! Ho cambiato il mio ciclo di abbattimento in questo invece (basato su quello usato da Dice in BF3)

uint threadCount = WORK_GROUP_SIZE * WORK_GROUP_SIZE;
    uint passCount = (numActiveLights + threadCount - 1) /threadCount;
for (uint passIt = 0; passIt < passCount; ++passIt)
{
    uint lightIndex =  passIt * threadCount + gl_LocalInvocationIndex;

    lightIndex = min(lightIndex, numActiveLights);

    p = pointLights[lightIndex];
    pos = view * vec4(p.position, 1.0f);
    rad = p.radius_length;

    if (pointLightCount < MAX_LIGHTS_PER_TILE)
    {
        inFrustum = true;
        for (uint i = 3; i >= 0 && inFrustum; i--)
        {
            dist = dot(frustumPlanes[i], pos);
            inFrustum = (-rad <= dist);
        }

        if (inFrustum)
        {
            id = atomicAdd(pointLightCount, 1);
            pointLightIndex[id] = lightIndex;
        }
    }
}

Ora posso fare 4096 luci a 80 fps, sono più che felice.


2

Ho risolto il problema, parzialmente. Questo è il nuovo codice di abbattimento, che funziona per tutto tranne che per il piano lontano e vicino. Le prestazioni sono ancora piuttosto scadenti, quindi se qualcuno può vedere cosa potrebbe causare che sarebbe apprezzato.

        ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);

    vec4 normalColor = imageLoad(normalDepth, pixel);

    float d = normalColor.w;

    uint depth = uint(d * 0xFFFFFFFF);

    atomicMin(minDepth, depth);
    atomicMax(maxDepth, depth);

    barrier();

    float minDepthZ = float(minDepth / float(0xFFFFFFFF));
    float maxDepthZ = float(maxDepth / float(0xFFFFFFFF));

    vec2 tileScale = vec2(1280, 720) * (1.0f / float( 2 * WORK_GROUP_SIZE));
    vec2 tileBias = tileScale - vec2(gl_WorkGroupID.xy);

    vec4 col1 = vec4(-proj[0][0]  * tileScale.x, proj[0][1], tileBias.x, proj[0][3]); 

    vec4 col2 = vec4(proj[1][0], -proj[1][1] * tileScale.y, tileBias.y, proj[1][3]);

    vec4 col4 = vec4(proj[3][0], proj[3][1],  -1.0f, proj[3][3]); 

    vec4 frustumPlanes[6];

    //Left plane
    frustumPlanes[0] = col4 + col1;

    //right plane
    frustumPlanes[1] = col4 - col1;

    //top plane
    frustumPlanes[2] = col4 - col2;

    //bottom plane
    frustumPlanes[3] = col4 + col2;

    //near
    frustumPlanes[4] =vec4(0.0f, 0.0f, -1.0f,  -minDepthZ);

    //far
    frustumPlanes[5] = vec4(0.0f, 0.0f, -1.0f,  maxDepthZ);

    for(int i = 0; i < 4; i++)
    {
        frustumPlanes[i] *= 1.0f / length(frustumPlanes[i].xyz);
    }

    //DO CULLING HERE
    for (uint lightIndex = gl_LocalInvocationIndex; lightIndex < numActiveLights; lightIndex += WORK_GROUP_SIZE)
    {
        PointLight p = pointLights[lightIndex];

        if (pointLightCount < MAX_LIGHTS_PER_TILE)
        {
            bool inFrustum = true;
            for (uint i = 3; i >= 0 && inFrustum; i--)
            {
                float dd = dot(frustumPlanes[i], view * vec4(p.position, 1.0f));
                inFrustum = (dd >= -p.radius_length);
            }

            if (inFrustum)
            {
                uint id = atomicAdd(pointLightCount, 1);
                pointLightIndex[id] = lightIndex;
            }
        }
    }

    barrier();

In azione:

http://www.youtube.com/watch?v=8SnvYya1Jn8&feature=youtu.be


1
Ho un po 'di esperienza nell'implementazione del rendering / differito con indicizzazione della luce. Per quanto riguarda i bordi delle luci, potresti dare un'occhiata a imdoingitwrong.wordpress.com/2011/01/31/light-attenuation questo ti consente di specificare una soglia con cui tagliare le luci e ti dà un'equazione per calcolare la scala che passi nello shader. Per quanto riguarda gli aerei vicini e lontani, ho avuto molti problemi con la luce indicizzata. Il metodo migliore che ho trovato è stato quello di realizzare l'intero schermo per le luci che intersecano il piano vicino. Per quanto riguarda l'aereo lontano, potresti voler cercare il serraggio di profondità (GL_ARB_depth_clamp)
ashleysmithgpu

1
Spiacente, non abbastanza spazio :). Per quanto riguarda le prestazioni, probabilmente si desidera creare un profilo dell'applicazione. Immagino che spostare il calcolo dell'illuminazione all'interno del test if (inFrustum) possa aiutare, poiché eviti di dover scrivere in memoria, eseguire il loop e leggere dalla memoria per calcolare l'illuminazione.
ashleysmithgpu,

Grazie per l'aiuto! Ho provato a fare un po 'di profilazione ed è la fase di abbattimento che sta uccidendo le prestazioni attualmente. In particolare, sembra scrivere in inFrustum (inFrustum = (dd> = -p.radius_length); per qualche motivo è assolutamente assassinare le prestazioni e non ho idea del perché? Dovrebbe essere nella memoria locale e non condiviso tra i thread, pensi che potrebbe essere causando un'eccessiva ramificazione? Non sei del tutto sicuro di come spostare il calcolo della luce all'interno della condizione if (inFrustum) poiché ogni thread ha bisogno di un elenco completo di luci?
Bentebent
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.