我正在尝试编写一个简单的体素 raycaster 作为学习练习。现在这纯粹是基于 CPU 的,直到我弄清楚事情是如何工作的——现在,OpenGL 只是(ab)用于尽可能频繁地将生成的位图 blit 到屏幕上。
现在我已经到了透视投影相机可以在世界中移动的地步,我可以渲染(主要是减去一些需要调查的人工制品)透视正确的“世界”3维视图,它基本上是空的,但包含斯坦福兔子的体素立方体。
所以我有一个相机,我可以上下移动,左右扫视和“向前/向后走”——到目前为止所有轴对齐,没有相机旋转。这就是我的问题。
屏幕截图:(1) 射线投射体素同时... ...(2) 相机保持... ...(3) 严格轴对齐。
现在我已经有几天试图让轮换工作了。理论上,矩阵和 3D 旋转背后的基本逻辑和理论对我来说非常清楚。然而,当相机旋转时,我只实现了“2.5 渲染”......鱼眼,有点像谷歌街景:即使我有一个体积世界表示,看起来 - 无论我尝试什么 - 就像我将首先从“前视图”创建一个渲染,然后根据相机旋转旋转该平面渲染。不用说,我现在知道旋转光线不是特别必要且容易出错。
尽管如此,在我最近的设置中,使用最简化的光线投射光线位置和方向算法,我的旋转仍然会产生相同的鱼眼平面渲染旋转样式外观:
相机“向右旋转了 39 度” ——请注意屏幕 #2 中立方体的蓝色阴影左侧在此旋转中是如何不可见的,但现在“它确实应该”!
现在我当然知道了这一点:在一个简单的轴对齐无旋转设置中,就像我在开始时所做的那样,光线只是以小步长穿过正 z 方向,向左或向右和顶部发散或底部仅取决于像素位置和投影矩阵。当我“向右或向左旋转相机”时——即我围绕 Y 轴旋转它——这些步骤应该简单地通过适当的旋转矩阵进行转换,对吗?因此,对于向前遍历,Z 步越小,凸轮旋转得越多,偏移量为 X 步中的“增加”。然而,对于基于像素位置的水平+垂直发散,需要将 x 步的增加部分“添加”到 z 步。不知何故,我试验过的许多矩阵都没有,
这是我的基本每射线预遍历算法——Go 中的语法,但将其作为伪代码:
- fx和fy:像素位置 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,这些部分“基本上是正确的”(虽然不漂亮)——当轴对齐/未旋转时。