Update
This commit is contained in:
parent
c856794e38
commit
eca7446b98
8 changed files with 279 additions and 40 deletions
276
raycasting.md
276
raycasting.md
|
@ -12,39 +12,40 @@ In [computer graphics](graphics.md) raycasting refers to a rendering technique i
|
|||
2D raycasting can be used to relatively easily render "3Dish" looking environments (commonly labeled "[pseudo 3D](pseudo3D.md)"), mostly some kind of right-angled labyrinth. There are limitations such as the inability for the camera to tilt up and down (which can nevertheless be faked with shearing). It used to be popular in very old games but can still be used nowadays for "retro" looking games, games for very weak hardware (e.g. [embedded](embedded.md)), in [demos](demoscene.md) etc. It is pretty cool, very [suckless](suckless.md) rendering method.
|
||||
|
||||
```
|
||||
................................................................................
|
||||
/////////.......................................................................
|
||||
///////////////.................................................................
|
||||
//////////////////////..........................................................
|
||||
//////////////////////..........................................................
|
||||
//////////////////////..........................................................
|
||||
//////////////////////..........................................................
|
||||
//////////////////////..............................X//.........................
|
||||
//////////////////////...........................XXXX//////////............XXXXX
|
||||
//////////////////////.......................XXXXXXXX////////////////XXXXXXXXXXX
|
||||
//////////////////////.......................XXXXXXXX////////////////XXXXXXXXXXX
|
||||
/////////////////////////////////////////....XXXXXXXX////////////////XXXXXXXXXXX
|
||||
/////////////////////////////////////////////XXXXXXXX////////////////XXXXXXXXXXX
|
||||
/////////////////////////////////////////////XXXXXXXX////////////////XXXXXXXXXXX
|
||||
/////////////////////////////////////////////XXXXXXXX////////////////XXXXXXXXXXX
|
||||
/////////////////////////////////////////////XXXXXXXX////////////////XXXXXXXXXXX
|
||||
/////////////////////////////////////////////XXXXXXXX////////////////XXXXXXXXXXX
|
||||
/////////////////////////////////////////....XXXXXXXX////////////////XXXXXXXXXXX
|
||||
//////////////////////.......................XXXXXXXX////////////////XXXXXXXXXXX
|
||||
//////////////////////.......................XXXXXXXX////////////////XXXXXXXXXXX
|
||||
//////////////////////...........................XXXX//////////............XXXXX
|
||||
//////////////////////..............................X//.........................
|
||||
//////////////////////..........................................................
|
||||
//////////////////////..........................................................
|
||||
//////////////////////..........................................................
|
||||
//////////////////////..........................................................
|
||||
///////////////.................................................................
|
||||
/////////.......................................................................
|
||||
................................................................................
|
||||
................................................................................
|
||||
....................................................................................................
|
||||
....................................................................................................
|
||||
###.................................................................................................
|
||||
#########...........................................................................................
|
||||
#########...........................................................................................
|
||||
#########...........................................................................................
|
||||
#########...........................................................................................
|
||||
#########.................///######..................................../#...........................
|
||||
#########..............//////############.....................//////////###.........................
|
||||
#########..............//////############............///////////////////####............////////////
|
||||
#########......///#####//////############.........//////////////////////####////////////////////////
|
||||
###############///#####//////############.........//////////////////////####////////////////////////
|
||||
###############///#####//////############//#####////////////////////////####////////////////////////
|
||||
###############///#####//////############//#####////////////////////////####////////////////////////
|
||||
###############///#####//////############//#####////////////////////////####////////////////////////
|
||||
###############///#####//////############//#####////////////////////////####////////////////////////
|
||||
###############///#####//////############//#####////////////////////////####////////////////////////
|
||||
###############///#####//////############//#####////////////////////////####////////////////////////
|
||||
###############///#####//////############//#####////////////////////////####////////////////////////
|
||||
###############///#####//////############.........//////////////////////####////////////////////////
|
||||
#########......///#####//////############.........//////////////////////####////////////////////////
|
||||
#########..............//////############............///////////////////####............////////////
|
||||
#########..............//////############.....................//////////###.........................
|
||||
#########.................///######..................................../#...........................
|
||||
#########...........................................................................................
|
||||
#########...........................................................................................
|
||||
#########...........................................................................................
|
||||
#########...........................................................................................
|
||||
###.................................................................................................
|
||||
....................................................................................................
|
||||
....................................................................................................
|
||||
```
|
||||
|
||||
*image rendered with [raycastlib](raycastlib.md)*
|
||||
*raycasted view, rendered by the example below*
|
||||
|
||||
The method is called *2D* because even though the rendered picture looks like a 3D view, the representation of the world we are rendering is 2 dimensional (usually a grid, a top-down plan of the environment with cells of either empty space or walls) and the casting of the rays is performed in this 2D space -- unlike with the 3D raycasting which really does cast rays in fully 3D environments. Also unlike with the 3D version which casts one ray per each rendered pixel (x * y rays per frame), 2D raycasting only casts **one ray per rendered column** (x rays per frame) which actually, compared to the 3D version, drastically reduces the number of rays cast and makes this method **fast enough for [real time](real_time.md)** rendering even using [software_rendering](sw_rendering.md) (without a GPU).
|
||||
|
||||
|
@ -73,4 +74,215 @@ The core element to implement is the code for casting rays, i.e. given the squar
|
|||
|
||||
In the above *V* is the position of the camera (viewer) which is facing towards the point *I*, *p* is the camera plane perpendicular to *VI* at the distance 1 from *V*. Ray *r* is cast from the camera and hits the point *X*. The length of the line *r* is the Euclidean distance, however we want to find out the distance *JX = VI*, which is perpendicular to *p*. There are two similar triangles: *VCA* and *VIB*; from this it follows that *1 / VA = VI / VB*, from which we derive that *JX = VB / VA*. We can therefore calculate the perpendicular distance just from the ratio of the distances along one principal axis (X or Y). However watch out for the case when *VA = VB = 0* to not divide by zero! In such case use the other principal axis (Y).
|
||||
|
||||
TODO: code
|
||||
Here is a complete **[C](c.md) example** that uses only [fixed point](fixed_point.md) with the exception of the stdlib [sin](sin.md)/[cos](cos.md) functions, for simplicity's sake (these can easily be replaced by custom fixed point implementation):
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <math.h> // for simplicity we'll use float sin, cos from stdlib
|
||||
|
||||
#define U 1024 // fixed-point unit
|
||||
#define LEVEL_SIZE 16 // level resolution
|
||||
#define SCREEN_W 100
|
||||
#define SCREEN_H 31
|
||||
|
||||
int wallHeight[SCREEN_W];
|
||||
int wallDir[SCREEN_W];
|
||||
|
||||
int perspective(int distance)
|
||||
{
|
||||
if (distance <= 0)
|
||||
distance = 1;
|
||||
|
||||
return (SCREEN_H * U) / distance;
|
||||
}
|
||||
|
||||
unsigned char level[LEVEL_SIZE * LEVEL_SIZE] =
|
||||
{
|
||||
#define E 1, // wall
|
||||
#define l 0, // floor
|
||||
l l l l E l l l l l l l l l E E
|
||||
l E l l E E E l l l l l E l l E
|
||||
l l l l l l l l l l l l l l l l
|
||||
l E l l E l E l E l E l E l l l
|
||||
l l l l E l l l l l l l l l E l
|
||||
l l l l E l l l l l l l l l E l
|
||||
l E E l E l l l l l l l l l l l
|
||||
l E E l E l l l l l l l l l l l
|
||||
l E l l l l l l l l l l l l l E
|
||||
l E l l E l l l l l l l l E l l
|
||||
l E l l E l l l l l l l l E l l
|
||||
l E l l l l E E E l l l l l l l
|
||||
l E E l E l l l l l E E E l l E
|
||||
l E E l E l l l l l E l l l E E
|
||||
l l l l l l E E E E E l l E E E
|
||||
l l E l l l l l l l l l E E E E
|
||||
#undef E
|
||||
#undef l
|
||||
};
|
||||
|
||||
unsigned char getTile(int x, int y)
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= LEVEL_SIZE || y >= LEVEL_SIZE)
|
||||
return 1;
|
||||
|
||||
return level[y * LEVEL_SIZE + x];
|
||||
}
|
||||
|
||||
// returns perpend. distance to hit and wall direction (0 or 1) in dir
|
||||
int castRay(int rayX, int rayY, int rayDx, int rayDy, int *dir)
|
||||
{
|
||||
int tileX = rayX / U,
|
||||
tileY = rayY / U,
|
||||
addX = 1, addY = 1;
|
||||
|
||||
// we'll convert all cases to tracing in +x, +y direction
|
||||
|
||||
*dir = 0;
|
||||
|
||||
if (rayDx == 0)
|
||||
rayDx = 1;
|
||||
else if (rayDx < 0)
|
||||
{
|
||||
rayDx *= -1;
|
||||
addX = -1;
|
||||
rayX = (tileX + 1) * U - rayX % U;
|
||||
}
|
||||
|
||||
if (rayDy == 0)
|
||||
rayDy = 1;
|
||||
else if (rayDy < 0)
|
||||
{
|
||||
rayDy *= -1;
|
||||
addY = -1;
|
||||
rayY = (tileY + 1) * U - rayY % U;
|
||||
}
|
||||
|
||||
int origX = rayX,
|
||||
origY = rayY;
|
||||
|
||||
for (int i = 0; i < 20; ++i) // trace at most 20 squares
|
||||
{
|
||||
int px = rayX % U, // x pos. within current square
|
||||
py = rayY % U,
|
||||
tmp;
|
||||
|
||||
if (py > ((rayDy * (px - U)) / rayDx) + U)
|
||||
{
|
||||
tileY += addY; // step up
|
||||
rayY = ((rayY / U) + 1) * U;
|
||||
|
||||
tmp = rayX / U;
|
||||
rayX += (rayDx * (U - py)) / rayDy;
|
||||
|
||||
if (rayX / U != tmp) // don't cross the border due to round. error
|
||||
rayX = (tmp + 1) * U - 1;
|
||||
|
||||
*dir = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
tileX += addX; // step right
|
||||
rayX = ((rayX / U) + 1) * U;
|
||||
|
||||
tmp = rayY / U;
|
||||
rayY += (rayDy * (U - px)) / rayDx;
|
||||
|
||||
if (rayY / U != tmp)
|
||||
rayY = (tmp + 1) * U - 1;
|
||||
|
||||
*dir = 1;
|
||||
}
|
||||
|
||||
if (getTile(tileX,tileY)) // hit?
|
||||
{
|
||||
px = rayX - origX;
|
||||
py = rayY - origY;
|
||||
|
||||
// get the perpend dist. to camera plane:
|
||||
return (px > py) ? ((px * U) / rayDx) : ((py * U) / rayDy);
|
||||
|
||||
// the following would give the fish eye effect instead
|
||||
// return sqrt(px * px + py * py);
|
||||
}
|
||||
}
|
||||
|
||||
return 100 * U; // no hit found
|
||||
}
|
||||
|
||||
void drawScreen(void)
|
||||
{
|
||||
for (int y = 0; y < SCREEN_H; ++y)
|
||||
{
|
||||
int lineY = y - SCREEN_H / 2;
|
||||
|
||||
lineY = lineY >= 0 ? lineY : (-1 * lineY);
|
||||
|
||||
for (int x = 0; x < SCREEN_W; ++x)
|
||||
putchar((lineY >= wallHeight[x]) ? '.' : (wallDir[x] ? '/' : '#'));
|
||||
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int camX = 10 * U + U / 4,
|
||||
camY = 9 * U + U / 2,
|
||||
camAngle = 600, // U => full angle (2 * pi)
|
||||
quit = 0;
|
||||
|
||||
while (!quit)
|
||||
{
|
||||
int forwX = cos((2 * 3.14 * camAngle) / ((float) U) ) * U,
|
||||
forwY = sin((2 * 3.14 * camAngle) / ((float) U) ) * U,
|
||||
vecFromX = forwX + forwY, // leftmost ray
|
||||
vecFromY = forwY - forwX,
|
||||
vecToX = forwX - forwY, // rightmost ray
|
||||
vecToY = forwY + forwX;
|
||||
|
||||
for (int i = 0; i < SCREEN_W; ++i) // process each screen column
|
||||
{
|
||||
// interpolate rays between vecFrom and vecTo
|
||||
int rayDx = (SCREEN_W - 1 - i) * vecFromX / SCREEN_W + (vecToX * i) / SCREEN_W,
|
||||
rayDy = (SCREEN_W - 1 - i) * vecFromY / SCREEN_W + (vecToY * i) / SCREEN_W,
|
||||
dir,
|
||||
dist = castRay(camX,camY,rayDx,rayDy,&dir);
|
||||
|
||||
wallHeight[i] = perspective(dist);
|
||||
wallDir[i] = dir;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
putchar('\n');
|
||||
|
||||
drawScreen();
|
||||
|
||||
char c = getchar();
|
||||
|
||||
switch (c) // movement
|
||||
{
|
||||
case 'a': camAngle -= 30; break;
|
||||
case 'd': camAngle += 30; break;
|
||||
case 'w': camX += forwX / 2; camY += forwY / 2; break;
|
||||
case 's': camX -= forwX / 2; camY -= forwY / 2; break;
|
||||
case 'q': quit = 1; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
How to make this more advanced? Here are some hints and tips:
|
||||
|
||||
- **textured walls**: This is pretty simply, the ray hit basically gives us a horizontal texturing coordinate, and we simply stretch the texture vertically to fit the wall. I.e. when the ray hits a wall, we take the hit coordinate along the principal axis of the wall (e.g. for vertical hit we take the Y coordinate) and [mod](mod.md) it by the fixed point unit which will give us the texturing coordinate. This coordinate tells us the column of the texture that the rendered column shall have; we read this texture column and render it stretched vertically to fit the column height given by the perspective. Note that for [cache](cache.md) friendliness ([optimization](optimization.md)) textures should be stored column-wide in memory as during rendering we'll be reading the texture by columns (row-wise stored textures would make us jump wide distances in the memory which CPU caches don't like).
|
||||
- **textured floor/ceiling**: Something aking [mode7](mode7.md) rendering can be used.
|
||||
- **sliding door**: TODO
|
||||
- **jumping**: Camera can easily be shifted up and down. If we are to place the camera e.g. one fixed point unit above its original position, then for each column we render we compute, with perspective applied to this one fixed point unit (the same way with which we determine the column size on the screen) the vertical screen-space offset of the wall and render this wall column that many pixel lower.
|
||||
- **looking up/down**: Correct view of a camera that's slightly tilted up/down can't be achieved (at least not in a reasonably simple way), but there's a simple trick for faking it -- camera shearing. Shearing literally just shifts the rendered view vertically, i.e. if we're to look a bit up, we render that same way as usual but start higher up on the screen (in the part of the rendered image that's normally above the screen and not visible), so that the vertical center of the screen will be shifted downwards. For smaller angles this looks [good enough](good_enough.md).
|
||||
- **multilevel floor/ceiling**: This is a bit more difficult but it can be done e.g. like this: for each level square we store its floor and ceiling height. When casting a ray, we will consider any change in ceiling and/or floor height a hit, AND we'll need to return multiple of those hits (not just the first one). When we cast a ray and get a set of such hits, from each hit we'll know there are tiny walls on the floor and/or ceiling, and we'll know their distances. This can be used to correctly render everything.
|
||||
- **different level geometry**: In theory the level doesn't have to be a square grid but some kind of another representation, or we may keep it a square grid but allow placement of additional shapes in it such as cylinders etc. Here you simply have to figure out how to trace the rays so as to find the first thing it hits.
|
||||
- **adding [billboards](billboard.md) (sprites)**: TODO
|
||||
- **reflections**: We can make our 2D raycaster a 2D [raytracer](raytracing.md), i.e. when we cast a camera ray and it hits a reflective wall (a mirror), we cast another, secondary reflected ray and trace it to see which wall it hits, i.e. which wall will get reflected in the reflective wall.
|
||||
- **partly transparent walls**: We can make some walls partially transparent, both with [alpha blending](alpha.md) or textures with transparent pixels. In both cases we'll have to look not just for the first hit of the ray, but also for the next.
|
Loading…
Add table
Add a link
Reference in a new issue