Оптимизация шейдера трассировки лучей в GLSL - PullRequest
0 голосов
/ 09 июня 2018

Я кодировал raytracer на основе вокселизации, который работает как ожидалось, но очень медленно.

В настоящее время код 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 итераций.

Однако этот шейдер занимает в среднем 200 000 микросекунд в gtx 1070.

Поскольку проблема заключается не в количестве выполнений, моя проблема, вероятно, связана с выполнением потока.

Кто-нибудь знает, как я мог оптимизировать этот код?Самым большим узким местом является использование стека.

Если я запускаю один и тот же код, не помещая в стек (генерируя неправильный вывод), время выполнения улучшается в 10 раз

Ответы [ 3 ]

0 голосов
/ 13 июня 2018

Первое, что выделяется - это функция пересечения ящиков.Взгляните на процедурную рамку * inigo quilez ' для гораздо более быстрой версии.Так как размер вашего ящика одинаков по всем осям и вам не нужен нормальный, вы можете получить еще более легкую версию.По сути, используйте математические методы вместо метода грубой силы, который проверяет каждую плоскость коробки.

Также старайтесь избегать временного хранения, где это возможно.Например, массив углов может быть вычислен по требованию для каждого блока октодерева.Конечно, с учетом приведенного выше предложения они будут заменены на боксовые центры.

Поскольку к nodes, levels и positions всегда обращаются вместе, попробуйте разместить их вместе в новой единой структуре иполучить доступ к ним как к единому устройству.

Подробнее будет позже ...

0 голосов
/ 19 июня 2018

Выполнение потоков в графическом процессоре может быть очень параллельным, но это не значит, что все потоки работают независимо друг от друга.Группы потоков выполняют одинаковые инструкции, единственное отличие - входные данные.Это означает, что ветвления и, следовательно, циклы не могут заставить поток расходиться при выполнении и, следовательно, также не позволяют им завершаться раньше.

В вашем примере показан самый крайний случай: когда существует высокая вероятность того, что в группе потоков вся выполняемая работа относится только к одному потоку.

Чтобы облегчить это, вы должны попытаться уменьшить разницу в длине выполнения (итерации в вашем случае) для потоков в группе (или в целом).Это можно сделать, установив ограничение на число итераций на один проход шейдера и изменив расписание только тех потоков / пикселей, которым требуется больше итераций.

0 голосов
/ 12 июня 2018

Кажется, вы проверяете на пересечение с лучом больше всего вокселей на каждом уровне октодерева.И сортировать их (по некоторому расстоянию) также на каждом уровне.Я предлагаю другой подход.

Если луч пересекается с ограничительной рамкой (уровень 0 октреева), он попадает на две грани рамки.Или в углу или на краю, это «угловые» случаи.

Найти пересечение трехмерной плоскости луча можно, например, здесь .Выяснить, находится ли пересечение внутри грани (квад), можно, проверив, находится ли точка внутри одного из двух треугольников грани, например, здесь .

Получитьсамое дальнее пересечение I0 от камеры.Также позвольте r быть единичным вектором луча в направлении I0 к камере.

Найдите самый глубокий воксел для I0 координат.Это самый дальний воксель от камеры.

Теперь нам нужны координаты выхода I0e для луча в этом вокселе через другое лицо.Хотя вы могли бы снова выполнить вычисления для всех 6 граней, если ваши вокселы выровнены по X, Y, X и вы определяете луч в той же системе координат, что и октре, тогда вычисления значительно упрощаются.

Примените небольшое смещение (например, 1/1000 наименьшего размера вокселя) к I0e на r единичный вектор луча: I1 = I0e + r/1000.Найдите воксель к этим I1.Это следующий воксель в отсортированном списке пересечений воксельных лучей.

Повторите поиск I1e, затем I2, затем I2e, затем I3 и т. Д. До тех пор, пока не выйдет ограничивающая рамка.Список пересеченных вокселей отсортирован.

Работа с октодеревом может быть оптимизирована в зависимости от того, как вы храните его информацию: все возможные узлы или только что использованные.Узлы с данными или просто «указатели» на другой контейнер с данными.Это вопрос другого вопроса.

...