Follow me on Twitter Follow me on GitHub
Chandler Prall Thoughts & Experiments for the Web

Blending WebGL Textures

View live demo.Blended Textures

I have dug myself into developing another WebGL game and so far it’s been going very well. It’s a city builder in the vein of SimCity and so far I have a number of the foundations set pretty well. One of the biggest challenges so far has been the terrain editing. While I’ll have a post tomorrow dedicated to displacements and editing heightmaps in WebGL, tonight I’ll stick to a simple topic: blending textures on the terrain. The goal is to have textures which will automatically blend themselves together depending on the geography’s elevation. This means having a base texture and defining secondary textures at different height levels. For WebGL I’m using the three.js framework so any Javascript code here will be based on that library.

First we need to create a geometry plane to act as our terrain and give it a shader material:

var ground = new THREE.Mesh(
	new THREE.PlaneGeometry(20, 20),
	new THREE.MeshShaderMaterial({
		uniforms: {
			texture_grass: { type: "t", value: 0, texture: THREE.ImageUtils.loadTexture('grass.jpg') },
			texture_rock: { type: "t", value: 1, texture: THREE.ImageUtils.loadTexture('rock.jpg') },
		},
		vertexShader: document.getElementById( 'groundVertexShader' ).textContent,
		fragmentShader: document.getElementById( 'groundFragmentShader' ).textContent
	})
);

This assumes you have two image files, grass.jpg and rock.jpg, in the same folder as your HTML file. It also assumes you have two <script> blocks defined with IDs of `groundVertexShader` and `groundFragmentShader`. These script blocks are used to hold the GLSL shader code that sets the colors used at each point on the geometric plane. Here is the shader code I’m using:

<script id="groundVertexShader" type="x-shader/x-fragment">
    varying vec2 vUv;
    varying vec3 vPosition;
    void main( void ) {
        vUv = uv;
        vPosition = position;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1);
    }
</script>
<script id="groundFragmentShader" type="x-shader/x-vertex">
    uniform sampler2D texture_grass;
    uniform sampler2D texture_rock;
    varying vec2 vUv;
    varying vec3 vPosition;
    void main() {
        // Texture loading
        vec4 diffuseGrass = texture2D( texture_grass, vUv );
        vec4 diffuseSand = vec4(.8, .8, .7, 1.0);
        vec4 diffuseRock = texture2D( texture_rock, vUv );
        vec4 diffuseSnow = vec4(.8, .9, 1.0, 1.0);
        vec4 color = diffuseGrass; // grass base
        // add sand
        color = mix(
            diffuseSand,
            color,
            min(abs(.0 - vPosition.z) / .03, 1.0) // Start at .0 for .03 units
        );
        // add rock
        color = mix(
            diffuseRock,
            color,
            min(abs(.6 - vPosition.z) / .6, 1.0) // Start at .6 for .6 units
        );
        // add snow
        color = mix(
            diffuseSnow,
            color,
            min(abs(1.3 - vPosition.z) / .5, 1.0) // Start at 1.3 for .5 units
        );
        gl_FragColor = color;
    }
</script>

In the top script block is the vertex shader. It’s job is to move any of the geometry’s vertices around. In this case we just set some variables for the fragment shader to use and move on – no changes needed. The bottom script block contains the fragment shader. It begins with import declarations (the `uniform`s and `varying`s) – these declare the two textures of the same names provided by the Javascript and also the two variables set by the vertex shader.

The first four lines of the main() function define the colors we could be using for these fragment. Both the grass and rock textures are converted to their colors based on the vertice’s UV values while the sand and snow colors are hardcoded, not changing based on what point of the terrain they are at.

The rest of the function determines how much of each texture is used for that fragment’s color. The grass texture is assigned to the `color` variable as the base. Next, sand is added based on how far away the vertex is from where sand is supposed to be: sand’s location starts at 0.0 elevation and extends .03 units both up and down, fading out as it moves away from 0.0. The shader compares against the vertex’s Z position because planes are created with a width (x) and height (y), no depth (z). This means changes that we perceive as terrain height actually happen on the Z axis.

Rock and snow textures and calculated in the same way. Finally, the `color` variable is assigned to `gl_FragColor` which is then exported as the fragment’s color. This yields blended textures which transition smoothly based on the height of the terrain.

To tie it together, throw in some elevation data and you’ve got yourself an auto-texturing piece of land.

9 Responses to Blending WebGL Textures

  1. Pingback: WebGL around the net, 11 August 2011 | Learning WebGL

  2. Silvana says:

    Hi:
    I’m trying to work out this tutorial. I have the files three.js, map.js, heighmap.html and two textures grass.jpg and rock.jpg of 600×400 px the first image and 300×225 the second but it doesn’t work for me. In firefox I get the error: Security error h.TEXTURE_MAG_FILTER,W……. on Three.js line 293. Can you help me please?

  3. Caioketo says:

    Hi there, i was trying to do something like that.
    but i want to load data (height and textures) from a file or an array.
    How can I do that?
    Like, i know how to put the height with three.js, in each vertices, but i dont know how to define a textures for each vertices.

  4. Pingback: PeaksAndValleys Added to WebGL-Samples Repository «BlackBerry Developer Blog

  5. Pingback: PeaksAndValleys Added to WebGL-Samples Repository | BlackBerry and Phone Shop

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>