Как генерировать лучи камеры для raycasting

Я пытаюсь сделать простой воксельный движок с OpenGL и C++. Мой первый шаг — отправить лучи из камеры и определить, пересекался ли луч с чем-то (для целей тестирования это всего две плоскости). Я заставил его работать без вращения камеры, создав полноэкранный четырехугольник и запрограммировав фрагментный шейдер на отправку луча для каждого фрагмента (пока что я просто предполагаю, что фрагмент - это пиксель), который находится в направлении texCoord. х, texCoord.y, -1. Теперь я пытаюсь реализовать вращение камеры.

Я попытался сгенерировать матрицу вращения внутри процессора и отправить ее в шейдер, который будет умножать ее на каждый луч. Однако, когда я поворачиваю камеру, плоскости начинают растягиваться таким образом, что я могу описать только это видео. https://www.youtube.com/watch?v=6NScMwnPe8c

Вот код, который создает матрицу и запускается каждый кадр:

float pi = 3.141592;
// camRotX and Y are defined elsewhere and can be controlled from the keyboard during runtime.
glm::vec3 camEulerAngles = glm::vec3(camRotX, camRotY, 0);

std::cout << "X: " << camEulerAngles.x << " Y: " << camEulerAngles.y << "\n";

// Convert to radians
camEulerAngles.x = camEulerAngles.x * pi / 180;
camEulerAngles.y = camEulerAngles.y * pi / 180;
camEulerAngles.z = camEulerAngles.z * pi / 180;

// Generate Quaternian
glm::quat camRotation;
camRotation = glm::quat(camEulerAngles);

// Generate rotation matrix from quaternian
glm::mat4 camToWorldMatrix = glm::toMat4(camRotation);
// No transformation matrix is created because the rays should be relative to 0,0,0

// Send the rotation matrix to the shader
int camTransformMatrixID = glGetUniformLocation(shader, "cameraTransformationMatrix");
glUniformMatrix4fv(camTransformMatrixID, 1, GL_FALSE, glm::value_ptr(camToWorldMatrix));

И фрагментный шейдер:

#version 330 core
in vec4 texCoord;

layout(location = 0) out vec4 color;
uniform vec3 cameraPosition;
uniform vec3 cameraTR;
uniform vec3 cameraTL;
uniform vec3 cameraBR;
uniform vec3 cameraBL;
uniform mat4 cameraTransformationMatrix;
uniform float fov;
uniform float aspectRatio;
float pi = 3.141592;

int RayHitCell(vec3 origin, vec3 direction, vec3 cellPosition, float cellSize)
{
    if(direction.z != 0)
    {
        float multiplicationFactorFront = cellPosition.z - origin.z;
        if(multiplicationFactorFront > 0){
            vec2 interceptFront = vec2(direction.x * multiplicationFactorFront + origin.x,
                                       direction.y * multiplicationFactorFront + origin.y);
            if(interceptFront.x > cellPosition.x && interceptFront.x < cellPosition.x + cellSize &&
               interceptFront.y > cellPosition.y && interceptFront.y < cellPosition.y + cellSize)
            {
                return 1;
            }
        }
        float multiplicationFactorBack = cellPosition.z + cellSize - origin.z;
        if(multiplicationFactorBack > 0){
            vec2 interceptBack = vec2(direction.x * multiplicationFactorBack + origin.x,
                                      direction.y * multiplicationFactorBack + origin.y);
            if(interceptBack.x > cellPosition.x && interceptBack.x < cellPosition.x + cellSize &&
               interceptBack.y > cellPosition.y && interceptBack.y < cellPosition.y + cellSize)
            {
                return 2;
            }
        }
    }
    return 0;
}

void main()
{
    // For now I'm not accounting for FOV and aspect ratio because I want to get the rotation working first
    vec4 beforeRotateRayDirection = vec4(texCoord.x,texCoord.y,-1,0);
    // Apply the rotation matrix that was generated on the cpu
    vec3 rayDirection = vec3(cameraTransformationMatrix *  beforeRotateRayDirection);

    int t = RayHitCell(cameraPosition, rayDirection, vec3(0,0,5), 1);
    if(t == 1)
    {
        // Hit front plane
        color = vec4(0, 0, 1, 0);
    }else if(t == 2)
    {
        // Hit back plane
        color = vec4(0, 0, 0.5, 0);
    }else{
        // background color
        color = vec4(0, 1, 0, 0);
    }
}

person C. Lang    schedule 16.04.2020    source источник
comment
см. Как лучше всего написать воксельный движок на C с учетом производительности... в основном вы будете генерировать направление в вершинном шейдере с помощью вычитание фокуса камеры из положения вершины и нормализация... при рендеринге одного QUAD, покрывающего близлежащую плоскость (весь экран/вид), положение фрагмента - это начало луча, а интерполированное направление от вершины - это направление   -  person Spektre    schedule 17.04.2020


Ответы (1)


Хорошо. Очень трудно понять, что не так, тем не менее я попробую.

Вот несколько советов и замечаний:

1) Вы можете отлаживать направления, сопоставляя их с цветом RGB. Имейте в виду, что вы должны нормализовать векторы и сопоставить от (-1,1) до (0,1). Просто сделайте что-нибудь типа dir*0.5+1.0. Пример:

color = vec4(normalize(rayDirection) * 0.5, 0) + vec4(1);

2) Вы можете получить матрицу вращения более прямым способом. Кватернион инициализируется с направления вперед, сначала он будет вращаться вокруг оси Y (горизонтальный вид), затем, и только потом, вокруг оси X (вертикальный вид). Имейте в виду, что порядок поворотов зависит от реализации, если вы инициализируете из углов Эйлера. Используйте mat4_cast, чтобы по возможности избегать экспериментального расширения glm (gtx). Пример:

// Define rotation quaternion starting from look rotation
glm::quat camRotation = glm::vec3(0, 0, 0);
camRotation = glm::rotate(camRotation, glm::radians(camRotY), glm::vec3(0, 1, 0));
camRotation = glm::rotate(camRotation, glm::radians(camRotX), glm::vec3(1, 0, 0));
glm::mat4 camToWorldMatrix = glm::mat4_cast(camRotation);

3) Ваш beforeRotateRayDirection - это вектор, который (вероятно) указывает от (-1,-1,-1) до (1,1,-1). Который не нормализован, длина (1,1,1) равна √3 ≈ 1,7320508075688772... Убедитесь, что вы приняли это во внимание при расчете столкновений или просто нормализуйте вектор.

Мой частичный ответ до сих пор...

Ваш тест на столкновение немного странный... Похоже, вы хотите направить луч в плоскость Z для заданной позиции ячейки (но дважды, один раз спереди и один сзади). Я просмотрел логику вашего кода, и в ней есть некоторый смысл, но без вершинной программы, то есть не зная, каковы значения диапазона texCoord, невозможно быть уверенным. Возможно, вы захотите переосмыслить свою логику примерно так:

int RayHitCell(vec3 origin, vec3 direction, vec3 cellPosition, float cellSize)
{
    //Get triangle side vectors
    vec3 tu = vec3(cellSize,0,0);   //Triangle U component
    vec3 tv = vec3(0,cellSize,0);   //Triangle V component

    //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 0;

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

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

    return 1;
}

void main()
{
    // For now I'm not accounting for FOV and aspect ratio because I want to get the 
    // rotation working first
    vec4 beforeRotateRayDirection = vec4(texCoord.x, texCoord.y, -1, 0);
    // Apply the rotation matrix that was generated on the cpu
    vec3 rayDirection = vec3(cameraTransformationMatrix * beforeRotateRayDirection);

    int t = RayHitCell(cameraPosition, normalize(rayDirection), vec3(0,0,5), 1);
    if (t == 1)
    {
        // Hit front plane
        color = vec4(0, 0, 1, 0);
    }
    else
    {
        // background color
        color = vec4(0, 1, 0, 0);
    }
}

Это должно дать вам самолет, дайте мне знать, если это сработает. Куб сделать будет очень просто.

PS.: u и v можно использовать для наложения текстур.

person GKann    schedule 17.04.2020
comment
Обновление функции RayHitCell заставило ее работать отлично. Я думал, что проблема как-то связана с матрицей вращения, поэтому, думаю, поэтому я не нашел решения. Вы сделали мой день - вы не представляете, как долго я работаю над этой проблемой :) - person C. Lang; 17.04.2020
comment
Приятно знать! Если вам нужна помощь в настройке функции для других лиц вокселей, дайте мне знать. Имейте в виду, что у вас, вероятно, будут проблемы с точностью, если вы отодвинетесь дальше от начала координат (0,0,0). Я думаю, вы можете справиться с этим, вычитая положение камеры из положения ячейки, что несколько близко к тому, как это делается с матрицей MVP (особенно часть «вид»). - person GKann; 18.04.2020
comment
Хотя мне удалось заставить это работать для куба, используя разные значения tu и tv для каждой грани (которые я нашел методом проб и ошибок), я действительно не понимаю, как работает эта функция. Не могли бы вы указать мне сайт, который объясняет это визуально, или поисковый запрос, который я мог бы использовать, чтобы узнать больше? Кроме того, как я могу найти положение в мировом пространстве, в котором происходит пересечение? Спасибо еще раз. - person C. Lang; 25.04.2020
comment
Конечно! Логика представляет собой оптимизированную версию этого алгоритма по ссылка, посмотрите на параметрическую форму. В нашем случае «u» и «v» — это компоненты столкновения, спроецированные на плоскость ячейки, а «t» — это спроецированные на сам луч. Есть 2 способа получить позицию попадания, вы можете использовать 't': vec3 hitPos = origin + direction * t Или вы можете использовать 'u' и 'v': vec3 hitPos = planeOrigin + u * tu + v * tv - person GKann; 26.04.2020
comment
Путем проб и ошибок я обнаружил, что второй менее склонен к ошибкам точности с плавающей запятой... - person GKann; 26.04.2020
comment
Весь алгоритм представляет собой инверсию матрицы, указанной в ссылке, отдельные строки объявления u, v и t соответствуют строке * умножению столбца двух матриц, указанных на этом изображении ссылка - person GKann; 26.04.2020