This commit is contained in:
Miloslav Ciz 2023-12-28 18:17:00 +01:00
parent 078a02731b
commit f856572509
16 changed files with 36 additions and 26 deletions

View file

@ -49,11 +49,13 @@ In [computer graphics](graphics.md) raycasting refers to a rendering technique i
*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).
The method is called *2D* because even though the rendered picture looks like a 3D view, the rays we are casting are 2 dimensional and the representation of the world we are rendering is also usually 2 dimensional (typically 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 3D rays and uses "fully 3D" environment representations. 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).
The principle is following: for each column we want to render we cast a ray from the camera and find out which wall in our 2D world it hits first and at what distance -- according to the distance we use [perspective](perspective.md) to calculate how tall the wall columns should look from the camera's point of view, and we render the column. Tracing the ray through the 2D grid representing the environment can be done relatively efficiently with algorithms normally used for line rasterization. There is another advantage for weak-hardware computers: we can easily use 2D raycasting **without a [framebuffer](framebuffer.md)** (without [double_buffering](double_buffering.md)) because we can render each frame top-to-bottom left-to-right without overwriting any pixels (as we simply cast the rays from left to right and then draw each column top-to-bottom). And of course, it can be implemented using [fixed point](fixed_point.md) (integers only).
SIDENOTE: The distinction between 2D and 3D raycasting may get [fuzzy](fuzzy.md), the transition may be gradual. It is possible to have "real 3D" world (with some limitations) but draw it using 2D raycasting, [Anarch](anarch.md) does something like that -- it uses 2D raycasting for rendering but player and projectiles have full X, Y and Z coordinates. Also consider for example performing 2D raycasting but having 3 layers of the 2D world, allowing for 3 different height levels; now we've added the extra Z dimension to 2D raycasting, though this dimension is small (Z coordinate of world cell can only be 0, 1 or 2), however we will now be casting 3 rays for each column and are getting closer to the full 3D raycasting. This is just to show that as with everything we can usually do "something in between".
The classic version of 2D raycasting -- as seen in the early 90s games -- only renders walls with [textures](texture.md); floors and ceilings are untextured and have a solid color. The walls all have the same height, the floor and ceiling also have the same height in the whole environment. In the walls there can be sliding doors. 2D sprites ([billboards](billboard.md)) can be used with raycasting to add items or characters in the environment -- for correct rendering here we usually need a 1 dimensional [z-buffer](z_buffer.md) in which we write distances to walls to correctly draw sprites that are e.g. partially behind a corner. However we can **extend** raycasting to allow levels with different heights of walls, floor and ceiling, we can add floor and ceiling texturing and, in theory, probably also use different level geometry than a square grid (however at this point it would be worth considering if e.g. [BSP](bsp.md) rendering wouldn't be better).
Back to pure 2D raycasting; the principle is following: for each column we want to render we cast a ray from the camera and find out which wall in our 2D world it hits first and at what distance -- according to the distance we use [perspective](perspective.md) to calculate how tall the wall columns should look from the camera's point of view, and we render the column. Tracing the ray through the 2D grid representing the environment can be done relatively efficiently with algorithms normally used for line rasterization. There is another advantage for weak-hardware computers: we can easily use 2D raycasting **without a [framebuffer](framebuffer.md)** (without [double_buffering](double_buffering.md)) because we can render each frame top-to-bottom left-to-right without overwriting any pixels (as we simply cast the rays from left to right and then draw each column top-to-bottom). And of course, it can be implemented using [fixed point](fixed_point.md) (integers only).
The classic version of 2D raycasting -- as seen in the early 90s games -- only renders walls with [textures](texture.md); floors and ceilings are untextured and have a solid color. The walls all have the same height, the floor and ceiling also have the same height in the whole environment. In the walls there can be sliding doors. 2D sprites ([billboards](billboard.md)) can be used with raycasting to add items or characters in the environment -- for correct rendering here we usually need a 1 dimensional [z-buffer](z_buffer.md) in which we write distances to walls to correctly draw sprites that are e.g. partially behind a corner. However we can **extend** raycasting to allow levels with different heights of walls, floor and ceiling, we can add floor and ceiling texturing and also use different level geometry than a square grid (however at this point it would be worth considering if e.g. [BSP](bsp.md) or [portal rendering](portal_rendering.md) wouldn't be better). An idea that might spawn from this is for example using [signed distance field](sdf.md) function to define an environment and then use 2D [raymarching](raymarching.md) to iteratively find intersections of the ray with the environment in the same way we are stepping through cells in the 2D raycasting described above.
### Implementation