Skip to content

A simple ray tracing engine in the form of ASCII (and extended ASCII) characters demostrating basic algorithms used for ray tracing with the help of basic math.

Notifications You must be signed in to change notification settings

pranitzope24/ASCII-Ray-Tracing-cpp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

fc9f7aa · Apr 11, 2023

History

31 Commits
Apr 10, 2023
Apr 8, 2023
Apr 10, 2023
Apr 10, 2023
Apr 11, 2023

Repository files navigation

ASCII-Ray-Tracing-cpp

Usage

Firstly, set the CLI window display size to 120x40 (some users might need to use 121x41 depending on the windows version)

In windows powershell, go/create your desired directory and clone this repository and cd into it

make
.\ray_tracing.exe

Controls

  • W - Forward
  • S - Backward
  • A - Left
  • D - Right

Approach for Building

Firstly, std::cout isn't going to work since the time taken is much higher than directly writing on the console. Hence we will use conventional printf or similar methods and its variants for our main display. Later figured out printing to screen buffer is even better.

Made a screen buffer of size 120x40

Using windows.h created a screen buffer using createConsoleScreenBuffer() and service to print directly onto it any desired manner using the coordinates using the WriteConsoleOutputCharacterW() method.

Made a basic Map

  • Made a 16X16 map to move around and demonstrate the basic ray tracing
  • # represent the wall
  • . represent the empty space

Initialised the main Game Loop

Just simply using while(true) for now for the continuous rendering.

Algorithm

The following image contains the player, the wall and the FOV of the player in the game (red dotted line).

Depiction of the algorithm

Distance and Ray angle calculation

General Conventions :

  • [ P x ,   P y ] = current player position on map
  • α r = ray angle
  • α p = player angle
  • θ = FOV
  • d = distance to wall

We calculate the distance to the wall by incrementing until the flag hitWall becomes true. (Depicted by the blue dashed line). If the point of calculation is out of bounds, we set it to the defined depth. Else we can continue until it reaches a wall and use the incremented value as the distance to wall.

α r = ( α p θ 2 ) + x i t e r w s c r θ

Then we can define a unit vector eye to depict the direction of the ray using the ray angle obtained

e ^ = [ s i n ( α r ) ,   c o s ( α r ) ]

Then we make integral test vectors to determine the position using the e ^ we just made. Simply T = [ P x , P y ] + d e ^ is the test vector. Using this and the map boundaries, we decide the distance to wall, either is the iterative value, or the maximum depth.

Ceiling and Flooring

This part is fairly easy and just involves some perspective. Upper half of the screen will have the ceiling and farther the wall is, lower the ceiling.

c = h s c r 2 h s c r d

f = h c r c

Results so far

We print to the buffer column wise having a wall visible with the # and floor and ceiling as empty spaces.

for(int y=0; y<screenHeight; y++){
    screen[y*screenWidth + x] = ((y<=ceiling)?' ':((y>ceiling&&y<=floor)?'#':' '));
}

Two spawns ( 8.0 , 8.0 , 0.0 ) and ( 8.0 , 8.0 , π 4 ) are shown based on the current code

spawn1

spawn2

Movement

GetAsyncKeyState() return a short integer whose most significant digit is 1. Hence, to check if it's pressed, we can us the bitwise & with a bitmask. Hence we structure the if statement like

if (GetAsyncKeyState('A') & 0x8000) {/*...*/}  //for all kinds of movements

0x8000 is 1000000000000000 in binary, hence we get 1 if the key is pressed, and get 0 if it's not pressed. Begin with an arbitary increment and dcrement in the playerA and take input using the GetAsyncKeyState(). Turns out that the movement is very fast and we need to time it according to the fram rate we are recieving.

Fixed by multiplying the angular speed to the frame time (calculated using std::chrono).

Hence,

  • for rotations, we simply increment or decrement the angles (maybe set an angular speed variable later on).
  • For position, we simply transform the coordinates in the Player frame and traverse using the additions of sines and cosines of playerA to the current position with a given speed.

Shading Walls for Better Clarity

Distinguishing the wall in the form of hashes was difficult to understand. Hence did a distance based shading for the walls. Based on comparision of depth proportioanlity and distance of the wall/obstacle from the player, used the extended ASCII characters

  • █ - hex 0x2588 U+2588 For full shading
  • ▓ - hex 0x2593 U+2593 For dark shading
  • ▒ - hex 0x2592 U+2592 For medium shading
  • ░ - hex 0x2591 U+2591 For light shading
  • blank character for very distant walls

wall shading

Fixing the Boundaries and Collisions

Used the std::wstring map [y*w + x] such that if the FOV array of distances lands on a # character, then it would revert the movement caused, hence never crossing a wall or a block.

At this point, the wall is visible but it's still difficult to distinguish between the boundaries of the wall. Hence we will need to define boundaries by hightlighting corners or edges in periodic intervals.

To do this, (well see how this goes)

Floor Shading (just for aesthetics)

Using a ratio to set the floor shading since the relative distance to the flooring will remain constant as we don't have any motion in the z direction.

f r = 1 y i t e r h s c r 2 h s c r 2

and then shade the portions accordingly. Now it looks like it has a 3D perspective. Only the shape boundary has to be designed for a better view. floor shading

About

A simple ray tracing engine in the form of ASCII (and extended ASCII) characters demostrating basic algorithms used for ray tracing with the help of basic math.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published