优化GLSL中的光线跟踪着色器

时间:2018-06-09 20:28:51

标签: c++ opengl optimization glsl gpu

我编写了一个基于体素化的光线跟踪器,它按预期工作但速度非常慢。

目前光线跟踪器代码如下:

#version 430 
//normalized positon from (-1, -1) to (1, 1)
in vec2 f_coord;

out vec4 fragment_color;

struct Voxel
{
    vec4 position;
    vec4 normal;
    vec4 color;
};

struct Node
{
    //children of the current node
    int children[8];
};

layout(std430, binding = 0) buffer voxel_buffer
{
    //last layer of the tree, the leafs
    Voxel voxels[];
};
layout(std430, binding = 1) buffer buffer_index
{
    uint index;
};
layout(std430, binding = 2) buffer tree_buffer
{
    //tree structure       
    Node tree[];
};
layout(std430, binding = 3) buffer tree_index
{
    uint t_index;
};

uniform vec3 camera_pos; //position of the camera
uniform float aspect_ratio; // aspect ratio of the window
uniform float cube_dim; //Dimenions of the voxelization cube
uniform int voxel_resolution; //Side length of the cube in voxels

#define EPSILON 0.01
// Detect whether a position is inside of the voxel with size size located at corner
bool inBoxBounds(vec3 corner, float size, vec3 position)
{
    bool inside = true;
    position-=corner;//coordinate of the position relative to the box coordinate system
    //Test that all coordinates are inside the box, if any is outisde, the point is out the box
    for(int i=0; i<3; i++)
    {
        inside = inside && (position[i] > -EPSILON);
        inside = inside && (position[i] < size+EPSILON);
    }

    return inside;
}

//Get the distance to a box or infinity if the box cannot be hit
float boxIntersection(vec3 origin, vec3 dir, vec3 corner0, float size)
{
    dir = normalize(dir);
    vec3 corner1 = corner0 + vec3(size,size,size);//Oposite corner of the box

    float coeffs[6];
    //Calculate the intersaction coefficients with te 6 bonding planes 
    coeffs[0] = (corner0.x - origin.x)/(dir.x);
    coeffs[1] = (corner0.y - origin.y)/(dir.y);
    coeffs[2] = (corner0.z - origin.z)/(dir.z);

    coeffs[3] = (corner1.x - origin.x)/(dir.x);
    coeffs[4] = (corner1.y - origin.y)/(dir.y);
    coeffs[5] = (corner1.z - origin.z)/(dir.z);
    //by default the distance to the box is infinity
    float t = 1.f/0.f;

    for(uint i=0; i<6; i++){
        //if the distance to a boxis negative, we set it to infinity as we cannot travel in the negative direction
        coeffs[i] = coeffs[i] < 0 ? 1.f/0.f : coeffs[i];
        //The distance is the minumum of the previous calculated distance and the current distance
        t = inBoxBounds(corner0,size,origin+dir*coeffs[i]) ? min(coeffs[i],t) : t;
    }

    return t;
}

#define MAX_TREE_HEIGHT 11
int nodes[MAX_TREE_HEIGHT];
int levels[MAX_TREE_HEIGHT];
vec3 positions[MAX_TREE_HEIGHT];
int sp=0;

void push(int node, int level, vec3 corner)
{
    nodes[sp] = node;
    levels[sp] = level;
    positions[sp] = corner;
    sp++;
}

void main()
{   
    int count = 0; //count the iterations of the algorithm
    vec3 r = vec3(f_coord.x, f_coord.y, 1.f/tan(radians(40))); //direction of the ray
    r.y/=aspect_ratio; //modify the direction based on the windows aspect ratio
    vec3 dir = r;
    r += vec3(0,0,-1.f/tan(radians(40))) + camera_pos; //put the ray at the camera position

    fragment_color = vec4(0);
    int max_level = int(log2(voxel_resolution));//height of the tree
    push(0,0,vec3(-cube_dim));//set the stack
    float tc = 1.f; //initial color value, to be decreased whenever a voxel is hit
    //tree variables
    int level=0;
    int node=0;
    vec3 corner;

    do
    {
        //pop from stack
        sp--;
        node = nodes[sp];
        level = levels[sp];
        corner = positions[sp];

        //set the size of the current voxel 
        float size = cube_dim / pow(2,level);
        //set the corners of the children
        vec3 corners[] =
            {corner,                        corner+vec3(0,0,size),
            corner+vec3(0, size,0),         corner+vec3(0,size,size),
            corner+vec3(size,0,0),          corner+vec3(size,0,size),
            corner+vec3(size,size,0),       corner+vec3(size,size,size)};

        float coeffs[8];
        for(int child=0; child<8; child++)
        {
            //Test non zero childs, zero childs are empty and thus should be discarded
            coeffs[child] = tree[node].children[child]>0?
                //Get the distance to your child if it's not empty or infinity if it's empty
                boxIntersection(r, dir, corners[child], size) : 1.f/0.f;
        }
        int indices[8] = {0,1,2,3,4,5,6,7};
        //sort the children from closest to farthest
        for(uint i=0; i<8; i++)
        {
            for(uint j=i; j<8; j++)
            {
                if((coeffs[j] < coeffs[i]))
                {
                    float swap = coeffs[i];
                    coeffs[i] = coeffs[j];
                    coeffs[j] = swap;

                    int iSwap = indices[i];
                    indices[i] = indices[j];
                    indices[j] = iSwap;

                    vec3 vSwap = corners[i];
                    corners[i] = corners[j];
                    corners[j] = vSwap;
                }
            }
        }
        //push to stack
        for(uint i=7; i>=0; i--)
        {
            if(!isinf(coeffs[i]))
            {
                push(tree[node].children[indices[i]],
                    level+1, corners[i]);
            }
        }
        count++;
    }while(level < (max_level-1) && sp>0);
    //set color
    fragment_color = vec4(count)/100;
}

由于可能不完全清楚这是做什么的,让我解释一下。

我们检查从一个大立方体开始的光线盒交叉点。如果我们点击它,我们测试与构成它的8个立方体的交集。

如果我们击中那些,我们检查构成该立方体的8个立方体的交叉点。

在2D中,这将如下所示:

enter image description here

在这种情况下,我们有4层,我们首先检查大盒子,然后检查红色,然后是绿色,最后是蓝色。

打印出光线追踪步骤作为颜色执行的次数(这是我提供的代码片段)

产生以下图像:

enter image description here

如您所见,大多数情况下着色器的迭代次数不超过100次。

然而,这个着色器在gtx 1070中平均执行200 000微秒。

由于问题不是执行次数,我的问题很可能是线程执行问题。

有谁知道如何优化此代码? 最大的底线似乎是使用筹码。

如果我在不推送到堆栈的情况下运行相同的代码(生成错误的输出),运行时间会有10倍的改进

3 个答案:

答案 0 :(得分:2)

似乎你测试的是与八角形的每个级别中的大多数体素的光线相交。并在每个级别中对它们进行排序(相隔一段距离)。 我提出了另一种方法。

如果光线与边界框(八叉树的0级)相交,则它会在框的两个面上。或者在一个角落或一个边缘,这些是#34;角落&#34;例。

查找3D光线平面交点可以像here那样完成。通过测试该点是否位于面部的两个三角形之一内,如here,可以查找交点是否在面内(四边形)。

从相机获取最远的交叉点I0。同时让r成为朝着相机I0方向的光线的单位矢量。

找到I0坐标的最深体素。这是相机中最远的体素。

现在,我们希望通过另一个面,该体素中的光线的出口坐标I0e。虽然您可以再次对所有6个面进行计算,但如果您的体素是X,Y,X对齐并且您在与八叉树相同的坐标系中定义光线,那么计算会简化很多。

通过光线的I0e单位矢量向r应用一点位移(例如,最小体素大小的1/1000):I1 = I0e + r/1000 。找到这些I1的体素。这是体素射线交叉点排序列表中的下一个体素。

重复发现I1e然后I2然后I2e然后I3等,直到退出边界框。交叉体素列表已排序。

根据您存储信息的方式,可以优化使用八叉树:所有可能的节点或仅使用。带有数据的节点或只是&#34;指针&#34;带有数据的另一个容器。这是另一个问题的问题。

答案 1 :(得分:2)

首先突出的是你的盒子交叉功能。查看更快版本的inigo quilez' procedural box function。由于你的盒子尺寸在所有轴上是均匀的,你不需要outNormal,你可以获得更轻的版本。实质上,使用数学而不是测试每个盒子平面的蛮力方法。

另外,尽可能避免临时存储。例如,可以根据需要为每个八叉树盒计算角阵列。当然,根据上述建议,这些将改为盒子中心。

由于始终一起访问nodeslevelspositions,因此请尝试将它们放在一个新的单个结构中,并将它们作为一个单元进行访问。

看起来会更晚......

答案 2 :(得分:1)

GPU上的线程执行可能是大规模并行的,但这并不意味着所有线程彼此独立运行。线程组执行完全相同的指令,唯一的区别是输入数据。这意味着分支和循环不能使线程在执行中发散,因此也不能让它们提前终止。

您的示例显示了最极端的情况:当一组线程中存在很高的可能性时,所有已完成的工作仅与一个线程相关。

为了缓解这种情况,您应该尝试减少组(或总体)中线程的执行长度(在您的情况下为迭代)的差异。这可以通过设置每个着色器传递的迭代次数限制并仅重新调度那些需要更多迭代的线程/像素来完成。