贴花的感知宽度取决于墙壁的旋转角度

Percieved width of a decal depending on the rotation angle of the wall

我正在使用 JavaScript canvas 从头开始​​创建光线投射游戏。

部分挑战(对我而言)是用随机图像(图片)装饰墙壁。我已经实现了墙壁、地板、天花板和精灵的绘制。 在绘制墙壁时,我为每个 x(描绘屏幕坐标)存储到墙壁的距离(Z-BUFFER)、墙壁的高度(H-BUFFER)和像素的实际坐标底层二维网格 (GRID_BUFFER).

我在墙上绘制贴花(图片)的方法如下(在确定理论上可能可见的贴花列表之后):

let decalScreenX = Math.floor((RAYCAST.SCREEN_WIDTH / 2) * (1 + CAMERA.transformX /CAMERA.transformDepth));

我看到宽度计算是根据贴花从玩家方向向量旋转一个角度,如果玩家方向与贴花面向 space 的方向不相反(示例):

或者如果玩家方向与贴花方向直接相反,则此角度为 0°(示例):

我的第一个方法是使用反向玩家方向和贴花面对方向的点积,从而得到向量之间角度的余弦值,并将其用作减少感知宽度的一个因素:

let CosA = PLAYER.dir.mirror().dot(decal.facingDir);
let widthScale = CosA * (CAMERA.transformDepth / decal.distance);

这个解决方案的问题是,当垂直时,系数为 0 并且不绘制贴花,但由于墙壁是用透视绘制的,所以情况不应该如此。于是我开始即兴创作。我定义了 CAMERA.minPerspective 因子,如下所示。视野 (FOV) 为 70°。

CAMERA.minPerspective = Math.cos(Math.radians((90 + this.FOV) / 2));

我的直觉是(唉,因为我缺乏透视和几何知识)对于小角度,系数应该保持为 1。对于接近 90° 的角度,应该有一些最小的系数,这样贴花仍然存在可见的。所以我带来了这个“改进”的代码:

let CosA = PLAYER.dir.mirror().dot(decal.facingDir);
let FACTOR = Math.min(1, CosA + CAMERA.minPerspective);
FACTOR = Math.max(FACTOR, CAMERA.minPerspective);
let widthScale = FACTOR * (CAMERA.transformDepth / decal.distance);

这种方法的效果要好得多,但也有一些缺陷。从视觉上看,对于 0-50° 的角度,折减系数太大。如果我使用这种宽度的贴花,可以观察到这一点,它们应该覆盖整个网格表面。 (见下图;楼梯左侧下面的墙是可见的,贴花应该覆盖完整的网格,但它没有覆盖,因为 FACTOR 太小了)。

我已经在 Stack Overflow 和 Web 的其余部分中搜索了更好的解决方案,似乎我的几何知识也阻止了我在脱离上下文的情况下识别正确的解决方案。

所以,请。可能存在用于计算感知宽度的确定性解决方案,无需再次使用光线投射阶段或使用我能够在光线投射阶段存储的信息。虽然代码示例中使用了 JavaScript,但我认为这个问题并不特定于任何编程语言。

我找到了保留(甚至改进)问题方法的简单性和时间复杂性的解决方案。

  • 我在贴花定义中添加了两点 - leftDrawStartrightStartDraw。这些在贴花点很容易计算 实例化,基于真实的精灵(贴花)宽度和定义 网格(块)大小。在进行此计算时,我从相机角度(而不是网格坐标)考虑leftDrawStart
  • 渲染贴花时,我使用 leftDrawStartrightStartDraw 的变换矩阵(如下面的代码示例)屏幕坐标从它们的网格坐标计算:
transform(spritePos) {
    let invDet = 1.0 / (CAMERA.dir.x * PLAYER.dir.y - PLAYER.dir.x * CAMERA.dir.y);
    CAMERA.transformX = invDet * (PLAYER.dir.y * spritePos.x - PLAYER.dir.x * spritePos.y);
    CAMERA.transformDepth = invDet * (-CAMERA.dir.y * spritePos.x + CAMERA.dir.x * spritePos.y);
  }
  • 我区分计算出的绝对 drawStartXdrawEndX,以及它们的调整,以便它们适合屏幕边界,或者 return 如果它们完全在屏幕外
  • 最后,甚至不需要贴花的感知宽度,因为纹理位置可以通过使用当前绘图之间的差异比率来计算 stripe - 绝对绘图开始和绝对绘图结束的差异 - 绝对绘图开始:
let texX = (((stripe - drawStartX_abs) / (drawEndX_abs - drawStartX_abs)) * imageData.width) | 0;

与在光线投射步骤中加入贴花投射的方法相比,该方法完全准确且速度要快得多。