This post is about a new GPU game engine, billboards, forest and kovasb/gamma.
I decided to scratch the idea of using a web worker to run the engine. Instead I want to run the engine on the GPU, using WebGL shaders. This allows to scale to a huge number of units (target 64k). But at this scale I can’t render expensive geometries anymore, so I will render billboards. This page explains how to do it. I am using solution #2 from that page. In addition, I need to discard transparent fragments in the fragment shader, as described here. Otherwise transparent fragments write to the depth buffer and block other fragments without setting a color, and that looks very glitchy. I am also using instancing to make it fast. Today I implemented only rendering a 2D sprite on the billboards, but I will switch it up later by rendering one instance of a 3D model to a texture at current camera angle, and then displaying that on all the billboards. I think it will be a decent approximation.
At large scale, multiplayer will also be a lot harder, and would have to be some kind of lock-step or turn based solution, so I think I will drop it, or maybe have 2 modes, swarm mode for single player and few units mode for multiplayer.
Also, a lot of my current work, like explosions, magic stars and attack vectors will need a rethink and reimplementation with the new GPU engine.
Here is a screenshot of a forest using trees on the new engine unit billboards. It runs 64k trees at 60 FPS.
I decided to start using the ClojureScript library kovasb/gamma for writing GLSL shaders functionally in a ClojureScript DSL rather than directly. Note that hendekagon/gamma on Clojars is a more recent packaging, and provides necessary fixes to for example aget for vector and matrix indexing. Gamma buys me composability. It’s a bit hard to get started, since documentation is not perfect, but you can piece it together from the Wiki, tutorial, the test code, examples and the source code itself. Using imperative hacks like the “discard” statement is hard though, because gamma is purely functional and only supports assignments at the top level. So since I needed “discard” I resorted to putting a “magic” vector vec4(0.0, 1.0, 2.0, 3.0) and doing a string search/replace of that line with discard. I opened an issue for gamma.
Next up is collision detection. At first I was thinking I needed a bounding volume hierarchy, asking a question on StackOverflow, but after asking on the EFNet IRC channel #shader.no I am reconsidering a simpler grid approach. The problem is that WebGL does not have atomic compare-and-exchange, so storing multiple objects per cell, for example in a linked list, is hard. I will write about my workaround if and when I find a solution.