Computer graphics programmers hello world
As a old C++ programmer it is very compelling to write your code and test it nativly and using Emscripten you can publish and use your code directly on the web.
But there is a learning curve to everything so to make a relevant and fun “Hello World” Application. That actually uses some Cpu power i decided to do a Wolfenstein Ray Casting demo. This is a cool an relativly easy technique and there are interesting design choice where to devide the computation between Java script and Web Assembly (c++).
Prerequisites
I asume a bit knowledge about C++, web development and javascript.
Scene setup:
The scene is a 2d array of integer, that determine if there is a wall or empty space. This is done by tracing a 2d ray inside the scene using a DDA algorithm (explained below) that step through each grid cell and find the distance to an intersected wall.
Ray Casting Algorithm
The basic idea is to render a 2.5 D image like in wolfenstein, where you render the image by finding intersection in 2D between a ray and the scene. Instead of true ray tracing where each pixel is calculated, by only tracing each collumn is much faster. But it also limits the rendering to walls of the same height.
My idea was to make the rendering simple and output the correct 3d Depth that later can be used for texturing and lighting.
But the first issue is finding the depth of the first intersection with the walls. In a lot of other tutorials i have seen there make this problem kind of difficult using trigonometric functions. But here i precent a simple ray casting (stepping) method.
In C++ pseudo code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
float rayCastMap(vec2 P, vec2 Dir) { ivec2 iNext; vec2 deltaInv; deltaInv.x = 1.0f / Dir.x; deltaInv.y = 1.0f / Dir.y; ivec2 iStep; iStep.x = (Dir.x < 0.0f) ? -1 : 1; iStep.y = (Dir.y < 0.0f) ? -1 : 1; iNext.x = (dir.x < 0.0f) ? ceil(p.x) + iStep.x : floor(p.x) + iStep.x; iNext.y = (dir.y < 0.0f) ? ceil(p.y) + iStep.y : floor(p.y) + iStep.y; float dist = 0.0f; do { float px = p.x + dir.x * dist; float py = p.y + dir.y * dist; int mapValue = getMapValue(px, py); if (mapValue == MAP_WALL) { return dist; } float dx = (static_cast<float>(iNext.x) - p.x) * deltaInv.x; float dy = (static_cast<float>(iNext.y) - p.y) * deltaInv.y; dist = min(dx, dy); if (dx == dist) iNext.x += iStep.x; if (dy == dist) iNext.y += iStep.y; } while (!outsideMap(iNext)); return false } |
Pretty simple right? Ok with the depth for each pixel collumn ready then we need to calculate the pixelheight of the wall and the 3D depth. This is based on the camera field of view and the image dimension.
Web Assembly
With the algorithm in place and tested, it is time to move the C++ to the web using Emscripten. Here i have designed the interface between Javascript and C++. Such that JS takes care of player input and C++ (Webasm) takes care of ray casting the final image thats is presented on the screen. Don’t consider this deep knowledge and there is a fair amount of handwaving because this is my first Webasm projekct. But is seems that the more work you offload to webAsm the better.
First you have to download and install Emscripten. Here you have to follow this guide https://emscripten.org/docs/getting_started/downloads.html
We have experimented i bit with the interface between Emscripten and Javascript, but here we us “Embind” even though it creates a big Javascript file that glues stuff together, as shown below. “RayLib.h” is the core ray casting class that exposes 3 methods that Embind can create javascript bindings for.
1 2 3 4 5 6 7 8 9 10 |
#include "emscripten/val.h" #include "emscripten/bind.h" #include "emscripten.h" #include "RayLib.h" EMSCRIPTEN_BINDINGS(ray_module) { function("rayCastImage", &rayCastImage); function("getMapType", &getMapType); function("getImage", &getImage); } |
CMake and Emscripten
It is possible to use CMake for both C++ and WebAsm development. Over time CMake really grows and it is so nice to have a way to quickly generate the right solution files for Visual Studio regardless of versions. Emscripten uses “make” to compile so on windows you need to install that. I use “MinGW” and for reasons i use Git bash to run make and other scripts.
Cmake for the Ray Casting project is shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
project(RayKarsten) cmake_minimum_required(VERSION 3.0) set (EMSRC src/RayLib.cpp src/RayLib.h src/EmWrapper.cpp) set (APPSRC src/RayLib.cpp src/RayLib.h src/main.cpp) if (CMAKE_SYSTEM_NAME MATCHES "Emscripten") add_executable(${PROJECT_NAME} ${EMSRC}) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 11 ) set(CMAKE_EXE_LINKER_FLAGS "--bind -O3 -s MODULARIZE=1" ) set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src) else() add_executable(${PROJECT_NAME} ${APPSRC}) SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES CXX_STANDARD 11 ) SET(CMAKE_CXX_STANDARD_REQUIRED ON) endif() |
To use CMake gui to generate the make file for Emscripten, you need to use the following setup after selecting your source and build folder.
Please download and play with the source code and let me know it there is something missing. Notice that we use NPM and Webpack to handle Javascript dependencies and bundeling the generated code. This is not my strongest competence so i hope it make sense. But i have made to small bash script that are included in the project.
Source code
https://bitbucket.alexandra.dk/projects/CG/repos/raykarstenwebasm/browse
References
https://emscripten.org/index.html
http://www.mingw.org/wiki/MSYS (for make)
https://www.permadi.com/tutorial/raycast/rayc1.html (Great ray casting tutorial)