{"id":18805583,"url":"https://github.com/melchor629/raycastergl","last_synced_at":"2025-04-13T19:12:23.942Z","repository":{"id":145875599,"uuid":"270263559","full_name":"melchor629/raycastergl","owner":"melchor629","description":"A Wolf3D-like raycaster using OpenGL shaders","archived":false,"fork":false,"pushed_at":"2023-12-10T20:47:09.000Z","size":86,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-12-10T21:35:21.256Z","etag":null,"topics":["cpp","cpp17","opengl","opengl-shaders","raycaster"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/melchor629.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null},"funding":{"ko_fi":"melchor629","custom":["paypal.me/melchor9000","www.buymeacoffee.com/melchor9000"]}},"created_at":"2020-06-07T09:59:53.000Z","updated_at":"2023-11-16T21:29:29.000Z","dependencies_parsed_at":"2023-12-10T21:44:06.128Z","dependency_job_id":null,"html_url":"https://github.com/melchor629/raycastergl","commit_stats":null,"previous_names":[],"tags_count":2,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melchor629%2Fraycastergl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melchor629%2Fraycastergl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melchor629%2Fraycastergl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melchor629%2Fraycastergl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/melchor629","download_url":"https://codeload.github.com/melchor629/raycastergl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223602520,"owners_count":17171946,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp17","opengl","opengl-shaders","raycaster"],"created_at":"2024-11-07T22:44:26.407Z","updated_at":"2024-11-07T22:44:27.041Z","avatar_url":"https://github.com/melchor629.png","language":"C++","readme":"# raycastergl\n\n\u003e A Wolf3D-like raycaster using OpenGL shaders\n\nThis is a hobby project to make a raycaster using the GPU power with Compute and Fragment Shaders. It is based on the [Lode's Computer Graphics Tutorial](https://lodev.org/cgtutor/raycasting.html) (the raycast series), which uses a full CPU solution.\n\n## How it works\n\nThe engine uses three steps for the rendering:\n\n1. **raycaster**: performs the raycasting calculations for each screen column (DDL, texture size, perspective...)\n2. **spritecaster**: performs the raycasting calculations for each sprite (sprite size, draw position, transformations...)\n3. **drawer**: draws the results from the previous steps into the screen (walls, ceiling, floor and sprites)\n\nThe first two are [Compute Shaders][compute-shaders], and the last one is a [Fragment Shader][fragment-shader].\n\nThe player position and direction are handled in the CPU side, and the values are sent to the shaders each frame.\n\nThe map uses the same format as in the tutorials, but it is stored internally as a 2D texture with only red component in 8-bit unsigned int format (the map is accessed as `map[x][y]` which is not the common way to do it).\n\nAll textures are stored in a 2D Texture Array, where each layer is a different texture.\n\n### raycaster shader\n\nThe shader runs in parallel calculations for each column of the screen (width). The input is the map texture as a `uimage2D` and the output is an array of structs with some data that will be used in the fragment shader. The struct has this look:\n\n```c++\nstruct xdata {\n    ivec2 draw;\n    int side;\n    uint textureNum;\n    int texX;\n    float step;\n    float texPos;\n    float distWall;\n    vec2 floorWall;\n};\n```\n\nVariables receive the same name as in tutorial (with some modifications if they are vectors).\n\nThe output uses a [Shared Storage Buffer Object][ssbo] that allows to allocate some space in the GPUs memory to read and write arbitrary data, and can be shared with shaders. The input, instead, is bound to the shader as a image (instead of texture) so the shader can read precisely the contents of the texture using `xy` coords (not `uv` coords, which is the common way to access textures).\n\nThe shader also receives as input the player `position`, the `direction` it looks at, the `plane` for the direction and the `screenSize` (the visible section, not the whole window).\n\nWith this input, the shader runs for each column of the screen (width) in parallel. Each instance calculates the values for that column and puts the result in the array of `struct xdata`. Uses the vertical version of the algorithm.\n\nCurrently, a screen cannot have more than 10000px, it is hardcoded like this :(\n\n\u003e Note: _the screen is always 4:3 aspect ratio, so your screen can be more than 10000px, but what cannot be true is `height * 4 / 3 \u003e 10000`._\n\n### spritecaster shader\n\nWhile the previous shader is running, this other shader prepares its run (and may even run in parallel with the raycaster). The shader runs in parallel some calculations for each sprite in the map. The input is an array of `Sprite`s sorted by position (first the further sprites), and the output is an array of the result calculations. The two structs look like this:\n\n```c++\n// input\nstruct sprite {\n    float x;\n    float y;\n    uint texture;\n    int uDiv;\n    int vDiv;\n    float vMove;\n};\n\n// output\nstruct spritedata {\n    int spriteWidth;\n    int spriteHeight;\n    float transformY;\n    int spriteScreenX;\n    ivec2 drawX;\n    ivec2 drawY;\n    int vMoveScreen;\n    uint texture;\n};\n```\n\nThe shader also receives as input the player `position`, the `direction` it looks at, the `plane` for the direction and the `screenSize` (the visible section, not the whole window). Basically, the same `uniform`s as in the previous shader.\n\nThe input and output data are also [Shared Storage Buffer Object][ssbo]s. The first one is filled from the CPU side using a `std::vector\u003cSprite\u003e` sorted by distance (first further ones) - update process is done once per second.\n\nEach instance of the shader, calculates the position and size of a sprite and puts the result in the output buffer, so the Fragment Shader can read the results.\n\nA map cannot have more than 100 sprites, it is limited to that (it is also hardocded sorry).\n\n### raycaster drawer shader\n\nThis shader draws into a plane the results. The plane is located in front of the \"camera\" so it will always occupy the whole viewport. This shader receives the two shared buffers from the previous shaders and the 2D texture array that contains all textures, and draws into the plane.\n\nTakes care of drawing the walls with its texture or the ceiling and floor, and then the sprites over. The sprites step tries to draw each sprite for each pixel. There is a `if` to prevent trying to draw the sprite directly, but the loop is there (this could be optimized). The sprite drawing in the tutorial used integer operations to avoid float calculations, in the shader floats are being used because it is faster.\n\n### Map loader\n\nThe game without a map is useless. Maps are stored as yaml files and contain the map itself (which will converted into a texture) and its size, the initial player position and direction, and the sprites. There is an example of map in the maps folder.\n\n\u003e Note: _map loader does not have validations, so if the schema of the yaml is wrong, the engine could crash_\n\nThe first section of the file is the `map` which describes how it looks like. The `contents` contains how the map is. Each row is a `x` coord and each column is a `y` coord (remember the map is flipped from normal texture usage). Each value of the map represents a texture that will be drawed into the screen (the value must substract 1 to get the texture ID, so `1` will point to texture ID `0`). `0` means no wall, so the player can move through this section. There are some two optional values `floor` and `ceil` that modifies what is rendered in the floor and ceiling. The accepted values are a texture ID (`3` just the number) or a color (`[ 0.2, 0.2, 0.2 ]` rgb color as floats).\n\nThe second section is the `initial`s values for the player position, direction and plane. When the engine is loaded, will set these values to the ones in the yaml.\n\nThe third, and last, section is the `sprites` list. Each value of the list points to a sprite that will be placed in the position and the texture to draw.\n\n### Texture loader\n\nAs mentioned several times, the textures are stored in a 2D Texture Array. This is the list of textures (and its ID) that are loaded into the engine:\n\n| ID | texture | Purpose |\n|----|---------|---------|\n| 0  | `eagle.png` | Wall |\n| 1  | `redbrick.png` | Wall |\n| 2  | `purplestone.png` | Wall |\n| 3  | `greystone.png` | Wall |\n| 4  | `bluestone.png` | Wall |\n| 5  | `mossy.png` | Wall |\n| 6  | `wood.png` | Wall |\n| 7  | `colorstone.png` | Wall |\n| 8  | `barrel.png` | Sprite |\n| 9  | `pillar.png` | Sprite |\n| 10 | `greenlight.png` | Sprite |\n\nThe repository does not have the textures, they must be downloaded from the [tutorial][the-tutorial], which has a download link almost at the end.\n\n## How to build\n\nThe project uses git submodules and [CMake][cmake]. To clone the repository use:\n\n```sh\ngit clone https://github.com/melchor629/raycastergl\n```\n\nThe required external dependency is [GLFW][glfw]. On Linux, you can run:\n\n```sh\n#Arch/Manjaro\nsudo pacman -S glfw-x11\n\n#Debian/Ubuntu\nsudo apt install -y libglfw3-dev libglfw3\n\n#Windows (using vcpkg - for a manual installation of GLFW3 see below section)\nvcpkg install glfw3\n```\n\nThe project uses C++17, so ensure to have a very recent compiler (like GNU GCC 9, Clang 9, or MSVC 15 \\[2019]).\n\nOnce all requirements are met and installed, you can now use `cmake` or `cmake-gui` to configure the project. An example for Linux:\n\n```sh\nmkdir build\ncmake -S . -B build\ncd build\nmake -j2\n./raycastergl\n```\n\nAnd for Windows (using vcpkg):\n\n```powershell\nmkdir build\n# If using VS 2017\ncmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=C:\\path\\to\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake -G \"Visual Studio 15 2017 Win64\"\n# If using VS 2019\ncmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=C:\\path\\to\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake -G \"Visual Studio 16 2019\" -A Win64\n# you can also open the solution in Visual Studio or open the folder in Visual Studio Code with cmake extension\ncd build\ncmake --build .\n.\\raycastergl\n```\n\n\u003e Please use 64-bit version for Windows instead of 32-bit. All code is tested under 64-bit and some nasty bug can appear when using 32-bit...\n\n\u003e To debug in your local, change the **Working Directory** to `$(OutputPath)`.\n\n### Manual installation of GLFW3 for Windows\n\n1. Go to [GLFW3][glfw] page and download the latest source code (or the latest of version 3)\n2. Extract the zip and open a terminal there\n3. Create the folder `build`\n4. Run `cmake -S . -B build -G \"Visual Studio 15 2017 Win64\" -DCMAKE_PREFIX_INSTALL=\"${PWD}\\output\"` or `cmake -S . -B build -G \"Visual Studio 16 2019\" -A Win64 -DCMAKE_PREFIX_INSTALL=\"${PWD}\\output\"`\n5. `cd build`\n6. `cmake --build .` to build the project\n7. `cmake --install .` to install the project (it will be installed in a folder called `output`)\n8. When running the `cmake` to configure the project, use `-DGLFW3_DIR=C:/Path/To/Glfw3/output/lib/cmake/glfw3` instead of `-DCMAKE_TOOLCHAIN_FILE=...`\n\n## Running the engine\n\nBy default, the engine will run the `default.yaml` map at resolution 1333x1000, with V-Sync enabled. All of this can be changed with arguments. Run `./raycastergl --help` to change these options.\n\nThe controls are:\n\n- `W`, `S`, `UP Arrow` and `DOWN Arrow` to move forward and backwards\n- `A`, `D`, `LEFT Arrow` and `RIGHT Arrow` to rotate the camera\n- `ESC` to close the game\n- `F` to enter or exit fullscreen mode\n- The mouse also works to move and rotate the camera\n\n  [the-tutorial]: https://lodev.org/cgtutor/raycasting.html\n  [compute-shaders]: https://www.khronos.org/opengl/wiki/Compute_Shader\n  [fragment-shader]: https://www.khronos.org/opengl/wiki/Fragment_Shader\n  [ssbo]: https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object\n  [cmake]: https://cmake.org/\n  [glfw]: https://www.glfw.org/\n","funding_links":["https://ko-fi.com/melchor629","paypal.me/melchor9000","www.buymeacoffee.com/melchor9000"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelchor629%2Fraycastergl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmelchor629%2Fraycastergl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelchor629%2Fraycastergl/lists"}