การแรเงาแบบเรียงต่อกันของกระเบื้องการคำนวณ frusta ของกระเบื้องใน OpenGL


11

ฉันพยายามแรเงาแบบเรียงต่อกันบนกระเบื้องใน OpenGL โดยใช้ตัวประมวลผล แต่ฉันได้รับอุปสรรคเมื่อพยายามสร้าง frustum สำหรับแต่ละไทล์ ฉันใช้การสาธิตฟอร์เวิร์ด + ของ AMD (เขียนใน D3D) เป็นแนวทาง แต่ดูเหมือนว่าไฟจะถูกกำจัดเมื่อพวกเขาไม่ควร

UPDATE

อ่านด้านล่างสำหรับการปรับปรุง

นี่คือ shader การคำนวณของฉัน (สมบูรณ์):

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

นี่คือส่วนที่ฉันคิดว่าเป็นปัญหาส่วนที่เลือกสรร:

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

สิ่งที่แปลกก็คือเมื่อฉันเห็นภาพการนับจำนวนแสงต่อแผ่นมันแสดงให้เห็นว่าแผ่นกระเบื้องทั้งหมดมีลักษณะของแสง (ภาพแรก)

ภาพที่สองแสดงผลลัพธ์สุดท้ายไฟเส้นเล็ก ๆ อยู่ตรงกลางของหน้าจอและไม่มีอะไรที่เหนือหรือใต้ การเอาการคัดออก (GetSignedDistanceFromPlane ()) ให้ผลลัพธ์ที่ต้องการแม้ว่าจะมีการวางเฟรมของฉันเหมือนก้อนหิน

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ฉันเดาว่า frustum นั้นสร้างผิด แต่ฉันไม่แน่ใจว่าคณิตศาสตร์อยู่เบื้องหลังและสามารถใช้ความช่วยเหลือได้ในตอนนี้

แก้ไข: เพิ่มรูปภาพอื่นซึ่งแสดงผลลัพธ์ที่ต้องการ

ป้อนคำอธิบายรูปภาพที่นี่

อัพเดท 1

เราได้เปลี่ยนวิธีการคัดสรรแล้วตอนนี้โค้ดมีลักษณะดังนี้:

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

ใช้งานได้ดีกว่าตอนนี้ไฟของเราถูกกำจัดอย่างเหมาะสม (ยกเว้นความลึกต่ำสุด / สูงสุดเพราะยังไม่ได้ติดตั้งอย่างเหมาะสม) กับกระเบื้องของเรา จนถึงตอนนี้ดีมาก แต่! เรามีปัญหากับขอบของไฟกระเบื้องไม่ครอบคลุมรัศมีแสงทั้งหมดและประสิทธิภาพการทำงานเป็นที่น่ากลัว 1024 ไฟให้ 40fps ที่ดีที่สุดพร้อมตันการพูดติดอ่าง

วิดีโอนี้แสดงสิ่งที่เกิดขึ้นที่ขอบกระเบื้องสีเทาคือสิ่งที่กระเบื้องได้รับผลกระทบจากแสง (จุดเดียว) และส่วนสีแดงเป็นรูปทรงเรขาคณิตที่มีเงา

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

ปรับขนาดรัศมีเพื่อให้ใหญ่ขึ้นเมื่อทำการ "กำจัด" แต่ทำให้ประสิทธิภาพลดลงได้ยากขึ้น

คำตอบ:


5

คำตอบสุดท้ายแก้ปัญหาประสิทธิภาพ! เปลี่ยนลูปการคัดเลือกของฉันเป็นแบบนี้แทน (อ้างอิงจากลูกเต๋าที่ใช้โดย 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;
        }
    }
}

ตอนนี้ฉันสามารถทำ 4096 ไฟที่ 80 เฟรมต่อวินาทีฉันมีความสุขมาก


2

ได้แก้ไขปัญหาบางส่วนแล้ว นี่คือรหัสการคัดใหม่ที่ใช้ได้กับทุกสิ่งยกเว้นเครื่องบินไกลและใกล้ การแสดงยังคงไม่ดีนักดังนั้นหากใครสามารถเห็นสิ่งที่อาจทำให้เกิดการชื่นชม

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

ในการดำเนินการ:

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


1
ฉันมีประสบการณ์เล็กน้อยเกี่ยวกับการนำการเรนเดอร์แสง / ดัชนีแบบเลื่อนออกไป สำหรับขอบของไฟคุณอาจต้องการดูimdoingitwrong.wordpress.com/2011/01/31/31/light-attenuationซึ่งจะช่วยให้คุณระบุเกณฑ์ในการตัดไฟด้วยและให้สมการในการคำนวณ สเกลที่คุณผ่านเข้าสู่ shader สำหรับเครื่องบินใกล้และไกลฉันมีปัญหามากกับการจัดทำดัชนีแสง วิธีที่ดีที่สุดที่ฉันพบคือทำทั้งหน้าจอสำหรับแสงที่ตัดกันระนาบใกล้ สำหรับเครื่องบินไกลคุณอาจต้องการค้นหาการจับยึดความลึก (GL_ARB_depth_clamp)
ashleysmithgpu

1
ขออภัยพื้นที่ไม่เพียงพอ :) สำหรับประสิทธิภาพคุณอาจต้องการโปรไฟล์แอปพลิเคชันของคุณ ฉันนึกภาพการย้ายการคำนวณแสงไปที่การทดสอบ if (inFrustum) จะช่วยได้เพราะคุณไม่จำเป็นต้องเขียนลงในหน่วยความจำวนรอบและอ่านจากหน่วยความจำเพื่อคำนวณแสง
ashleysmithgpu

ขอบคุณสำหรับความช่วยเหลือ! ฉันพยายามทำโปรไฟล์แล้วและมันก็เป็นขั้นตอนการคัดสรรซึ่งกำลังฆ่าการแสดงอยู่ในขณะนี้ โดยเฉพาะอย่างยิ่งดูเหมือนว่าจะเขียนถึง inFrustum (inFrustum = (dd> = -p.radius_length) ด้วยเหตุผลบางอย่างคือประสิทธิภาพการฆ่าอย่างสมบูรณ์และฉันไม่รู้ว่าทำไมมันควรอยู่ในหน่วยความจำภายในและไม่ใช้ร่วมกันระหว่างเธรดคิดว่าอาจเป็น ทำให้เกิดการแตกแขนงมากเกินไปหรือไม่ไม่แน่ใจอย่างสิ้นเชิงว่าจะย้ายการคำนวณแสงให้อยู่ในเงื่อนไข if (inFrustum) เนื่องจากแต่ละเธรดต้องการรายการไฟทั้งหมดหรือไม่
Bentebent
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.