如何为光线投射生成相机光线

Posted

技术标签:

【中文标题】如何为光线投射生成相机光线【英文标题】:How to generate camera rays for ray casting 【发布时间】:2020-04-16 14:55:47 【问题描述】:

我正在尝试使用 OpenGL 和 C++ 制作一个简单的体素引擎。我的第一步是从相机发出光线并检测光线是否与某物相交(出于测试目的,它只有两个平面)。我通过创建一个全屏四边形并编程片段着色器为每个片段(现在我只是假设一个片段是一个像素)发出一条在 texCoord 方向上的射线,从而在没有相机旋转的情况下让它工作。 x,texCoord.y,-1。现在我正在尝试实现相机旋转。

我尝试在 cpu 中生成一个旋转矩阵并将其发送到着色器,着色器将它与每条光线相乘。然而,当我旋转相机时,平面开始以一种我只能用这个视频来描述的方式拉伸。 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);
    

【问题讨论】:

见How to best write a voxel engine in C with performance in mind ...基本上你将通过从顶点位置减去相机焦点并归一化来在顶点着色器中生成方向......同时渲染覆盖znear平面(整个屏幕/视图)片段位置的单个QUAD是光线的起点,从顶点插值的方向是方向 【参考方案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...确保在碰撞数学中考虑到这一点,或者只是对向量进行归一化。 p>

到目前为止我的部分答案......

您的碰撞测试有点奇怪......您似乎想将光线投射到给定单元位置的 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.:uv 可用于纹理映射。

【讨论】:

更新 RayHitCell 函数使其完美运行。我认为这个问题与旋转矩阵有关,所以我想这就是我没有找到解决方案的原因。你让我很开心——你不知道我在这个问题上工作了多长时间:) 很高兴知道!如果您需要任何帮助来调整其他体素面的功能,请告诉我。请记住,如果您远离原点 (0,0,0),您可能会遇到精度问题。我认为您可以通过从单元格位置中减去相机位置来处理它,这有点接近 MVP 矩阵的处理方式(尤其是“视图”部分)。 虽然,我已经设法通过为每个面使用不同的 tu 和 tv 值(我通过反复试验发现)使这个适用于立方体,但我并不真正了解该函数是如何工作的。您能否指出一个可以直观地解释这一点的网站或一个我可以用来查找更多信息的搜索词?另外,我怎样才能找到交叉点发生的世界空间位置?再次感谢。 没问题!逻辑是这个算法的优化版本link,查看参数形式。在我们的例子中,“u”和“v”是投影到单元平面的碰撞命中的分量,“t”是投影到射线本身的分量。有两种方法可以得到命中位置,你可以使用't':vec3 hitPos = origin + direction * t或者你可以使用'u'和'v':vec3 hitPos = planeOrigin + u * tu + v * tv 经过反复试验,我发现第二个不太容易出现浮点精度错误...

以上是关于如何为光线投射生成相机光线的主要内容,如果未能解决你的问题,请参考以下文章

正交相机和使用光线投射选择对象

在three.js中使用正交相机进行不精确的光线投射

针对其中的盒子几何形状进行光线投射(three.js)

Three.js - 将光线从物体发送到相机

Three.js 从鼠标位置投射光线

Hololens 2 的光线投射问题