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
Multiple items type AmbientLight = inherit Light new : ?hex:U2<float,string> * ?intensity:float -> AmbientLight member clone : ?recursive:bool -> AmbientLight member copy : source:AmbientLight -> AmbientLight member castShadow : bool member castShadow : bool with set
Full name: Fable.Import.Three.AmbientLight
-------------------- new : ?hex:U2<float,string> * ?intensity:float -> AmbientLight
member Object3D.add : object:Object3D -> unit
Multiple items type SpotLight = inherit Light new : ?hex:U2<float,string> * ?intensity:float * ?distance:float * ?angle:float * ?exponent:float * ?decay:float -> SpotLight member clone : ?recursive:bool -> SpotLight member copy : source:PointLight -> SpotLight member angle : float member decay : float member distance : float member exponent : float member intensity : float member power : float ...
member Vector3.set : x:float * y:float * z:float -> Vector3
Multiple items type PerspectiveCamera = inherit Camera new : ?fov:float * ?aspect:float * ?near:float * ?far:float -> PerspectiveCamera member clone : unit -> PerspectiveCamera member copy : source:PerspectiveCamera -> PerspectiveCamera member aspect : float member far : float member focalLength : float member fov : float member near : float member zoom : float ...
Multiple items type WebGLRenderer = interface Renderer new : ?parameters:WebGLRendererParameters -> WebGLRenderer member clear : ?color:bool * ?depth:bool * ?stencil:bool -> unit member clearColor : unit -> unit member clearDepth : unit -> unit member clearStencil : unit -> unit member clearTarget : renderTarget:WebGLRenderTarget * color:bool * depth:bool * stencil:bool -> unit member dispose : unit -> unit member enableScissorTest : boolean:obj -> obj member forceContextLoss : unit -> unit ...
Full name: Fable.Import.Three.WebGLRenderer
-------------------- new : ?parameters:WebGLRendererParameters -> WebGLRenderer
member WebGLRenderer.setClearColor : color:Color * ?alpha:float -> unit member WebGLRenderer.setClearColor : color:string * ?alpha:float -> unit member WebGLRenderer.setClearColor : color:float * ?alpha:float -> unit
type Renderer = interface abstract member domElement : HTMLCanvasElement abstract member render : scene:Scene * camera:Camera -> unit abstract member setSize : width:float * height:float * ?updateStyle:bool -> unit abstract member domElement : HTMLCanvasElement with set end
Full name: Fable.Import.Three.Renderer
val document : Browser.Document
Full name: Fable.Import.Browser.document
Multiple items type BoxGeometry = inherit Geometry new : width:float * height:float * depth:float * ?widthSegments:float * ?heightSegments:float * ?depthSegments:float -> BoxGeometry member clone : unit -> BoxGeometry member parameters : obj member parameters : obj with set
Multiple items type BufferGeometry = new : unit -> BufferGeometry member addAttribute : name:string * attribute:U2<BufferAttribute,InterleavedBufferAttribute> -> BufferGeometry member addAttribute : name:obj * array:obj * itemSize:obj -> obj member addDrawCall : start:obj * count:obj * ?indexOffset:obj -> unit member addEventListener : type:string * listener:Func<Event,unit> -> unit member addGroup : start:float * count:float * ?materialIndex:float -> unit member addIndex : index:obj -> unit member applyMatrix : matrix:Matrix4 -> BufferGeometry member center : unit -> Vector3 member clearDrawCalls : unit -> unit ...
Full name: Fable.Import.Three.BufferGeometry
-------------------- new : unit -> BufferGeometry
Multiple items type Mesh = inherit Object3D new : ?geometry:BufferGeometry * ?material:Material -> Mesh member clone : unit -> Mesh member copy : source:Mesh -> Mesh member getMorphTargetIndexByName : name:string -> float member drawMode : TrianglesDrawModes member geometry : U2<Geometry,BufferGeometry> member material : Material member raycast : raycaster:Raycaster * intersects:obj -> unit member setDrawMode : drawMode:TrianglesDrawModes -> unit ...
Full name: Fable.Import.Three.Mesh
-------------------- new : ?geometry:BufferGeometry * ?material:Material -> Mesh
Multiple items type MeshLambertMaterial = inherit Material new : ?parameters:MeshLambertMaterialParameters -> MeshLambertMaterial member clone : unit -> MeshLambertMaterial member copy : source:MeshLambertMaterial -> MeshLambertMaterial member alphaMap : Texture member aoMap : Texture member aoMapIntensity : float member blending : Blending member color : Color member combine : Combine ...
Full name: Fable.Import.Three.MeshLambertMaterial
-------------------- new : ?parameters:MeshLambertMaterialParameters -> MeshLambertMaterial
Multiple items type Scene = inherit Object3D new : unit -> Scene member copy : source:Scene * ?recursive:bool -> Scene member autoUpdate : bool member fog : IFog member overrideMaterial : Material member autoUpdate : bool with set member fog : IFog with set member overrideMaterial : Material with set
Full name: Fable.Import.Three.Scene
-------------------- new : unit -> Scene
property Scene.autoUpdate: bool
member BufferGeometry.rotateX : angle:float -> BufferGeometry
member BufferGeometry.rotateY : angle:float -> BufferGeometry
member BufferGeometry.rotateZ : angle:float -> BufferGeometry
member WebGLRenderer.render : scene:Scene * camera:Camera * ?renderTarget:RenderTarget * ?forceClear:bool -> unit