Độ trễ lát gạch bị trì hoãn, tính toán gạch lát trong OpenGL


11

Tôi đang cố gắng tạo bóng mờ được trì hoãn trong OpenGL bằng cách sử dụng trình đổ bóng tính toán nhưng tôi đã gặp phải một trở ngại khi cố gắng tạo ra sự thất vọng cho mỗi ô. Tôi đang sử dụng bản demo Forward + của AMD (được viết bằng D3D) làm hướng dẫn nhưng đèn dường như bị loại bỏ khi không nên sử dụng.

CẬP NHẬT

Đọc dưới đây để cập nhật.

Đây là shader tính toán (hoàn chỉnh) của tôi:

    #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));
        }
}

Đây là phần tôi nghĩ là vấn đề, phần loại bỏ:

        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();

Điều kỳ lạ là khi tôi hình dung số lượng ánh sáng trên mỗi ô, nó cho thấy tất cả các ô có một số loại ánh sáng (hình ảnh đầu tiên).

Hình ảnh thứ hai cho thấy đầu ra cuối cùng, một dòng đèn mỏng ở giữa màn hình và không có gì ở trên hoặc dưới. Loại bỏ việc loại bỏ (GetSignDistanceFromPlane ()) mang lại kết quả mong muốn, mặc dù với tốc độ khung hình của tôi giảm xuống như một tảng đá.

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Tôi đoán là sự thất vọng được xây dựng sai nhưng tôi không chắc về toán học đằng sau nó và có thể sử dụng một số trợ giúp ngay bây giờ.

Chỉnh sửa: Đã thêm một hình ảnh khác cho thấy đầu ra dự kiến.

nhập mô tả hình ảnh ở đây

CẬP NHẬT 1

Chúng tôi đã thay đổi cách loại bỏ được thực hiện, mã bây giờ trông như thế này:

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();

Điều này hoạt động tốt hơn, đèn của chúng tôi hiện đã được loại bỏ đúng cách (ngoại trừ độ sâu tối thiểu / tối đa vì nó chưa được triển khai đúng cách) so với gạch của chúng tôi. Cho đến nay, rất tốt, NHƯNG! Chúng tôi có một vấn đề với các cạnh của đèn, gạch không bao phủ toàn bộ bán kính ánh sáng và hiệu suất là tuyệt vời. 1024 đèn cho tốc độ 40 khung hình tốt nhất với hàng tấn lắp đặt.

Video này cho thấy những gì xảy ra ở các cạnh, gạch màu xám là những gì gạch bị ảnh hưởng bởi ánh sáng (đèn chiếu đơn) và các phần màu đỏ là hình học bóng mờ.

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

Thu nhỏ bán kính để nó lớn hơn khi loại bỏ "công việc" nhưng làm cho hiệu suất giảm thậm chí khó hơn.

Câu trả lời:


5

Câu trả lời cuối cùng, giải quyết vấn đề hiệu suất! Thay đổi vòng lặp loại bỏ của tôi thành thay thế này (dựa trên vòng lặp được sử dụng bởi Dice trong 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;
        }
    }
}

Bây giờ tôi có thể làm 4096 đèn ở tốc độ 80 khung hình / giây, tôi hạnh phúc hơn.


2

Đã giải quyết được vấn đề, một phần. Đây là mã loại mới, hoạt động cho tất cả mọi thứ trừ mặt phẳng xa và gần. Hiệu suất vẫn còn khá tệ vì vậy nếu bất cứ ai cũng có thể thấy những gì có thể gây ra rằng nó sẽ được đánh giá cao.

        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();

Trong hành động:

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


1
Tôi có một chút kinh nghiệm về việc thực hiện kết xuất / hoãn chỉ mục ánh sáng. Đối với các cạnh của đèn, bạn có thể muốn xem imdoingitwrong.wordpress.com/2011/01/31/light-atteninating, điều này cho phép bạn chỉ định ngưỡng để cắt đèn và đưa ra phương trình để tính toán quy mô mà bạn vượt qua vào shader. Đối với các máy bay gần và xa, tôi đã gặp nhiều rắc rối với chỉ số ánh sáng. Phương pháp tốt nhất tôi tìm thấy là làm toàn bộ màn hình cho đèn chiếu qua mặt phẳng gần. Đối với mặt phẳng xa, bạn có thể muốn tìm kiếm độ sâu kẹp (GL_ARB_depth_clamp)
ashleymithgpu

1
Xin lỗi, không đủ không gian :). Về hiệu suất, có lẽ bạn muốn hồ sơ ứng dụng của bạn. Tôi sẽ tưởng tượng việc chuyển tính toán ánh sáng vào bên trong phép thử if (inFrustum) sẽ giúp ích, vì bạn tránh việc phải ghi vào bộ nhớ, lặp và đọc từ bộ nhớ để tính toán ánh sáng.
ashleymithgpu

Cảm ơn đã giúp đỡ! Tôi đã cố gắng thực hiện một số hồ sơ và đó là giai đoạn tiêu hủy đang giết chết hiệu suất hiện tại. Cụ thể, có vẻ như viết thư cho inFrustum (inFrustum = (dd> = -p.radius_length); vì một số lý do là hoàn toàn giết chết hiệu suất và tôi không biết tại sao? Nó nên ở trong bộ nhớ cục bộ và không được chia sẻ giữa các luồng, nghĩ rằng nó có thể gây ra sự phân nhánh quá mức? Không hoàn toàn chắc chắn làm thế nào để di chuyển tính toán ánh sáng sang trong điều kiện if (inFrustum) vì mỗi luồng cần một danh sách đầy đủ các đèn?
Bentebent 19/12/13
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.