How to Create an Artificial Island Landscape with BabylonJS

Learn WebGL and Babylon.js at your own pace

Feel free to check out our online course 3D Programming with WebGL and Babylon.js for Beginners on Zenva Academy. The course covers the Babylon.js framework and explains all you need to get started with using this fantastic library in new or existing projects.

Preparing the web page

First of all we need a simple HTML 5 page with a full page canvas:

The page just needs to reference babylon.js (you can find the latest version here) and hand.js for the touch support (latest version here).

Then you can create a script block right after the canvas element with the following code:

This code creates the engine, the main scene and add a camera and a light (the sun) to it.

The camera is an ArcRotateCamera so you can use your mouse/touch/keyboard to rotate around a central pivot. We just want to limit the amplitude of the camera with beforeRenderFunction function (because we don’t want to go under the ground or beyond the sky Sourire). This function is attached to the scene with registerBeforeRender so it will be called before every frame to guarantee our constraints.

Adding a skybox

A skybox is a box with a special material used to simulate the sky:

image

The material uses a special reflection texture. To create a skybox with Babylon.js you just have to use this code (because skyboxes are already supported by the StandardMaterial):

A skybox is just a box with a StandardMaterial Clignement d'œil. The key point is the CubeTexture used for the reflection channel. A cube texture is composed of 6 textures (one for each face of a cube). Babylon.js will choose the right one depending on the position of the viewer to simulate a continuous sky:

image

When you want to create a CubeTexture, you have to specify a file scheme based on the following; nx, ny, nz stand for negative (x, y, z) and px, py, pz stand for positive (x, y, z).

Adding the ground and the island

The ground is a simple plane textured with a repetitive bitmap:

image

To create it, the code is really simple:

The diffuse texture is scaled by a ratio of 60 in order to repeat it along the ground.
The island also uses a plane but deforms it through an height map.

image

The height map is a simple map used to define the altitude of every vertex of the plane:

image

To use it, you have to create a plane with more subdivisions. The plane is then updated using the height map:

The definition of the CreateGroundFromHeightMap is the following:

As you can see the fifth parameter allows you to increase the complexity of your mesh in order to improve the visual quality of it:

image

The water

The water itself is just a simple plane created with the following code:

image

The true water lies within the material used to display it. And this time we will not use the good old StandardMaterial.

Indeed, to create a convincing water surface, we need to take in account complex  phenomena like refraction and reflection that the StandardMaterialis not intended to handle. To reproduce them, we will use a very powerful feature of Babylon.js: the render target textures. This kind of textures allows you to render a scene into a texture in order to use it later in your own shaders.

Creating an empty custom material

So we need to create our first custom material! To do so, we first need to add a new JavaScript file to our site. This file named waterMaterial.js will contains the following empty anonymous function:

Do not forget to reference it in your HTML page:

Starting from our empty anonymous function, let’s add a new object called WaterMaterial:

This is the minimal code to provide for a custom material:

  1. constructor to register the material to the scene and get important information (in my case I need the current light to simulate the sun)
  2. Our material MUST retrieve and use  the BABYLON.Material prototype
  3. needAlphaBlending: The material must indicate to Babylon.js if it requires alpha blending (in our case we are not based on alpha blending but on render target textures)
  4. needAlphatesting: The material must indicate to Babylon.js if it requires alpha testing (same thing here, we do not need alpha testing)
  5. isReady: Babylon.js will call this function to know if the material is ready to be used
  6. bind: Babylon.js will call this function to activate the shader before rendering objects that use it
  7. dispose: This function allows you to release resources you may have created for your material

The vertex and fragment shaders

The next thing we need to prepare are the shaders themselves. Shaders define the code executed by the GPU to process the vertices sent by the meshes and the pixels produced by these vertices.

For more information about how a GPU works internally and how to create a 3D engine I suggest you to read the excellent series on how to create a 3D soft engine from scratch written by David Rousset:

http://blogs.msdn.com/b/davrous/archive/2013/06/13/tutorial-series-learning-how-to-write-a-3d-soft-engine-from-scratch-in-c-typescript-or-javascript.aspx

The goal of this blog is not to talk about shaders and GLSL but we need them, so here is the vertex shader that we will use for our material:

And here is the fragment shader

These shaders use many external variables to achieve the rendering. External variables (defined with the uniform keyword) are used to communicate between your code and the GPU. The GPU will execute the shaders code using the values provided to the external variables by your code:

  • waveData: Defines height and amplitude of waves
  • windMatrix: Defines the direction of the water
  • world: World matrix (Matrix of the current mesh)
  • worldViewProjection: Combined transformation matrix (world x view x projection)
  • vEyePosition: Camera’s position
  • vLightPosition: Sun’s position
  • vLevels: Blending levels of reflection and refraction
  • waterColor: Water’s color
  • refractionSampler: Variable used to read the refraction texture
  • reflectionSampler: Variable used to read the reflection texture
  • bumpSampler: Variable used to read the bump texture (which is used to generate the waves)

To sum things up, we can decompose the shaders’ work through the following pipeline:

1. The mesh is transformed by the vertex shader to generate the triangle used to find the pixels that require to be painted:

image

2. Diffuse color is computed first (based on the sun position and on the water’s color)

image

3. Specular is then added (based on camera’s position):

image

4. The refraction texture is then used to simulate the transparency of the water:

image

5. The bump texture is then used to add perturbations:

image

6. The reflection texture is finally used to add the reflected objects (island and sky):

image

7. The cherry on the cake is added by using a Fresnel computation in order to prioritize reflection or refraction depending on the view angle of the camera.

As you can see we start for an almost full reflection to finish to full with a full refraction when we are perpendicular with the ground.

Linking shaders to the material

Now we have created our shaders, we need to link them with the material. We also need to prepare data for the external variables used by the shaders. To do so, let’s update the constructor:

The bump texture is a standard texture:

image

The reflection texture is created from a BABYLON.MirrorTexture which is able to simulate a mirror (exactly what we need!).

The refraction texture is based on a BABYLON.RenderTargetTexture. This kind of texture can receive the rendering of a scene and can then be used as standard texture resource for a shader. With the onBeforeRender and onAfterRender functions you can configure and restore back the scene for your rendering (In this case, I’ve just activated a clipping plane to limit the refraction to the objects over the water).

Then we need to add a new function to the WaterMaterial in order to register these two special textures:

This function is mandatory for us, because we need to have our textures prepared before using our shaders.

The link with the shaders itself is done with the isReady function:

The engine object has a function called createEffect that you can use to compile/link your shaders into a simple object. This function has the following parameters:

  • An array of attributes describing the topology of your vertices
  • An array of uniforms (the external variables) defined by the shaders
  • An array of samplers (the objects used to read textures)
  • An optional define string

The createEffect has an internal cache in order to compile/link your shaders only once (the subsequent calls return directly the cached effect).

Once the effect is created we can use it to transfer values to the shaders within the bind function:

Please note that I use the animated _time variable to generate the windMatrix variable in order to simulate waves movement.

Finally do not forget to clean things up when leaving Sourire

Using our material

Now we have a specific material to simulate the water, we can go back to our HTML page. We have to attach the WaterMaterial to the water object and specify which meshes are used by the reflection and the refraction textures:

One important thing to note here: The BABYLON.Engine.ShadersRepository is used to specify the path to shaders’ folder. For this demo I did not use one so we have to set it to empty string.

You are now ready to see your wonderful world with its so cute moving water Sourire

Published by

David Catuhe

David Catuhe is technical evangelist lead at Microsoft France. He defines himself as a geek. He loves to develop with JavaScript and HTML5 but also with DirectX, C#, C++ or even Kinect (He wrote a book about it which is available on Amazon).David has a blog called Eternal Coding where he writes about HTML5, Windows, Kinect and 3D Development.

Share this article