歪斜:一个简单的基于CPU的体素raycaster / raytracer中的旋转相机

时间:2012-03-25 02:02:31

标签: raytracing voxel raycasting

我正在尝试编写一个简单的体素raycaster作为学习练习。这是纯粹基于CPU的,直到我弄清楚事情是如何工作的 - 现在,OpenGL只是(ab)用来尽可能频繁地将生成的位图blit到屏幕上。

现在我已经达到了透视投影相机可以穿越世界的程度,我可以渲染(主要是减去一些需要调查的文物)透视正确的“世界”三维视图,这是基本上是空的,但包含斯坦福兔子的体素立方体。

所以我有一个相机,我可以上下移动,左右扫视和“向前/向后走” - 所有轴对齐到目前为止,没有相机旋转。这就是我的问题。

屏幕截图:(1) raycasting voxels while... ...(2) the camera remains... ...(3) strictly axis-aligned.

现在我已经有几天试图让轮换工作了。从理论上讲,矩阵和3D旋转背后的基本逻辑和理论对我来说非常清楚。然而,当相机旋转时,我只能实现“2.5渲染”......鱼眼,有点像谷歌街景:即使我有体积世界表示,似乎 - 无论我尝试什么 - 就像我首先从“前视图”创建渲染,然后根据相机旋转旋转该平面渲染。毋庸置疑,我现在意识到旋转光线并不是特别必要且容易出错。

尽管如此,在我最近的设置中,使用最简化的光线投射光线位置和方向算法,我的旋转仍然会产生相同的鱼眼平面渲染旋转样式:

camera "rotated to the right by 39 degrees" - 请注意屏幕#2中蓝色阴影的左侧是如何在此轮换中看不到的,但到现在为止“它确实应该”!

现在我当然知道这一点:在一个简单的轴对齐 - 无旋转设置中,就像我在开始时一样,光线只是以小步进正z方向,向左或向左偏离或右侧和顶部或底部仅取决于像素位置和投影矩阵。当我“向右或向左旋转相机” - 即我围绕Y轴旋转时 - 这些步骤应该通过适当的旋转矩阵进行简单转换,对吧?因此,对于前向遍历,Z步骤在凸轮旋转越多时越小,在X步骤中“增加”。然而,对于基于像素位置的水平+垂直发散,需要将x步骤的增加分数“加”到z步骤。不知怎的,我试验过的很多矩阵,以及我用无矩阵硬编码的详细sin / cos计算的实验都没有真正得到这一部分。

这是我的基本per-ray预遍历算法 - Go中的语法,但将其视为伪代码:

  • fx fy :像素位置x和y
  • rayPos :vec3表示世界空间中的光线起始位置(按下图计算)
  • rayDir :vec3,用于在光线遍历期间在每个步骤中添加到rayPos的xyz步骤
  • rayStep :临时vec3
  • camPos :世界空间中相机位置的vec3
  • camRad :vec3用于以弧度为单位的相机旋转
  • pmat :典型的透视投影矩阵

算法/伪代码:

// 1: rayPos is for now "this pixel, as a vector on the view plane in 3d, at The Origin"
rayPos.X, rayPos.Y, rayPos.Z = ((fx / width) - 0.5), ((fy / height) - 0.5), 0

// 2: rotate around Y axis depending on cam rotation. No prob since view plane still at Origin 0,0,0
rayPos.MultMat(num.NewDmat4RotationY(camRad.Y))

// 3: a temp vec3. planeDist is -0.15 or some such -- fov-based dist of view plane from eye and also the non-normalized, "in axis-aligned world" traversal step size "forward into the screen"
rayStep.X, rayStep.Y, rayStep.Z = 0, 0, planeDist

// 4: rotate this too -- 0,zstep should become some meaningful xzstep,xzstep
rayStep.MultMat(num.NewDmat4RotationY(CamRad.Y))

// set up direction vector from still-origin-based-ray-position-off-rotated-view-plane plus rotated-zstep-vector
rayDir.X, rayDir.Y, rayDir.Z = -rayPos.X - me.rayStep.X, -rayPos.Y, rayPos.Z + rayStep.Z

// perspective projection
rayDir.Normalize()
rayDir.MultMat(pmat)

// before traversal, the ray starting position has to be transformed from origin-relative to campos-relative
rayPos.Add(camPos)

我正在跳过遍历和采样部分 - 根据屏幕#1到#3,这些是“基本上大部分正确”(虽然不是很漂亮) - 当轴对齐/不旋转时。

1 个答案:

答案 0 :(得分:4)

如果您将系统描绘成针孔相机而不是其他任何东西,那么这将更容易。不是从表示图像的矩形表面拍摄光线,而是将光线从一个点,通过将成为图像平面的矩形射入场景。所有主要光线应该具有相同的原点,只有略微不同的方向。使用基本三角形确定方向,通过该三角形,您希望它们通过图像平面中的像素。为了得到最简单的例子,让我们想象你的点在摄像机上,你的图像平面是沿z轴的一个单位,两个单位高和宽。这样,左上角的像素想要从(0,0,0)到(-1,-1,1)。标准化(-1,-1,1)以获得方向。 (您实际上不需要将方向标准化以进行光线交叉,但如果您决定不这样做,请记住在尝试计算光线行进的距离或类似之前,您的方向是非标准化的。)每隔一个像素,通过将平面的大小除以每个方向上的像素数来计算它想要通过你已经做过的方式的平面上的点。

然后,这是最重要的事情,尝试进行透视投影。这对于扫描转换技术来说是必要的,将每个顶点映射到屏幕上的一个点,但是在光线跟踪中,你的光线只是通过从一个点扩散到空间来实现。从起点(摄像机位置,本例中的原点)到图像平面的方向正是您需要跟踪的方向。如果你想要一个正交投影(并且你几乎从不想要这个),你可以通过使所有光线的方向相同来实现这一点,并且起始位置在整个图像平面上变化。

如果你这样做,你将有一个很好的起点。然后,您可以再次尝试添加相机旋转,方法是在迭代它以计算光线方向之前围绕原点旋转图像平面,或者直接旋转光线方向。直接旋转方向没有错!当你记住方向就是你的光线从原点开始经过的位置时,很容易看到旋转方向,并旋转它经过的点,做同样的事情。