Using a Canvas element as a Texture in three.js

So far I have not written any posts on textures with my three.js collection of posts, so lets put and end to that today. In three.js you have a Scene, and in that scene you place things like cameras, and other Objects like a Mesh that is composed of a Geometry, and a Material. It s with materials that textures come into play, and one way to go about creating a texture is with canvas.

When we look at Materials in depth they are composed of many properties, some of which are part of the base Material class, and others are part of the specific Material such as the Basic Material, or Lambert Material. Properties such as map, and emissiveMap that expect a Texture, which is an image that can be used to define how the surface is going to look.

The Image used to define a Texture can be loaded from an external source such as a *png image, but it can also be defined with javaScript, by making something with canvas, and then using that as an Image source. This is useful to help make projects that do not depend an an extremal source asset aside from three.js, or to make dynamic animated textures because it is well canvas.

To achieve just this there is the CanvasTexture constructor however the plain old Texture constructor can be used also by just setting one little property value for the texture after it is created. Both constructors can be used by just passing a reference to the canvas element that is to be used.

1 - Getting started with threejs and canvas for textures

In this section I will be going over the basics of using canvas to create a texture that will then be used with the basic material map property. It is impotent to note that the properties of materials will differ from one to another, the basic material does not do anything with light but other materials do. For example in the standard material I would want to use the emissive property of the material to define a texture that will always emit light even if there is not light source.

Anyway getting back to the main topic at hand here this example will be just a simple getting started example of using canvas to map just a basic color map to a basic material.

1.1 - Start With just a quick canvas element and drawing to the 2d context

I order to use a canvas as a texture we will of course need an instance of a canvas that can be created with document.createElement. The dom element does not have to be appended to the HTML, we just need to have one to give to the Texture constructor.

The width and height values should be a base 2 number such as 8, 16, 32 and so forth else you might get webGl errors in the console. Aside from that concern so far it seems like you can just create and draw to a simple plane old canvas element like normal using the 2d drawing context. The resulting image created with the drawing context and javaScript code can then be used as your texture from things like the map property of a material.

So say you just want to start out with something very simple, just use the canvas 2d drawing context to create a texture that is just a square. In which case you might get together something like this:

1
2
3
4
5
6
7
8
9
10
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = 8;
canvas.height = 8;
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#ff00ff';
ctx.strokeRect(0, 0, canvas.width, canvas.height);

So I created a canvas, set the size of it to something that is a base 2 number, and drawn something to it. Great now I have the easy part out of the way, I am now ready to use it to passed it as an argument to a threejs constructor that will return a texture that I can the use in a material that I can then use with a mesh.

1.2 - Creating a texture with canvas using THREE.CanvasTexture or just THREE.Texture

Although The base Texture class can be used to create a texture that uses a canvas, there is a constructor for this purpose called THREE.CanvasTexture. The only difference is that it would appear that the needsUpdate boolean of the texture is set to true by default. In any case you just pass the reference to the canvas (not it’s drawing context) to the constructor that use use as the first argument.

So then this:

1
var texture = new THREE.CanvasTexture(canvas);

Seems to have the same effect as doing this:.

1
2
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;

In Any case you now have both a canvas, a drawing context for that canvas, and a texture made from that canvas that can now be used in a Material that can make use of that texture. Regardless of what constructor you use the needUpdate boolean is of interest as even if it is set to true by default, you will want to set it true again each time you want the texture updated, more on that later.

1.3 - Using the texture with a Material

I will not get into this in depth, as this matter can quickly become a whole new post when it comes to using a texture with a Material. However a quick example involving the basic material is in order for sure to say the least when it comes to using a texture with a material. For this example I am just setting the texture that is created with a canvas element to the map property of a basic material.

1
2
3
var material = new THREE.MeshBasicMaterial({
map: texture
});

The basic material is an example of a material that does not respond to a light source, so for the most part it is just the color of the surface that we are concerned with when working with this kind of material. So when I set the map property of a Basic Material to the texture, the canvas texture will work in place of the color property.

The material that you are using makes a big difference, some materials use the map property for the texture that is to respond to a light source. As such the property that you might want to set the texture that you have made to is the emissiveMap property rather than map. This is the case with the Lambert material

1
2
3
4
var material = new THREE.MeshLambertMaterial({
emissive: new THREE.Color(0xffffff),
emissiveMap: texture
});

There are other properties that make use of a texture, I will not get into detail with them all here as it is off topic, but it is something that you should be aware of if not before hand.

So now that we have the basics when it comes to making a material with a texture that is created using a canvas element we can now use the material with a mesh. So lets start looking at some full examples of this in action.

2 - Full Demo

Once you have the canvas, texture, and material we can go on with everything else as normal. In this demo I will set up a scene, add a camera and set it away from a the origin where I will be placing a simple Mesh with the simple box geometry that is often used for these kinds of examples.

In this example I will just be rendering the box once and be done with it just so show that you can use a canvas to make a static texture, more on animation later.

2.1 - A create canvas texture helper method

I started out with a helper method that just returns a texture that is ready to use with a material. In this helper I create a canvas element, get the drawing context, and draw to it. In then used to THREE.Texture constructor to create the texture by just passing the canvas element as the first argument when calling it. I then set the needs update flag to true, and return the texture.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// create and return a canvas texture
var createCanvasTexture = function () {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = 16;
canvas.height = 16;
ctx.fillStyle = '#000000';
ctx.lineWidth = 1;
ctx.fillRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
ctx.strokeStyle = '#ff0000';
ctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
};

2.1 - create a cube helper

I then have another helper that makes use of the create canvas texture helper by calling it and using the resulting texture that is returned for the map property of the basic material that is used for a mesh. I then just use the box geometry constructor for the geometry of the mesh, and return the mesh.

1
2
3
4
5
6
7
8
// create a cube the makes use of a canvas texture
var createCube = function () {
return new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({
map: createCanvasTexture()
}));
};

So when putting together a scene I just need to call this method, and then I have a mesh that is all set to be added to a scene.

2.2 - The rest of the full threejs example that involves a canvas texture

So now to make use of my create cube, and thus create canvas texture helpers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Scene
var scene = new THREE.Scene();
// Camera
var camera = new THREE.PerspectiveCamera(75, 320 / 240, .025, 20);
camera.position.set(1, 1, 1);
camera.lookAt(0, 0, 0);
// add cube to scene that makes use
// of the canvas texture
scene.add(createCube());
// RENDER
var renderer = new THREE.WebGLRenderer();
renderer.setSize(320, 240);
document.getElementById('demo').appendChild(renderer.domElement);
renderer.render(scene, camera);

Notice that I set the needs update property of the texture to true. As I mentioned earlier this does not need to be set true if I where to use the CanvasTexture constructor, if I am just doing something like this in which I am not redrawing the canvas this only needs to be set true once.

3 - Create canvas helper with a custom draw method

In this section I will be writing about an example that makes use of a slightly more advanced revision of the create canvas helper that I made for the first example for this post. This method accepts a custom draw method that can be used to draw something else for the texture that is created.

3.1 - A Canvas.js module

Things are starting to get a little cluttered so for this example I will create an external jaavScript file called canavs.js and place all these custom helpers and methods there.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var can3 = {};
can3.draw = function (ctx, canvas) {
ctx.fillStyle = '#000000';
ctx.lineWidth = 1;
ctx.fillRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
ctx.strokeStyle = '#ff0000';
ctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
}
// create and return a canvas texture
can3.createCanvasTexture = function (draw) {
draw = draw || can3.draw;
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = 16;
canvas.height = 16;
draw(ctx, canvas);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
};
// create a cube the makes use of a canvas texture
can3.createCube = function (texture) {
return new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({
map: texture
}));
};

3.2 - The rest of the example

So now to use my canvas.js module in an example. Here I just made two cubes for the scene one that makes used of the default draw method, and anther where I am passing a custom draw method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Scene
var scene = new THREE.Scene();
// Camera
var camera = new THREE.PerspectiveCamera(75, 320 / 240, .025, 20);
camera.position.set(4, 4, 4);
camera.lookAt(0, 0, 0);
// create texture with default draw method
var texture = can3.createCanvasTexture();
var cube = can3.createCube(texture);
scene.add(cube);
// create texture with custom draw method
texture = can3.createCanvasTexture(function (ctx, canvas) {
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(canvas.width / 2 + 0.5, canvas.height / 2 + 0.5, canvas.width / 2 - 2, 0, Math.PI * 2);
ctx.fill();
});
cube = can3.createCube(texture);
cube.position.set(0, 0, 2)
scene.add(cube);
// RENDER
var renderer = new THREE.WebGLRenderer();
renderer.setSize(320, 240);
document.getElementById('demo').appendChild(renderer.domElement);
renderer.render(scene, camera);

4 - Animation

So because the source is a canvas you might be wondering if it is possible to redraw the canvas and update the texture, making an animated texture. The answer is yes, all you need to do is redraw the contents of the canvas, and set the needsUpdate property of the texture to true before calling the render method of your renderer.

Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Loop
var frame = 0,
maxFrame = 500,
loop = function () {
var per = frame / maxFrame,
bias = Math.abs(.5 - per) / .5,
x = canvas.width / 2 * bias;
y = canvas.height / 2 * bias;
w = canvas.width - canvas.width * bias;
h = canvas.height - canvas.height * bias;
requestAnimationFrame(loop);
ctx.lineWidth = 3;
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#ff00ff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeRect(x, y, w, h);
controls.update();
texture.needsUpdate = true;
renderer.render(scene, camera);
frame += 1;
frame = frame % maxFrame;
};
loop();

It should go without saying that this will use more overhead compared to a static texture, so I would not go wild with it just yet, but it is pretty cool that I can do this.