We recently published a pathtracer that runs in JavaScript and WebGL (link). The WebGL pathtracer is inspired by a pathtracer that we previously implemented in C++ with OpenGL shaders written in the OpenGL Shading Language (GLSL) 3.3. However, since WebGL 1.0 uses a simpler version of GLSL, namely the OpenGL ES shading language 1.0, we encountered some language constructs that were not supported. This post describes some of the challenges we encountered in order to implement our pathtracer in WebGL. Finally, we benchmark how our WebGL pathtracer performs compared to the OpenGL version.

We use a standard pathtracing algorithm with a binary bounding volume hierarchy (BVH) acceleration structure. We store one triangle in each leaf node of the BVH tree. Probably the most common and efficient way to do ray-triangle intersection tests with the BVH tree is to maintain a stack of pointers to nodes that need to be tested. The basic tree traversal algorithm is outlined in the code listing below. A nice property of this algorithm is that each node is never visited more than once.

In GLSL it is not possible to implement a fully dynamics stack. However, in GLSL 3.3 it is straightforward to implement a fixed sized stack of pointers as an integer array with a stack counter, e.g.

Unfortunately, the OpenGL ES Shading Language 1.0 does not allow us to access an array element with a variable index, so a stack based approach is not feasible in WebGL. Thus, we implemented the stackless BVH traversal proposed in Ref. [1], which was reported to be about 30 % slower than the stack based traversal.

An additional problem with WebGL is that Windows browsers by default translates OpenGL calls to DirectX through a layer called ANGLE. Our experience with this translation is that loops in shaders get unrolled, and, hence, if we have shaders with very long loops, the shader compiler may run out of resources and fails to compile. With the typical scenes and BVH trees that we have tested, the ANGLE shader compiler can only compile a shader that traverses the tree a single time, i.e., we can only implement a pathtracer with a single bounce. Our solution to this problem is to run each bounce in a separate pass and save the state between the passes. This method will potentially involve some overhead because intermediate results must be read from and written to a texture. Additionally we must issue an additional draw instruction for each trace pass.

Linux and Mac browsers use the native OpenGL shader compiler. The Nvidia compiler that we have tested compiles the full pathtracer in a single shader without any problems.

Test setup
Nvidia GeForce 470 GTX
Intel E5620 2.4 GHz Quad Core
Linux Nvidia drivers version 304.43
Windows Nvidia drivers version 306.97

Our test scene consists of 8728 triangles. The figure shows the final pathtraced image.

Our benchmark results are shown in the table and figure below. If we compare the stackless and stack based versions in GLSL 3.3, we see that the stackless version is almost 50% slower, which is somewhat disappointing compared to the results reported in [1]. WebGL/GLSL ES 1.0 seems to be slightly slower than GLSL 3.3 when we do the full pathtracing in a single shader. The WebGL multipass version does not seem to be affected much by storing intermediate results between the passes. In fact the fastest multipass results are comparable with the results obtained with GLSL 3.3.

Table 1 Samples per second for a 512×512 pixel image of the test scene. The maximum trace depth is 4.
C++/GLSL 3.3 linux WebGL Chrome linux WebGL Firefox linux WebGL Chrome Windows (native) WebGL Firefox Windows (native) WebGL Chrome Windows (Angle) WebGL Firefox Windows (Angle)
Singlepass with stack 34.4
Singlepass stackless 18.2 15.0 13.7 15.8 14.5
Multipass stackless 15.1 15.1 17.4 15.1 19.2 18.2
[1] Efficient Stack-less BVH Traversal for Ray Tracing, M. Hapala et al., SCCG (2011).

WebGL pathtracing - Xmas competition
Oriented Particles in 2D