c++ - 在 GLSL 中优化光线追踪着色器

标签 c++ opengl optimization glsl gpu

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

目前raytracer代码如下:

#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 倍

最佳答案

您似乎测试了与八叉树每个级别中所有体素的大部分光线的交点。并在每个级别对它们进行排序(按一定距离)。 我提出另一种方法。

如果光线与边界框(八叉树的第 0 级)相交,它会在框的两个面上。或者在角落或边缘,这些都是“角落”情况。

可以像here 那样找到3D 射线-平面相交点.可以通过测试该点是否在面的两个三角形之一的内部来确定交点是否在面内(四边形),例如 here。 .

获取距离相机最远的路口I0。还让 r 是方向 I0 朝向相机的光线的单位 vector 。

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

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

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

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

可以根据存储信息的方式优化八叉树的工作:所有可能的节点或仅使用。具有数据的节点或只是指向另一个具有数据的容器的“指针”。这是另一个问题的问题。

关于c++ - 在 GLSL 中优化光线追踪着色器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50778260/

相关文章:

c++ - 如何将 CSV 文件导入 QTableWidget

java - OpenGL glfwInit()自动执行?

cocoa - 如何使用 AVFoundation 从视频中获取 openGL 纹理?

mysql - INNER JOIN 之前的 WHERE 子句

javascript - 在 JavaScript 中使用或不使用中间变量时代码是否更优化

MySQL 阻塞(或者如何优化它)

C++:模板类指针转换

c++ - 指向临时字符串的指针

c++ - 类中的段错误

opengl - 缓存 OpenGL 状态仍然有意义吗?