3 point gradient trick and vertex colors

Continuing with the three.js development, after implementing multi lights the flat shading was starting to be quite a limitation.

In order to get smooth faces I needed to figure out a way to create what it’s called 3 point gradients. I took a look at the usual Flash 3D engines and what was my surprise when I found out that they tend to be limited to just 1 light (correct me if I’m wrong). This is probably because by supporting 1 light they just needed to create a light map per material. Something like this:

That’s indeed a fast approach, but it limits to just 1 light, and then you need to update the map if the ambient light changes or the color of the material… not really up my street.

The old way of doing this with OpenGL is by using Vertex Colors. Basically, I was after this:

I wondered if there was a way to create this kind of gradient with the Canvas API. I googled a bit to see what people had come up with, but all the approaches seemed quite cpu intensive. Like this one from nicoptere.

Then a crazy idea pop into my mind. What about having a 2×2 image, and change the colors of each pixel depending of the vertex color and do that at render time per polygon? The browser will then stretch that image and create all the gradient in between those 3-4 pixels.

This is the 2×2 image:

Can’t you see it? Ok, this is the same image scaled to 256×256 with no filtering (using Gimp):

However, by default (whether you like it or not) the browsers filter scaled images. This is what you get within the browsers:

Each browser gets slightly different results but pretty much that’s what you get. This wasn’t exactly what I was after, there is too much color on the corners. However, by looking at all the results from all the browsers I realised that the center part of the image was the only part that was always similar.

Then I realised that that’s the actual gradient I was after!

Here it’s the code:

var QUALITY = 256;

var canvas_colors = document.createElement( 'canvas' );
canvas_colors.width = 2;
canvas_colors.height = 2;

var context_colors = canvas_colors.getContext( '2d' );
context_colors.fillStyle = 'rgba(0,0,0,1)';
context_colors.fillRect( 0, 0, 2, 2 );

var image_colors = context_colors.getImageData( 0, 0, 2, 2 );
var data = image_colors.data;

var canvas_render = document.createElement( 'canvas' );
canvas_render.width = QUALITY;
canvas_render.height = QUALITY;
document.body.appendChild( canvas_render );

var context_render = canvas_render.getContext( '2d' );
context_render.translate( - QUALITY / 2, - QUALITY / 2 );
context_render.scale( QUALITY, QUALITY );

data[ 0 ] = 255; // Top-left, red component
data[ 5 ] = 255; // Top-right, green component
data[ 10 ] = 255; // Bottom-left, blue component

context_colors.putImageData( image_colors, 0, 0 );
context_render.drawImage( canvas_colors, 0, 0 );

So it was just a matter of changing 3-4 pixels, scaling up and cropping and then use that as a texture for each polygon. Crazy? Yes. But it works! And fast enough! And what’s more, it’s not just limited to 3 points, but a 4th point comes for free (quads).

Here it’s an example of the first material I’ve applied the technique to (use arrow keys and ASWD to navigate).

At this point I was surprised that I didn’t see this before. And that I hadn’t seen this being done in any of the usual 3d engines. I did another search and I found this snippet from Pixelero that uses the same concept. Good to know I’m not the only one with crazy ideas! 🙂

Thanks to this, now I’m able to do smooth materials (gouraud, phong) that support multiple lights, fog, even SAAO 🙂 We just need the browsers to become faster (which they seem to be on it already).

Of course, if your browser supports WebGL you should be using that instead, but if it doesn’t, at least you’ll have something better than a text message.

No Responses to “3 point gradient trick and vertex colors”

Post a Comment