Monthly Update on a Haskell Game Engine

Jan 1, 2023

I've been working the past month or two in a shader-centric, type-heavy 3d-renderer/game engine, written in Haskell. In this post I present some of the current implementation details and pictures of the multiple achievements and progress done so far.

1 Ghengin

I’ve been working the past month or two in a game engine titled Ghengin (pronounced /ɡɛn-ʤɪn/, never /ɡɛn-ɡɪn/). This is not yet a release, and version 0.1.0 is far into the future. However, I’ve come a long way and I’d like to share a few pictures of my progress. This post was migrated from the discussion at the Haskell Discourse

The demo I’ve been working on is based on Sebastian Lague’s series Procedural Planets. It is a showcase of procedurally generated planets you can move around in and tweak the procedural generation parameters of the planets to create oceans and continents.

Fig 1. Screenshot of planets demo

1.1 Bullets on Technical Details

I hope to, soon enough, write a more substantial explanation of the engine’s technical challenges and overall design decisions so far, and on the game developer’s facing side of the engine. In the meantime, here are a few key points regarding the technical feats of the engine along with the main libraries it currently depends on, which help create a picture of how it is working:

FIR is a really cool shader library and unlike any you’ve likely tried before (it’s embeded in Haskell, but that’s just the start). The shader’s “interfaces” are defined at the type level, and in ghengin that type information is used to validate the game-developer-defined-materials. In short, if you define materials incompatible with your shaders, the program will fail at compile time

1.2 The Small Victories

To give a general sense of progress, I put together a small roadmap of victories attained while developing the engine, both in words and in screenshots.

Fig 2. Hello World! - Rendering a triangle
Fig 3. Rendering a rotating cube
Fig 4. A moving camera and some broken spheres
Fig 5. DearImGUI and simple diffuse lighting
Fig 6. The very first planets
Fig 7. The height influences the color
Fig 8. Texture sampling based on the color!

1.3 A peek into the code

Unfortunately, I don’t expect it to be useful without a proper explanation, but nonetheless I’ll present a small snippet of the Main module of the procedural planets game. Additionally, the full source is avaliable1 – that’s also where engine development is happening. The next feature I’ve just completed, at the time of writing, is a gradient editor for the in game GUI (Fig. 1).

As promised, here’s a quick look at the Main module of the procedural planets game:

initG :: Ghengin World ()
initG = do

  -- Planet settings used to generate the planet and which are edited through the UI
  ps <- makeSettings @PlanetSettings
  (planetMesh,minmax) <- newPlanet ps

  -- Load the planet gradient texture
  tex         <- texture "assets/planet_gradient.png" sampler

  -- Create the render pipeline based on the shader definition
  planetPipeline <- makeRenderPipeline Shader.shaderPipeline

  -- Create a material which will be validated against the render pipeline at compile time
  m1 <- material (Texture2DBinding tex . StaticBinding (vec3 1 0 0) . StaticBinding minmax) planetPipeline

  -- Create a render packet with the mesh, material, and pipeline.
  -- All entities with a RenderPacket component are rendered according to it.
  let p1 = renderPacket planetMesh m1 planetPipeline

  -- Define our scene graph
  sceneGraph do

    -- A planet entity, with the planet render packet and a transform
    e1 <- newEntity ( p1, Transform (vec3 0 0 0) (vec3 1 1 1) (vec3 0 (pi/2) 0) )

    -- A camera
    newEntity ( Camera (Perspective (radians 65) 0.1 100) ViewTransform
              , Transform (vec3 0 0 0) (vec3 1 1 1) (vec3 0 0 0))

    -- The planet UI component based on the `ps` settings
    newEntityUI "Planet"  $ makeComponents ps (e1,tex)

  pure ()

updateG :: () -> DeltaTime -> Ghengin World Bool
updateG () dt = do

  -- Every frame we update the first person camera with the user inputs and the planet's rotation
  cmapM $ \(_ :: Camera, tr :: Transform) -> updateFirstPersonCameraTransform dt tr
  cmap $ \(_ :: RenderPacket, tr :: Transform) -> (tr{rotation = withVec3 tr.rotation (\x y z -> vec3 x (y+0.5*dt) z) } :: Transform)

  pure False

main :: IO ()
main = do
  -- Run the game with this init, update and end function
  ghengin w initG undefined updateG endG

  1. If you are curious about the full source of the planets game, beware of dragons 🙂. It is not ready as a learning resource whatsoever.↩︎