The F# to JS compiler Fable is looking ever more impressive. What better way to showcase its abilities than
by putting a spinning a cube on your screen?... The official Fable ThreeJs / WebGL demo
is actually far superior to my effort here. This is a much cut-down version of that demo, showing how little code is needed to get F#
drawing 3D graphics in a browser.
First things first
To use Fable, your machine will need .NET Core installed - so you can use the dotnet command line tool. Assuming you have that, the quickest way to get started
is to clone the official Fable samples-browser repository and then run the restore script
contained in its base directory, which will install the Fable extensions to the dotnet command line tool and will also then go on to run yarn install and
code in the samples-browser/webGLTerrain/src/App.fs file with the below. Note that you should comment out the first two lines of the below code
(they are necessary only as I'm writing a fsx script file for this blog, whereas probably you would write a standard fs file, if not blogging). Once you've copied, pasted
http://localhost:8080/webGLTerrain (which should actually now show you a plain spinning cube rather than fancy terrain). Talking of the code,
the first lines are below. Initially we just import the necessary namespaces and modules, then we define two functions to return the desired
size of the graphics canvas.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
// Comment out the below two lines if you are writing a .fs (compiled)// file rather than a .fsx (script) file#r"../../../packages/Fable.Core/lib/netstandard1.6/Fable.Core.dll"#load"../../../node_modules/fable-import-three/Fable.Import.Three.fs"openSystemopenFable.CoreopenFable.Core.JsInteropopenFable.ImportopenFable.Import.Threeletwidth () =Browser.window.innerWidth/2.0;
letheight () =Browser.window.innerHeight/2.0
Our scene needs lights (so that we can see stuff).The below function adds a dim ambient light and
a bright spotlight to the scene. Ambient light defines a base-level of illumination within the
scene. It does not have a particular direction or position (hence it is easy to create). By contrast,
a spotlight has an exact location and shines in a particular direction, illuminating objects within
it's beam differently depending on the angle they meet it at. Here we just set the spotlight's
position and leave it shining at the origin (the center of our scene). Note that before we
specify a colour as a hex string, we have to use U2.Case2 to create a union case. This is because
We also need a camera, which sets the location that the scene is viewed from. We need to set the
aspect ratio of the camer'a field of view. We use the width() and height() functions we defined above so that the
camera field's aspect will match the intended dimensions of our graphics output area. The last line of
the function is its return value; so the camera is returned back to the caller so that it can be used
OK, so not quite a case of lights-camera-action, as we now need a renderer. A renderer is sort of
analogous to a screen, and embodies the output area for our graphics. We have to tell Three which
DOM element within our page to put the render target into (and our HTML page must contain an element
called graphicsContainer. We also get to choose which kind of renderer to use. WebGLRenderer is
the fastest, but Three does support other renderers that could be used on devices without WebGL support.
The call to setClearColour sets the background colour for areas of the screen that are not otherwise
drawn on. Again we set the size of the output area using the width() and height() functions that we defined
above, so that the output dimensions tie up with the aspect ratio of the camera.
Nearly there, but not quite like the movies. Now we have to create something to star in our scene.
We have only one cast member, a simple cube. Fortunately Three provides methods for defining most
standard geometric shapes, so we don't have to build the cube up out of individual triangles. Each object
also needs its surface properties defining (so that we know what it should look like). Here we say
that our cube's surface is made from a Lambert type material (which would allow for some shininess,
but we don't set that up here and just go for a plain purple matt surface). We buffer the cube's
geometry, which moves it to a more compact internal representation (for better performance) and then
combine it's shape and material definition together into a mesh. Finally we add the mesh to the scene
and also return the cube geometry for later use.
Finally we're there. We can create a Scene and initialise all required elements by calling
the functions we defined above. We return a 4-tuple of the 4 key graphics elements back to
the caller so that those elements can be used later on in rendering / animation. In-fact,
"the caller" is just the line of script at the bottom of the section, which creates top-level
bindings to each of the key graphics elements.
So, as we're using the movies as an analogy, we actually ought to add some movement to the scene,
a spinning cube is going to be much more impressive than a static one. Each frame we rotate the
cube a little about each of its axes to make it appear to spin. The use of requestAnimationFrame
(rather than a loop) ensures that the animation is paused if the render's target element isn't