*blog... kind of... *rss 



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 2x2 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 2x2 image:


Can't you see it? Ok, this is the same image scaled to 256x256 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.
18 comments

Very clever. I was wondering how you accomplished the smooth shading, since I'd never seen it done before.
Very smart and creative, congrats ^^
AWESOME!!!!! How would you use that to do triangles?
Blimey. This is the new world being discovered and nobody's noticing.
Awesome stuff. You are truly showing the potential of JavaScript and modern web browsers, and sharing this with us makes us all a little smarter. Nuff respect. :-)
lol ... good one, Mr. Doob!
Very very nifty. clever trick :)
@Anthony: This function takes care of that: https://github.com/mrdoob/three.js/blob/master/src/renderers/CanvasRenderer.js#L840 :)
@ Danny
new world is it?

you guys wanna take us back to the days of "this website is best viewed with [xxxxx] browser."

people will not allow this to happen - so invest your time in this stuff at your peril.
@aaron: Ah... those were the days... When browsers had to keep up getting better otherwise people would switch. That's called competition, and that's always good.

But then Flash appeared, and then browser vendors thought they could save some development time by giving all that responsibility to Macromedia/Adobe. But then it happened that you would need [xxxx] operating system to be able to see that website (or you need [xxxx] plugin for that matter), and you wouldn't expect a single company to be able to deploy to all the operating systems. Yes, there are many more than just 2 operating systems.

I'm using Linux 64-bit. Flash keeps crashing (for whichever reason). And there is no news that Unity3D will release a player for the OS I chosed to use. Oh! and no, there is no way I'll go back to Windows or MacOS.

So, all in all... "this website is best viewed with [xxxxx] browser" is a good thing. Specially now that the browser vendors are so active.
@Mr.doob: Thank you :)
@mr doob

I think you missed the bit where i said people will not allow this to happen - whatever your experience on Linux. Most people don't want a return to the "best viewed with" scenario and they won't have it. Devs and consumers will just go elsewhere when faced with that i'm afraid.

At least you're not trying to suggest that html5 is about open development, which of course it tries to cloak itself in. As you point out its about a small number of vendors and their interests.

It's not easy when you become immersed in your os of choice, but most people don't really care, and will carry on using Flash regardless. Like or not, that is the situation in the real world that we all have to live with.

The truth is that HTML5 was a PR stunt, as many expected it's turned out to be more marketing than IT.

The main point for prospective developers is that its several years away, and always will be. It would be a shame to discover that after investing lots of time and effort.
aaron has a point about the issue of when html5 will be finished - most people are shocked when they find out it's been in development since 2004.

http://en.wikipedia.org/wiki/Html5
Time will tell.

In the meantime, I'll continue happily creating stuff in my bubble. Unfortunately, only people using up to date browsers will be able to play with these new things.
Great work.
This is also a rather known behaviour for having gradient backgrounds since Windows 98 where some people used this technique to stretch 2-4 pixel bmps to whole screen to get gradients.
Very good Mr. Doob! But, there is a little problem here.

In a triangle with red, green and blue on each edge as in your example, and with the 2x2 texture you used, the gradient in the edge red-green will be as you expect it, the red-blue, also as you expect it, but the green-blue edge will have black and red on it, it won't be the middle cyan you expect.

One possible solution for this problem might be to set the colors of the 2x2 texture like this:

- Top-left: red
- Top-right: green
- Bottom-left: blue
- Bottom-right: the average of green and blue

Then, divide the triangle you want to apply it in 2 parts, by the red vertex and the middle of the blue-green edge. Apply the textures of the (top-left, bottom-left, bottom-right) triangle and the (top-left, top-right, bottom-right) triangle.

At this time of the night I'm not sure if it would add some significative distortion, if any... but I think it is worth a try :)
Indeed. The results I'm getting feature some glitches.

http://mrdoob.github.com/three.js/examples/materials.html

The idea of splitting the triangle in two is interesting but it'll be twice as slow as it'll have to draw twice the amount of triangles. But I'll give it a try. If that's what we have to pay for getting the expected results let be it :)

Als er staat dat zoveel andere het ook hebben gedaan vinden ze de bevestiging en zijn ze eerde geneigd te klikken.