贴花的感知宽度取决于墙壁的旋转角度
Percieved width of a decal depending on the rotation angle of the wall
我正在使用 JavaScript canvas 从头开始创建光线投射游戏。
部分挑战(对我而言)是用随机图像(图片)装饰墙壁。我已经实现了墙壁、地板、天花板和精灵的绘制。
在绘制墙壁时,我为每个 x
(描绘屏幕坐标)存储到墙壁的距离(Z-BUFFER
)、墙壁的高度(H-BUFFER
)和像素的实际坐标底层二维网格 (GRID_BUFFER
).
我在墙上绘制贴花(图片)的方法如下(在确定理论上可能可见的贴花列表之后):
- 计算到贴花位置的距离(位置定义为在面向观察者的网格顶点的中间)
- 屏幕坐标
decalScreenX
是根据网格坐标到屏幕坐标的变换矩阵计算出来的。这可以正常工作:
let decalScreenX = Math.floor((RAYCAST.SCREEN_WIDTH / 2) * (1 + CAMERA.transformX /CAMERA.transformDepth));
- 然后我检索相关贴花的图像数据并获取其宽度和高度
- 然后根据距离和观察角度,我计算出贴花的感知宽度。这才是真正的问题所在,正如我所看到的,我没有完全准确地计算这个宽度。
- 有了所有这些信息,就可以很容易地计算左右屏幕坐标 - 从哪里开始和在哪里结束绘制贴花,使用
H-BUFFER
计算高度系数并使用 GRID_BUFFER
仅在属于此贴花的网格上绘制。
我看到宽度计算是根据贴花从玩家方向向量旋转一个角度,如果玩家方向与贴花面向 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,但我认为这个问题并不特定于任何编程语言。
我找到了保留(甚至改进)问题方法的简单性和时间复杂性的解决方案。
- 我在贴花定义中添加了两点 -
leftDrawStart
和
rightStartDraw
。这些在贴花点很容易计算
实例化,基于真实的精灵(贴花)宽度和定义
网格(块)大小。在进行此计算时,我从相机角度(而不是网格坐标)考虑leftDrawStart
。
- 渲染贴花时,我使用
leftDrawStart
和 rightStartDraw
的变换矩阵(如下面的代码示例)屏幕坐标从它们的网格坐标计算:
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);
}
- 我区分计算出的绝对
drawStartX
和 drawEndX
,以及它们的调整,以便它们适合屏幕边界,或者 return 如果它们完全在屏幕外
- 最后,甚至不需要贴花的感知宽度,因为纹理位置可以通过使用当前绘图之间的差异比率来计算
stripe
- 绝对绘图开始和绝对绘图结束的差异 - 绝对绘图开始:
let texX = (((stripe - drawStartX_abs) / (drawEndX_abs - drawStartX_abs)) * imageData.width) | 0;
与在光线投射步骤中加入贴花投射的方法相比,该方法完全准确且速度要快得多。
我正在使用 JavaScript canvas 从头开始创建光线投射游戏。
部分挑战(对我而言)是用随机图像(图片)装饰墙壁。我已经实现了墙壁、地板、天花板和精灵的绘制。
在绘制墙壁时,我为每个 x
(描绘屏幕坐标)存储到墙壁的距离(Z-BUFFER
)、墙壁的高度(H-BUFFER
)和像素的实际坐标底层二维网格 (GRID_BUFFER
).
我在墙上绘制贴花(图片)的方法如下(在确定理论上可能可见的贴花列表之后):
- 计算到贴花位置的距离(位置定义为在面向观察者的网格顶点的中间)
- 屏幕坐标
decalScreenX
是根据网格坐标到屏幕坐标的变换矩阵计算出来的。这可以正常工作:
let decalScreenX = Math.floor((RAYCAST.SCREEN_WIDTH / 2) * (1 + CAMERA.transformX /CAMERA.transformDepth));
- 然后我检索相关贴花的图像数据并获取其宽度和高度
- 然后根据距离和观察角度,我计算出贴花的感知宽度。这才是真正的问题所在,正如我所看到的,我没有完全准确地计算这个宽度。
- 有了所有这些信息,就可以很容易地计算左右屏幕坐标 - 从哪里开始和在哪里结束绘制贴花,使用
H-BUFFER
计算高度系数并使用GRID_BUFFER
仅在属于此贴花的网格上绘制。
我看到宽度计算是根据贴花从玩家方向向量旋转一个角度,如果玩家方向与贴花面向 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,但我认为这个问题并不特定于任何编程语言。
我找到了保留(甚至改进)问题方法的简单性和时间复杂性的解决方案。
- 我在贴花定义中添加了两点 -
leftDrawStart
和rightStartDraw
。这些在贴花点很容易计算 实例化,基于真实的精灵(贴花)宽度和定义 网格(块)大小。在进行此计算时,我从相机角度(而不是网格坐标)考虑leftDrawStart
。 - 渲染贴花时,我使用
leftDrawStart
和rightStartDraw
的变换矩阵(如下面的代码示例)屏幕坐标从它们的网格坐标计算:
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);
}
- 我区分计算出的绝对
drawStartX
和drawEndX
,以及它们的调整,以便它们适合屏幕边界,或者 return 如果它们完全在屏幕外 - 最后,甚至不需要贴花的感知宽度,因为纹理位置可以通过使用当前绘图之间的差异比率来计算
stripe
- 绝对绘图开始和绝对绘图结束的差异 - 绝对绘图开始:
let texX = (((stripe - drawStartX_abs) / (drawEndX_abs - drawStartX_abs)) * imageData.width) | 0;
与在光线投射步骤中加入贴花投射的方法相比,该方法完全准确且速度要快得多。