1

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

现在我已经到了透视投影相机可以在世界中移动的地步,我可以渲染(主要是减去一些需要调查的人工制品)透视正确的“世界”3维视图,它基本上是空的,但包含斯坦福兔子的体素立方体。

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

屏幕截图:(1) 射线投射体素同时... ...(2) 相机保持... ...(3) 严格轴对齐。

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

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

相机“向右旋转了 39 度” ——请注意屏幕 #2 中立方体的蓝色阴影左侧在此旋转中是如何不可见的,但现在“它确实应该”!

现在我当然知道了这一点:在一个简单的轴对齐无旋转设置中,就像我在开始时所做的那样,光线只是以小步长穿过正 z 方向,向左或向右和顶部发散或底部仅取决于像素位置和投影矩阵。当我“向右或向左旋转相机”时——即我围绕 Y 轴旋转它——这些步骤应该简单地通过适当的旋转矩阵进行转换,对吗?因此,对于向前遍历,Z 步越小,凸轮旋转得越多,偏移量为 X 步中的“增加”。然而,对于基于像素位置的水平+垂直发散,需要将 x 步的增加部分“添加”到 z 步。不知何故,我试验过的许多矩阵都没有,

这是我的基本每射线预遍历算法——Go 中的语法,但将其作为伪代码:

  • fxfy:像素位置 x 和 y
  • rayPos : vec3 表示世界空间中的光线起始位置(计算如下)
  • rayDir : vec3,用于在光线遍历期间的每个步骤中将 xyz 步骤添加到 rayPos
  • 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,这些部分“基本上是正确的”(虽然不漂亮)——当轴对齐/未旋转时。

4

1 回答 1

4

如果您将系统想象成针孔相机而不是其他任何东西,这会容易得多。与其从代表图像的矩形表面射出光线,不如从一个点射出光线,穿过将成为图像平面的矩形,进入场景。所有的主光线都应该有相同的原点,只是方向略有不同。方向是使用基本触发确定的,您希望它们通过图像平面中的哪个像素。举个最简单的例子,假设你的点在相机上,你的图像平面是沿 z 轴的一个单位,高和宽两个单位。这样,左上角的像素想要从 (0,0,0) 到 (-1, -1, 1)。归一化 (-1, -1, 1) 以获得方向。(你不

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

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

于 2012-06-28T21:25:55.957 回答