HTML Canvas Ray Casting地板渲染效果异常问题排查
Wolfenstein风格射线投射地板渲染异常排查
问题背景
我正在使用Javascript/HTML-canvas开发一款Wolfenstein风格的小型游戏,开发过程参考Permadi教程。目前已经成功实现带纹理的墙体Ray Casting(射线投射)功能,接下来需要完成地板的射线投射渲染。
按照开发思路,绘制完单条墙体切片后,需要检查该墙体是否抵达画布底部:如果没有抵达,说明墙体下方区域需要渲染地板。此时需要遍历墙体底部到画布底部的每一个像素,计算这些像素对应的真实世界坐标,采样对应纹理后绘制到屏幕上。
开发过程参考两份原理示意图:

核心实现代码如下:
//we check if the wall reaches the bottom of the canvas // this.wallToBorder = (400 - wallHeight) / 2; if (this.wallToBorder > 0) { // we calculate how many pixels we have from bottom of wall to border of canvas var pixelsToBottom = Math.floor(this.wallToBorder); //we calculate the distance between the first pixel at the bottom of the wall and the player eyes (canvas.height / 2) var pixelRowHeight = 200 - pixelsToBottom; // then we loop through every pixels until we reach the border of the canvas for (let i = pixelRowHeight; i < 200; i++) { // we calculate the straight distance between the player and the pixel var directDistFloor = this.screenDist * (canvas.height/2) / Math.floor(i); // we calculate it's real world distance with the angle relative to the player var realDistance = directDistFloor / Math.cos(this.angleR); // we calculate it's real world coordinates with the player angle this.floorPointx = this.player.x + Math.cos(this.angle) * realDistance; this.floorPointy = this.player.y - Math.sin(this.angle) * realDistance; // we map the texture var textY = Math.floor(this.floorPointx % 64); var textX = Math.floor(this.floorPointy % 64); var pixWidthHeight = (1 / realDistance) * this.screenDist; if (pixWidthHeight < 1) pixWidthHeight = 1; // we draw it on the canvas this.ctx.drawImage(wallsSprite, textX, textY + 64, 1, 1, this.index, i + 200, pixWidthHeight, pixWidthHeight); } }
实际运行后渲染结果不符合预期,异常效果如下:
完整项目可在StackBlitz查看,入口文件为ray.js,需要排查实现逻辑中的错误。
错误点与修正方案
代码共有4处核心逻辑错误,逐一修正即可得到正确的渲染效果:
- 距离计算逻辑错误
计算地板点到玩家的垂直投影距离时,不应对行偏移值i提前做Math.floor()取整,否则会丢失精度导致近远景过渡错位,且当i值小于1时会触发除以0的异常;同时公式里的高度参数应该用玩家视高(和项目匹配的32单位,对应64尺寸墙高的一半),而非画布半高。修正为:var directDistFloor = this.screenDist * 32 / i; - 多余的鱼眼校正逻辑
计算出的directDistFloor已经是垂直于投影平面的校正后距离,不需要再除以射线夹角余弦值做二次鱼眼校正,直接用这个距离沿当前射线方向计算世界坐标即可,多余的除法会导致远处地板被异常拉伸。 - 纹理采样逻辑错误
一是X、Y轴的纹理坐标映射写反,二是没有处理世界坐标为负数时取模得到负纹理坐标的问题,会采样到纹理范围外的错误像素;额外添加的pixWidthHeight像素缩放逻辑完全多余,逐列绘制地板时单像素尺寸固定为1*1即可,不需要动态缩放,否则会出现像素重叠、错位。修正后的纹理采样与绘制代码为:this.floorPointx = this.player.x + Math.cos(this.angle) * directDistFloor; this.floorPointy = this.player.y - Math.sin(this.angle) * directDistFloor; var textX = Math.floor(this.floorPointx % 64); var textY = Math.floor(this.floorPointy % 64); if (textX < 0) textX += 64; if (textY < 0) textY += 64; this.ctx.drawImage(wallsSprite, textX, textY + 64, 1, 1, this.index, i + 200, 1, 1); - 循环边界精度问题
计算pixelsToBottom、pixelRowHeight时不需要提前做Math.floor()取整,浮点数计算可以保证墙体和地板衔接处没有缝隙,浏览器绘制时会自动处理像素对齐。
修正后墙体和地板可以自然衔接,纹理透视关系正确,不会出现拉伸、错位的异常效果。
内容的提问来源于stack exchange,提问作者Toto Briac
相关产品推荐
相关产品推荐

