0
$\begingroup$

I'm trying to write a simple ray caster in OpenGL with C++. Previously, I have been using the fragment shader of a quad that covers the entire screen to do all the ray calculations. Now, I want to do those calculations in a compute shader that will generate a texture which will then be rendered to the screen by a fragment shader of a full screen quad. This will make things like post processing easier and seems like the first step towards turning the ray caster into a ray tracer as I will have more control over the threads.

By following this tutorial (http://antongerdelan.net/opengl/compute.html), I was able to get the compute shader working but it runs much slower than when I was using a fragment shader to do all the calculations (a drop from ~350 to ~30 FPS with the same scene).

At first I thought this slowdown was caused by the writing to the texture in the compute shader. But after commenting out all the texture code (keeping only the ray calculations that decide the pixels color), I still got very slow frame rates.

I have also experimented with the local group sizes of the compute shader, but nothing made any notable difference.

I'm struggling to understand why its running slower, the only difference between the fast running fragment shader and the slow running compute shader is how the screen space position of each thread is calculated (but this does not seem to be making the difference).

Not sure if it will help but here's the compute shader source:

#version 430 core

// For context, this renders a sparse voxel octree. Proably should have mentioned that earlier...

layout(std430, binding = 2) buffer mainOctreeVoxelData
{
    unsigned int mainOctree[];
};

layout(std430, binding = 3) buffer materialData
{
    float materials[];
};

// After a bit of experimenting I found 5 by 5 to be the fastest local group size
layout(local_size_x = 5, local_size_y = 5, local_size_z = 1) in;
layout(rgba32f, binding = 0) uniform image2D imageOutput;

uniform vec3 cameraPosition;
uniform mat4 cameraTransformationMatrix;
uniform float fov;
uniform float aspectRatio;
uniform float screenHeight;
uniform float screenWidth;
uniform int mainOctreeRootSize;
float pi = 3.141592;

vec3 RayHitFace(vec3 origin, vec3 direction, vec3 cellPosition, vec3 tu, vec3 tv)
{
    //Determinant for inverse matrix
    vec3 q = cross(direction, tv);
    float det = dot(tu, q);
    //if(abs(det) < 0.0000001) //If too close to zero
    //  return;
    float invdet = 1.0 / det;

    //Solve component parameters
    vec3 s = origin - cellPosition;
    float u = dot(s, q) * invdet;
    if (u < 0.0 || u > 1.0)
        return vec3(-1, -1, -1);

    vec3 r = cross(s, tu);
    float v = dot(direction, r) * invdet;
    if (v < 0.0 || v > 1.0)
        return vec3(-1, -1, -1);

    float t = dot(tv, r) * invdet;
    if (t <= 0.0)
        return vec3(-1, -1, -1);

    return cellPosition + u * tu + v * tv;
};

int[8] octreeCheckOrder = { 0,1,2,3,4,5,6,7 };
vec3[8] octreeNodePositionOfsets = {
    vec3(0,0,0),
    vec3(1,0,0),
    vec3(0,1,0),
    vec3(0,0,1),
    vec3(1,1,0),
    vec3(1,0,1),
    vec3(0,1,1),
    vec3(1,1,1)
};
struct NodeInfo
{
    vec3 pos;
    unsigned int size;
    unsigned int data;
};
vec3 rayPos;
vec3 rayDir;
NodeInfo GetNodeFromPoint(vec3 point)
{
    unsigned int index = 0;
    int size = mainOctreeRootSize;
    vec3 position = vec3(0, 0, 0);
    while (true) {
        unsigned int thisNodeData = mainOctree[index];
        if (thisNodeData > 2147483647 || thisNodeData == 0) {
            NodeInfo thisNodeStruct;
            thisNodeStruct.pos = position;
            thisNodeStruct.size = size;
            thisNodeStruct.data = thisNodeData;
            return thisNodeStruct;
        }

        int halfSize = size / 2;

        unsigned int childIndex = thisNodeData;
        vec3 newPosition;

        float addedX = position.x + halfSize;
        if (point.x > addedX || (point.x == addedX && rayDir.x > 0)) {
            childIndex += 4;
            newPosition.x = addedX;
        }
        else {
            newPosition.x = position.x;
        }

        float addedY = position.y + halfSize;
        if (point.y > addedY || (point.y == addedY && rayDir.y > 0)) {
            childIndex += 2;
            newPosition.y = addedY;
        }
        else {
            newPosition.y = position.y;
        }

        float addedZ = position.z + halfSize;
        if (point.z > addedZ || (point.z == addedZ && rayDir.z > 0)) {
            childIndex += 1;
            newPosition.z = addedZ;
        }
        else {
            newPosition.z = position.z;
        }

        position = newPosition;
        size = halfSize;
        index = childIndex;
    }
}
unsigned int MainOctreeIntercectMaterialID(vec3 origin, vec3 dir)
{
    vec3 face1TU = vec3(1, 0, 0);
    vec3 face1TV = vec3(0, 1, 0);
    vec3 exitFace1PosOfset;
    vec3 entryFace1PosOfset;
    if (dir.z < 0)
    {
        exitFace1PosOfset = vec3(0, 0, 0);
        entryFace1PosOfset = vec3(0, 0, 1);
    }
    else
    {
        exitFace1PosOfset = vec3(0, 0, 1);
        entryFace1PosOfset = vec3(0, 0, 0);
    }

    vec3 face2TU = vec3(0, 0, 1);
    vec3 face2TV = vec3(0, 1, 0);
    vec3 exitFace2PosOfset;
    vec3 entryFace2PosOfset;
    if (dir.x < 0)
    {
        exitFace2PosOfset = vec3(0, 0, 0);
        entryFace2PosOfset = vec3(1, 0, 0);
    }
    else
    {
        exitFace2PosOfset = vec3(1, 0, 0);
        entryFace2PosOfset = vec3(0, 0, 0);
    }

    vec3 face3TU = vec3(0, 0, 1);
    vec3 face3TV = vec3(1, 0, 0);
    vec3 exitFace3PosOfset;
    vec3 entryFace3PosOfset;
    if (dir.y < 0)
    {
        exitFace3PosOfset = vec3(0, 0, 0);
        entryFace3PosOfset = vec3(0, 1, 0);
    }
    else
    {
        exitFace3PosOfset = vec3(0, 1, 0);
        entryFace3PosOfset = vec3(0, 0, 0);
    }


    NodeInfo thisTraversedNode;
    vec3 thisTraversedPoint;

    if (cameraPosition.x < 0 || cameraPosition.x > mainOctreeRootSize ||
        cameraPosition.y < 0 || cameraPosition.y > mainOctreeRootSize ||
        cameraPosition.z < 0 || cameraPosition.z > mainOctreeRootSize) {
        vec3 hitFace1Point = RayHitFace(cameraPosition, dir, entryFace1PosOfset * mainOctreeRootSize, face1TU * mainOctreeRootSize, face1TV * mainOctreeRootSize);
        if (
            hitFace1Point != vec3(-1, -1, -1)
            ) {
            thisTraversedPoint = hitFace1Point;
        }
        else {

            vec3 hitFace2Point = RayHitFace(cameraPosition, dir, entryFace2PosOfset * mainOctreeRootSize, face2TU * mainOctreeRootSize, face2TV * mainOctreeRootSize);
            if (
                hitFace2Point != vec3(-1, -1, -1)
                ) {
                thisTraversedPoint = hitFace2Point;
            }
            else {
                vec3 hitFace3Point = RayHitFace(cameraPosition, dir, entryFace3PosOfset * mainOctreeRootSize, face3TU * mainOctreeRootSize, face3TV * mainOctreeRootSize);
                if (
                    hitFace3Point != vec3(-1, -1, -1)
                    ) {
                    thisTraversedPoint = hitFace3Point;
                }
                else {
                    return 0;
                }
            }
        }
    }
    else {
        thisTraversedPoint = cameraPosition;
    }

    thisTraversedNode = GetNodeFromPoint(thisTraversedPoint);

    while (true)
    {
        if (thisTraversedNode.data > 2147483647)
        {
            return thisTraversedNode.data - 2147483647;
        }

        vec3 hitFace1Point = RayHitFace(thisTraversedPoint, dir, thisTraversedNode.pos + exitFace1PosOfset * thisTraversedNode.size, face1TU * thisTraversedNode.size, face1TV * thisTraversedNode.size);
        if (
            hitFace1Point != vec3(-1, -1, -1)
            ) {
            thisTraversedPoint = hitFace1Point;
            thisTraversedNode = GetNodeFromPoint(thisTraversedPoint);
        }
        else {

            vec3 hitFace2Point = RayHitFace(thisTraversedPoint, dir, thisTraversedNode.pos + exitFace2PosOfset * thisTraversedNode.size, face2TU * thisTraversedNode.size, face2TV * thisTraversedNode.size);
            if (
                hitFace2Point != vec3(-1, -1, -1)
                ) {
                thisTraversedPoint = hitFace2Point;
                thisTraversedNode = GetNodeFromPoint(thisTraversedPoint);
            }
            else {
                vec3 hitFace3Point = RayHitFace(thisTraversedPoint, dir, thisTraversedNode.pos + exitFace3PosOfset * thisTraversedNode.size, face3TU * thisTraversedNode.size, face3TV * thisTraversedNode.size);
                if (
                    hitFace3Point != vec3(-1, -1, -1)
                    ) {
                    thisTraversedPoint = hitFace3Point;
                    thisTraversedNode = GetNodeFromPoint(thisTraversedPoint);
                }
                else {
                    return 0;
                }
            }
        }
    }

    return 0;
};

void main()
{
    //vec4 pixelCol = vec4(1, 0, 0, 0);

    // In the fragment shader 'screenSpaceCoords' is not calculated because I can use texture coordinates passed down from the vertex shader
    ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy);
    ivec2 dims = imageSize(imageOutput);
    vec2 screenSpaceCoords = vec2(
        float(pixelCoords.x * 2 - dims.x) / dims.x,
        float(pixelCoords.y * 2 - dims.y) / dims.y
    );

    vec4 screenSpaceRayDirection = vec4(screenSpaceCoords.x * aspectRatio, screenSpaceCoords.y, -1, 0);
    rayDir = normalize(vec3(cameraTransformationMatrix * screenSpaceRayDirection));
    unsigned int material = MainOctreeIntercectMaterialID(cameraPosition, rayDir);

    // Commented out to prove its not whats slowing things down \/
    //if (material == 0)
    //{
    //  pixelCol = vec4(0.1, 0, 0, 0); // Background Color
    //}
    //else
    //{
    //  unsigned int matOfset = (material - 1) * 3;
    //  pixelCol = vec4(materials[matOfset + 0], materials[matOfset + 1], materials[matOfset + 2], 0);
    //}
    //imageStore(imageOutput, pixelCoords, pixelCol);
}
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.