如何在 HTML-Canvas 上渲染光线投射墙纹理

How to render raycasted wall textures on HTML-Canvas

我正在尝试构建光线投射引擎。我已经成功地使用 ctx.fillRect() 逐列渲染了一个场景,如下所示。

canvas-raycasting.png

demo code

我为上面的渲染写的代码:

var scene = [];// this contains distance of wall from player for an perticular ray 
var points = [];// this contains point at which ray hits the wall 

/*
i have a Raycaster class which does all math required for ray casting and returns 
an object which contains two arrays 
1 > scene : array of numbers representing distance of wall from player.
2 > points : contains objects of type { x , y } representing point where ray hits the wall.
*/

var data = raycaster.cast(wall);
/* 
raycaster : instance of Raycaster class , 
walls : array of boundries constains object of type { x1 , y1 , x2 , y2 } where 
(x1,y1) represent start point ,
(x2,y2) represent end point.
*/
scene = data.scene; 

var scene_width = 800;
var scene_height = 400; 
var w = scene_width / scene.length;
for(var i=0;i<scene.length;++i){
    var c = scene[i] == Infinity ? 500 : scene[i] ;

    var s = map(c,0,500,255,0);// how dark/bright the wall should be 
    var h = map(c,0,500,scene_height,10); // relative height of wall (farther the smaller)

    ctx.beginPath();
    ctx.fillStyle = 'rgb('+s+','+s+','+s+')';
    ctx.fillRect(i*w,200-h*0.5,w+1,h);
    ctx.closePath();
}

现在我正在尝试构建一个基于网络的 FPS(第一人称射击游戏)并坚持在 canvas 上渲染墙壁纹理。

ctx.drawImage() 方法接受参数如下

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

ctx.drawImage_arguments

但是 ctx.drawImage() 方法将图像绘制为矩形,没有像 Wolfenstein 3D

那样的 3D 效果

我不知道该怎么做。

我应该使用ctx.tranform()吗?如果是,如何?如果不是,我该怎么办?

我正在寻找用于使用 2D 光线投射产生伪 3d 效果的数学。

一些伪3d游戏是Wolfenstein 3D Doom

我正在尝试构建类似 this

的东西

谢谢你:)

您映射(或不映射,视情况而定)纹理坐标的方式未按预期工作。

I am looking for the Maths used to produce pseudo 3d effect using 2D raycasting

Wikipedia Entry for Texture Mapping 有一个很好的部分,其中包含有关 Doom 如何执行纹理贴图的特定数学运算。来自条目:

The Doom engine restricted the world to vertical walls and horizontal floors/ceilings, with a camera that could only rotate about the vertical axis. This meant the walls would be a constant depth coordinate along a vertical line and the floors/ceilings would have a constant depth along a horizontal line. A fast affine mapping could be used along those lines because it would be correct.

A "fast affine mapping" 只是纹理坐标的简单 2D 插值,对于您正在尝试的操作来说是一个合适的操作。 Doom 引擎的限制还在于

Doom renders vertical and horizontal spans with affine texture mapping, and is therefore unable to draw ramped floors or slanted walls.

您的逻辑似乎不包含任何用于在各种坐标空间之间转换坐标的代码。您至少需要在给定的光线追踪坐标和纹理坐标空间之间应用变换。这通常涉及矩阵数学并且非常常见,也可以称为 Projection,就像将点从一个 space/surface 投影到另一个。通过仿射变换,您可以避免使用矩阵来支持线性插值。

适用于您的变量(见上文)的坐标方程可能如下所示:

u = (1 - a) * wallStart + a * wallEnd

where 0 <= *a* <= 1

或者,您可以使用 Weak Perspective projection,因为您已经计算了很多数据。再次来自维基百科:

To determine which screen x-coordinate corresponds to a point at

A_x_,A_z_
multiply the point coordinates by:

B_x = A_x * B_z / A_z
where

B_x
is the screen x coordinate
A_x
is the model x coordinate
B_z
is the focal length—the axial distance from the camera center *to the image plane*
A_z
is the subject distance.
Because the camera is in 3D, the same works for the screen y-coordinate, substituting y for x in the above diagram and equation.

在您的例子中,A_x 是墙在世界空间中的位置。 B_z是焦距,就是1A_z 是您使用光线追踪计算的距离。结果是代表视空间平移的 x 或 y 坐标。

main draw routine for W3D documents the techniques used to raytrace and transform coordinates for rendering the game. The code is quite readable even if you're not familiar with C/ASM and is a great way to learn more about your topics of interest. For more reading, I would suggest performing a search in your engine of choice for things like "matrix transformation of coordinates for texture mapping", or search the GameDev SE site类似。 该文件的特定区域 zero-in 将是从 ln 267 开始的此部分:

> ========================
> =
> = TransformTile
> =
> = Takes paramaters:
> =   tx,ty     : tile the object is centered in
> =
> = globals:
> =   viewx,viewy       : point of view
> =   viewcos,viewsin   : sin/cos of viewangle
> =   scale     : conversion from global value to screen value
> =
> = sets:
> =   screenx,transx,transy,screenheight: projected edge location and size
> =
> = Returns true if the tile is withing getting distance
> =

一本关于 "teh Maths" 的好书是 this one - 我强烈推荐给任何想要创造或提高这些技能的人。

更新: 本质上,您将把图像中的像素(点)映射到矩形 wall-tile 上的点,如光线追踪所报告的那样。

伪(ish)代码:

var image = getImage(someImage); // get the image however you want. make sure it finishes loading before drawing
var iWidth = image.width, iHeight = image.height;
var sX = 0, sY = 0; // top-left corner of image. Adjust when using e.g., sprite sheets

for(var i=0;i<scene.length;++i){
    var c = scene[i] == Infinity ? 500 : scene[i];

    var s = map(c,0,500,255,0);// how dark/bright the wall should be 
    var h = map(c,0,500,scene_height,10); // relative height of wall (farther the smaller)
    var wX = i*w, wY = 200 - h * 0.5;
    var wWidth = w + 1, wHeight = h;
//... render the rectangle shape
    /* we are using the same image, but we are scaling it to the size of the rectangle
     and placing it at the same location as the wall. 

    */
    var u, v, uW, vH; // texture x- and y- values and sizes. compute these.

    ctx.drawImage(image, sX, sY, iWidth, iHeight, u, v, uW, vH); 
}

由于我不熟悉您执行光线追踪的代码、它的坐标系等,您可能需要进一步调整 wXwY、[=20= 的值],以及 wHeight(例如,将点从中心平移到 top-left 角)。