Set a sphere on fire with three.js

Fire it up!

Next element of nature in line, smoke and fire! I did a version in flash about a year ago, playing with 4D Perlin noise. That bitmapdata was like 128×128 big using haxe to work with fast memory. At 20fps. Now we can do fullscreen in 60+ fps. Sweet.

First, note that code snippets below is taken out of context and that they using built-in variables and syntax from three.js. If you have done shaders before you will recognize variables like mvPosition and viewMatrix, or uniforms and texture-lookups.

The noise is generated within a fragment shader, applied to a plane inside a hidden scene, rendered to a texture and linked to the target material. This process is just forked from this demo by @alteredq. Before wrapping the texture around the sphere we have to adjust the output of the noise to be suitable for spherical mapping. If we don’t, the texture will look pinched at the poles as seen in many examples. This is one of the advantages of generating procedural noise; that you can modify the output right from the start. By doing this, each fragment on the sphere get the same level of details in the texture instead of stretching the image across a surface. For spheres, this following technique is one way of finding where (in the imaginary volume of noise) you should fetch the noise-value:

float PI = 3.14159265;
float TWOPI = 6.28318531;
float baseRadius = 1.0;

vec3 sphere( float u, float v) {
	u *= PI;
	v *= TWOPI;
	vec3 pSphere;
	pSphere.x = baseRadius * cos(v) * sin(u);
	pSphere.y = baseRadius * sin(v) * sin(u);
	pSphere.z = baseRadius * cos(u);
	return pSphere;
}

 

Notice the 2d-position that that goes into the function and the returning vec3-type. The uv-coordinates is translated to a 3d-position on the sphere. Now we can just look up that noise-value and plot it to the origin uv-position. Changing the baseRadius affects the repeating of the texture.

Another approach is to do everything in the same shader-pair, both noise and final output. We get the interpolated world-position of each fragment from the vertex-shader, so no need for manipulating right? But since this example uses displacements in the vertex-shader I need to create the texture in a separate pass and send it as a flat texture to the last shader.

So now we have a sphere with a noise texture applied to it. It look nice but the surface is flat.

I mentioned displacements, this is all you have to do:

#ifdef VERTEX_TEXTURES
	vec3 texturePosition = texture2D( tHeightMap, vUv ).xyz;
	float dispFactor = uDispScale * texturePosition.x + uDispBias;
	vec4 dispPos = vec4( normal.xyz * dispFactor, 0.0 ) + mvPosition;
	gl_Position = projectionMatrix * dispPos;
#else
	gl_Position = projectionMatrix * mvPosition;
#endif

 

uDispScale and uDispBias is uniform variables that we can control with javascript at runtime. We just add a value to the original position along the normal direction. The #ifdef-condition let us use a fallback for those on browsers or environments that doesn’t support textures in the vertex-shader. It’s not available in all implementations of webGL, but depending on your hardware and drivers it should work in latest Chrome and Firefox builds. Correct me if I’m wrong here. This feature is crucial for this demo, so I prompt a alert-box for those unlucky users.

After adding displacement I also animate the vertices. You can e.g. modify the vertex-positions based on time, the vertex position itself or the uv-coordinates to make the effects more dynamic. One of the effects available in the settings-panel is “twist”. I found it somewhere I don’t remember now, but here it is if you’re interested:

vec4 DoTwist( vec4 pos, float t )
{
	float st = sin(t);
	float ct = cos(t);
	vec4 new_pos;

	new_pos.x = pos.x*ct - pos.z*st;
	new_pos.z = pos.x*st + pos.z*ct;

	new_pos.y = pos.y;
	new_pos.w = pos.w;

	return( new_pos );
}

//this goes into the main function
float angle_rad = uTwist * 3.14159 / 180.0;
float force = (position.y-uHeight*0.5)/-uHeight * angle_rad;

vec4 twistedPosition = DoTwist(mPosition, force);
vec4 twistedNormal = DoTwist(vec4(vNormal,1.0), force);

//change matrix
vec4 mvPosition = viewMatrix * twistedPosition;

 

The variable force has to be changed to your need. Basically you want to set a starting position and a range that is going to be modified.

Then we reach the final fragment-shader. This one is fairly simple. I blend two colors. Multiply those with one of the channels in the height-map (the noise texture). Then mix this color with the smoke color. All colors get their values based on the screen-coord, a quickfix for this solution. Lastly I apply fog to make fire blend into the background, looking less like a sphere.

For some final touch, a bloom filter is applied and some particles is thrown in as well. Had some help by @aerotwist and his particle tutorial.

That’s it folks, thanks for reading.

 

 

Comments (4)

  1. Try and see if there is another process that using the GPU, like Photoshop. Have experienced some troubles when more than one application claims the window-area.

  2. Pingback: Great balls of fire | CreativeJS

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>