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
- ForwardS
- BackwardA
- LeftD
- Right
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.
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 16X16 map to move around and demonstrate the basic ray tracing
#
represent the wall.
represent the empty space
Just simply using while(true)
for now for the continuous rendering.
The following image contains the player, the wall and the FOV of the player in the game (red dotted line).
General Conventions :
= current player position on map = ray angle = player angle = FOV = 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.
Then we can define a unit vector eye
to depict the direction of the ray using the ray angle obtained
Then we make integral test vectors to determine the position using the
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.
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
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.
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
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)
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
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.