【问题标题】:Percieved width of a decal depending on the rotation angle of the wall贴花的感知宽度取决于墙壁的旋转角度
【发布时间】:2021-06-30 17:27:14
【问题描述】:

我正在使用 JavaScript 画布从头开始创建一个光线投射游戏。

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

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

  • 计算到贴花位置的距离(位置定义为位于面向观察者的网格顶点的中间)
  • 屏幕坐标decalScreenX是根据网格坐标到屏幕坐标的变换矩阵计算得出的。这可以正常工作:

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

  • 然后我检索相关贴花的图像数据并获取它的宽度和高度
  • 根据距离和观察到的角度,我计算出贴花的感知宽度。这是真正的问题所在,因为我发现我没有完全准确地计算这个宽度。
  • 有了所有这些信息,就可以轻松计算左右屏幕坐标 - 从哪里开始和在哪里结束绘制贴花,使用 H-BUFFER 计算高度因子并使用 GRID_BUFFER 仅在网格上绘制属于这个贴花。

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

或者如果玩家方向与贴花方向直接相反,则该角度为 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,但我认为这个问题并不特定于任何编程语言。

【问题讨论】:

  • 嗯,这听起来像是光线投射与 3D 渲染引擎的结合……这很奇怪,但速度很快。然而,它会导致这样的问题。我会以不同的方式执行此操作,并以与墙壁相同的方式渲染“贴花”图像……但是为此,您需要将其存储在与墙壁纹理相同的分辨率/大小并将其用作精灵(不渲染边框空间)所以要么混合或其他测试......

标签: math graphics geometry raycasting perspective


【解决方案1】:

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

  • 我在贴花定义中添加了两个点 - 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,并对其进行了调整,以便它们适合屏幕边界或在完全离屏时从函数返回
  • 最后,贴花的感知宽度甚至不需要,因为可以使用当前绘图stripe - 绝对绘图开始和绝对绘图结束的差 - 绝对绘图开始之间的差异比率来计算纹理位置:
let texX = (((stripe - drawStartX_abs) / (drawEndX_abs - drawStartX_abs)) * imageData.width) | 0;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多