Making a skydome in three.js

In three.js, the illusion of a sky is often made using a "skybox," a cube made up of 6 images that fold up neatly. The user's perspective is placed within the cube, giving the illusion of being contained in a 3D environment.

This tutorial explains how to create a "skydome" or "skysphere." Similar to a skybox, a skydome or skysphere is a way to create the illusion of a sky in your 3D environment.

Using three.js's SphereGeometry, it is quite simple:

var geometry = new THREE.SphereGeometry(3000, 60, 40);  
var uniforms = {  
  texture: { type: 't', value: THREE.ImageUtils.loadTexture('/path/to/my_image.jpg') }
};

var material = new THREE.ShaderMaterial( {  
  uniforms:       uniforms,
  vertexShader:   document.getElementById('sky-vertex').textContent,
  fragmentShader: document.getElementById('sky-fragment').textContent
});

skyBox = new THREE.Mesh(geometry, material);  
skyBox.scale.set(-1, 1, 1);  
skyBox.eulerOrder = 'XZY';  
skyBox.renderDepth = 1000.0;  
scene.add(skyBox);  

Notice that we reference some shaders #sky-vertex and #sky-fragment. For a simple sky, these shaders just map to your texture and not much else:

<script type="application/x-glsl" id="sky-vertex">  
varying vec2 vUV;

void main() {  
  vUV = uv;
  vec4 pos = vec4(position, 1.0);
  gl_Position = projectionMatrix * modelViewMatrix * pos;
}
</script>

<script type="application/x-glsl" id="sky-fragment">  
uniform sampler2D texture;  
varying vec2 vUV;

void main() {  
  vec4 sample = texture2D(texture, vUV);
  gl_FragColor = vec4(sample.xyz, sample.w);
}
</script>  

Asterank uses this code to render the ESO's famous high-resolution panorama of the milky way:

ESO milky way panorama

And in the simulation:

Asterank with galaxy panorama

Why not SkyBox?

It can be frustrating to find good skybox images. You can create your own via Blender, but the process is somewhat involved. Depending on your image, you may have to do some manipulation to eliminate seams and other graphical artifacts. In the end I wound up downloading a bunch of software and not being happy with the outcome.

In my opinion, it's much easier to create a 'skydome' or a 'skysphere' with just a single image.

There are some tradeoffs, as noted in this stackoverflow post. But in most cases, I find it much easier to deal with a single image instead of 6 cube images.

Happy coding! Follow me on twitter @iwebst.

Debugging ANGLE errors in WebGL on Windows

In order to support WebGL on Windows, browsers use ANGLE, short for Almost Native Graphics Layer Engine. ANGLE is an API that translates OpenGL calls to DirectX calls, necessary because most of the time Windows users are running DirectX, Microsoft's proprietary graphics framework, rather than OpenGL.

Because Microsoft refuses to support WebGL directly, ANGLE is developed mostly at Google and used by Firefox and Chrome to provide webgl support on Windows. The lack of built-in support for OpenGL can cause some frustrating issues in WebGL development.

Is the bug caused by ANGLE?

This step is typically straightforward. Your shaders don't work on most Windows environments.

To verify, start Chrome with the flag --use-gl=desktop. You will force it to use opengl and your animation should work properly. If your animation works properly, there is a bug in ANGLE-generated HLSL.

Debugging the ANGLE problem

You may gain some insight by manually inspecting the ANGLE-generated HLSL. To obtain HLSL source, start Chrome on windows with --enable-privileged-webgl-extension. Then, use the getTranslatedShaderSource API call:

var shadertxt = document.getElementById('vertexshader');

var gl = new THREE.WebGLRenderer().getContext();

var vsh = gl.createShader(gl.VERTEX_SHADER);  
gl.shaderSource(vsh, shadertxt);  
gl.compileShader(vsh);  
if(!gl.getShaderParameter(vsh, gl.COMPILE_STATUS)) {  
    console.log("Invalid shader : " + gl.getShaderInfoLog(vsh));
    var lines = shadertxt.split('\n');
    for (i in lines) {
        console.log(i, lines[i]);
    }
};
var hlsl = gl.getExtension("WEBGL_debug_shaders").getTranslatedShaderSource(vsh);

console.log("Your shader's HLSL code:", hlsl);  

I've included a full working example in this jsfiddle, which outputs the HLSL translation of a GLSL shader. Note that you must be running Chrome with the --enable-privileged-webgl-extension flag.

Next Steps

Actually identifying the issue can be painful. This can vary based on the problem. For example, loop unrolling bugs or other translation idiosyncracies should be obvious. But smaller bugs may require further troubleshooting:

  • The language is very similar to GLSL, and it may be possible to follow the shader logic, note the area that is wrong, and refactor GLSL code to work around it.
  • Binary search/process of elimination. Simplify your shader, removing parts of code until it works on Windows. If you do this systematically, you can find the code that causes the problem.
  • Other debugging tools. Extensions like the Chrome WebGL Inspector may prove useful if you run them on your Windows machine.

Overall

I'm very thankful for ANGLE and the work that people have contributed to the project. If it weren't possible to write WebGL for modern browsers on Windows, WebGL would be more of a toy than a powerful framework with growing browser support and market share. ANGLE has been improving quickly, and someday there will be no more issues.

Hopefully this post helps someone who is also running into mysterious Windows WebGL bugs. Good luck!

Introduction to Custom Shaders in Three.js

This writeup is a compilation of things I wish I'd known before I started working on my Three.js app with custom shaders.

Why Use Custom Shaders?

Shaders run on the graphics card and give you much lower-level access to how things are rendered. In my case, switching to custom shaders improved performance in my astronomics simulation a thousandfold and made it visually stunning:

OpenGL Shader Language (GLSL)

Webgl shaders are small programs written in a specialized language called GLSL similar to C. It is strongly typed and includes basic data types such as float, int, and bool, and additional Vector data types, which are useful for graphics programming: vec2, vec3, and vec4. A "vector" in this context is essentially a fixed-length array. GLSL vectors contain floats by default, but you can specify int vectors as ivec2 and bool vectors as bvec2, for example.*

There are two kinds of shaders: vertex shaders and fragment shaders.

  • Vertex shaders manipulate the vertices of polygons (aka the points that define their shape). This gives you control over the shape and position of things in your rendering. At render time, your vertex shader is run on every vertex.

  • Fragment shaders are also known as pixel shaders, and they determine how the pixels between your vertices look. This is useful for things like lighting or gradients.

You can pass variables into shaders, either as uniforms or as attributes. Uniforms are constant across all vertices. Attributes can vary from vertex to vertex. These values are supplied by Javascript. When referenced in shaders, they are constants - you can't reassign uniform or attributes in GLSL.

Let's write some simple shaders.

Vertex shaders always run first:


// These have global scope

uniform vec3 color;
attribute float size;

varying vec3 vColor;  // 'varying' vars are passed to the fragment shader

void main() {
  vColor = color;   // pass the color to the fragment shader
  gl_PointSize = size;
}

Fragment shaders run afterwards:

varying vec3 vColor;

void main() {  
  gl_FragColor = vec4(vColor, 0.5);  // adjust the alpha
}

For a more detailed introduction to GLSL and the graphics pipeline, take a look here.

Using shaders in Three.js

Include your GLSL shaders on your page in script tags, like so:

<script type="x-shader/x-vertex" id="vertexShader">  
  // Your GLSL vertex shader here...
</script>

<script type="x-shader/x-fragment" id="fragmentShader">  
  // Your GLSL fragment shader here...
</script>  

Browsers will not recognize the script type, so they won't execute your shaders as JS.

In Three.js, custom shaders use a ShaderMaterial. When you create this material, you supply your custom shaders:

material = new THREE.ShaderMaterial({  
  uniforms: uniforms,
  attributes: attributes,
  vertexShader: document.getElementById('vertexShader').textContent,
  fragmentShader: document.getElementById('fragmentShader').textContent
});

As you can see, a shader is just text. Instead of loading the shader from the DOM, you may opt to include the text of your shaders directly in your JS, or you can load it via AJAX request.

As mentioned before, uniforms and attributes are variables passed to your shaders. They are defined in your JS by type and value:

// Define a color-typed uniform
var uniforms = {  
  myColor: { type: "c", value: new THREE.Color( 0xffffff ) },
};

Attributes are defined as arrays with length equal to the number of vertices. Each index in the array is an attribute for the corresponding vertex.

// My float attribute
var attributes = {  
  size: { type: 'f', value: [] },
};

for (var i=0; i < numVertices; i++) {  
  attributes.size.value[i] = 5 + Math.floor(Math.random() * 10);
}

Be careful, type matters. A list of types available is on the Three.js wiki. As of writing, integer and boolean types are not allowed as uniforms or attributes.

ANGLE and Hidden Compatibility Issues

If you develop on Linux like me (or a mac), be sure to test on Windows. There are nontrivial differences between webgl on Chrome/Firefox on Windows versus Linux/OSX.

This is because in nearly all cases, Windows users are running Microsoft's proprietary DirectX instead of OpenGL. The GLSL is automatically translated to HLSL (DirectX's equivalent) via ANGLE. Although ANGLE is great because it makes webgl work on Windows, you may occasionally run into some issues.

In my case, the geometries with custom shaders simply didn't show up. In other reported cases, ANGLE-generated HLSL may result in significant performance penalties due to faulty loop unrolling.

How to determine if your bug is caused by ANGLE

The symptoms are fairly straightforward: your shaders work on linux/osx but not on Windows. If you start Chrome with the flag --use-gl=desktop, you will force it to use opengl and your animation should work properly.

How to debug an ANGLE problem

This part can be painful and there isn't an easy way to do it.

In my case, I generated the HLSL and manually inspected it. You can do this by starting Chrome on windows with --enable-privileged-webgl-extension and using getTranslatedShaderSource:

var shadertxt = document.getElementById('vertexshader');

var gl = new THREE.WebGLRenderer().getContext();

var vsh = gl.createShader(gl.VERTEX_SHADER);  
gl.shaderSource(vsh, shadertxt);  
gl.compileShader(vsh);  
if(!gl.getShaderParameter(vsh, gl.COMPILE_STATUS)) {  
    console.log("invalid shader : " + gl.getShaderInfoLog(vsh));
    var lines = shadertxt.split('\n');
    for (i in lines) {
        console.log(i, lines[i]);
    }
};
var hlsl = gl.getExtension("WEBGL_debug_shaders").getTranslatedShaderSource(vsh);

console.log(hlsl);  
document.write(hlsl);  

I've included a full working example in this jsfiddle.

Unfortunately, I couldn't find the bug just by eyeballing the HLSL. I used a process-of-elimination approach where I got rid of parts of shader code until things rendered properly. Then, I added parts back until I found the code that was causing the problem. Hopefully there will be a better way in the future.

Conclusion

It was a lot of fun to learn about webgl shaders, and it improved my simulation drastically. Aside from frustrating ANGLE problems (which should be uncommon), it is quite easy overall. Good luck!

Footnotes

* There are also matrix types, mat2, mat3, and mat4, which only support floats.

Optimizing Three.js Performance: Simulating Tens Of Thousands Of Independent Moving Objects

This post covers my experience building Asterank 3D, a simulation engine capable of showing tens of thousands of particles with unique trajectories. Each particle has a unique path calculated on-the-fly using laws of astrodynamics.

I documented my progress as I scaled the simulation from 60 objects to 50,000 objects. Each section will cover techniques that produced significant performance gains.

Use a ParticleSystem

A naive implementation of the solar system view represents every single object as its own particle. While this is a convenient approach, it becomes less reliable at around 40 moving particles or so on a typical laptop.

My original code was analogous to:

for (var i=0; i < 100; i++) {  
  var particle = new THREE.Particle(new THREE.Vector3D(x, y, z));
  scene.add(particle);
}

Rather than individually adding each particle to the scene, use a single ParticleSystem:

var particle_system_geometry = new THREE.Geometry();  
for (var i=0; i < 100; i++) {  
  particle_system_geometry.vertices.push(new THREE.Vector3D(x, y, z));
}
var particle_system_material = new THREE.ParticleBasicMaterial({  
  color: 0xffffff,
  size: 1
});
var particleSystem = new THREE.ParticleSystem(  
  particle_system_geometry,
    particle_system_material
);
scene.add(particleSystem);  

To customize the appearance of your particles, you can use sprites to override the default shape.

This change will easily result in several orders of magnitude improvement. However, things like ray tracing/collision detection (which are useful for things like mouseover behaviors) become more complicated.

Browser "multithreading" with web workers

Javascript is single-threaded in all browsers, which means all logic blocks the main UI thread. As a result, your simulation will feel jumpy or unresponsive to the user if the browser is busy computing many new particle positions.

HTML5 Web Workers are similar to threads that communicate with the main UI thread via message passing. Running complex blocking calculations with a web worker will keep the UI updating smoothly.

Web workers are typically defined as JS files that run within their own context. They can't use any of the global variables you use in your main scripts, such as window or document. You can't do UI/DOM work in web workers, but you can perform other calculations.

Here's an example webworker file:


// in worker.js
self.addEventListener('message', function() {
  // We received a message from the main thread!
  // do some computation that may normally cause the browser to hang
  // in my case, I computed the position of an object in space according
  // to Kepler's Laws

  //  now send back the results
  self.postMessage({
    type: 'results',
    data: {
      // ...
    }
  })
})

Create and run the web worker from your UI code like so:

// in your main js file
var worker = new Worker('worker.js');  
worker.postMessage({  
  some_data: 'foo',
  some_more_data: 'bar'
});

A practical implication of web worker context limitations is that you can't use console.log. In order to log messages to the console, you must pass them to the main thread. This also serves as a good example of how to use web workers to do work and pass messages back to the main thread:

// in worker.js
self.postMessage({  
  type: 'debug',
  value: 'some debug message here'
});
// in main js file
worker.onmessage = function(e) {  
  var data = e.data;
  if (data.type === 'debug') {
    console.log(data.value);
  }
  else if (data.type === 'result') {
    // process results
  }
}

For a more in-depth look at web workers, I recommend the HTML5 Rocks introduction.

The gains I saw from using a web worker to handle orbital calculations were substantial. Using a single worker allowed me to smoothly render 10,000 moving particles, as the main UI thread was no longer tied up by position calculations. Additional workers could give more gains in theory, but in my case the benefits diminished very quickly.

Web workers are supported by recent versions of all browsers except for IE (IE 10 adds web worker support). They're also supported by the default browsers on iOS and Android, but have been removed from more recent versions of Android. I created a simple web worker compatibility library for browsers that don't have native support for workers.

Smarter execution flow with Timed Array Processing

After adding web workers, I discovered another bottleneck. Despite having separate threads, we must update the animation on the main thread. It turns out updating the position of tens of thousands of coordinates in Three.js was lagging the simulation.

My code essentially looked like this:

// Blocks rendering until complete
for (var i=0; i < 20000; i++)  
  particles[i].UpdatePosition();

This simple approach delays the execution of renderAnimateFrame() and other rendering updates until the loop is completed. If the loop takes longer than your desired framerate, the simulation will lag.

The fix I implemented is based on this article by Nicholas Zakas, author of O'Reilly's High-Performance JavaScript and other books. I wound up implementing my own version of timedChunk:

function timedChunk(particles, positions, fn, context, callback){  
  var i = 0;
  var tick = function() {
    var start = new Date().getTime();
    for (; i < positions.length && (new Date().getTime()) - start < 50; i++) {
      fn.call(context, particles[i], positions[i]);
    }
    if (i < positions.length) {
      // Yield execution to rendering logic
      setTimeout(tick, 25);
    } else {
      callback(positions, particles);
    }
  };
  setTimeout(tick, 25);
}

In this solution, setTimeout yields execution flow to things like calls to animate(). Even a setTimeout of 0 would work for this purpose, as calling it with no delay simply passes execution flow to whatever's waiting.

Using timedChunk allowed me to increase the number of web workers because it eliminated the UI thread bottleneck.

Using custom shaders

Finally, I saw a huge improvement in smoothness of the simulation by writing a custom shader for my particle system.

This effectively moved all the complex position calculations for my particles to the GPU, which went a long way toward ensuring the speed and reliability of the simulation. Custom shaders are written in GLSL, which is close enough to C that it's not too difficult to translate your math into.

At this point, the number of particles I could show on my desktop became so high that I chose to limit it so laptops with weaker graphics cards could handle the simulation.

You will see significant gains with custom shaders, especially if your particles have independent trajectories. I wrote a quick introduction to shaders that may be useful to beginners.

Conclusion

I went from lagging at ~60 moving particles to being able to smoothly render 50,000+ independently moving particles.

Although webgl performance lags behind native capabilities, it's only a matter of time until the technology and hardware catch up. Until then, I hope this post helps anyone who's running into similar performance issues.

Follow me: @iwebst

How I Built a Canvas/WebGL Visualization With No Graphics Knowledge

I created Asterank, a database of economic value of all the asteroids in our solar system. It condenses about 600,000 data points to the top 100, but the numbers can be pretty difficult to grasp. I sought to create a browser-based visualization of our solar system that illustrates its abundance of resources and mesmerizing beauty. Prior to this, I knew nothing about 3D graphics or WebGL.

Asterank 3D

Choosing a library

I wanted the user to be able to view my models from an arbitrary point in space, so the first step was to choose a JavaScript library for browser-based 3D visualization.

3D in the browser is a fast-developing space. Libraries such as Copperlicht, GLGE, SceneJS, and THREE.js are quite popular, with no major consensus on which is the best. I wound up choosing THREE.js because of its HTML5 Canvas fallback.

Getting started with THREE.js

This basic tutorial is the place to start, introducing the concepts that will guide most of your programming: scenes, meshes, materials, lighting, and the render loop creates animation effects.

The official examples helped with some basic knowledge, and taught me things like how to extrude 3D geometries from simple 2D shapes, but were not terribly helpful overall.

Jerome Etienne's THREE.js boilerplate was immensely useful for hitting the ground running, and he's even developed the option to generate your own customized boilerplate (pictured below).

Three.js boilerplate

Filling the knowledge gap

THREE.js documentation is very sparse and not very useful if you're just starting with 3D. Its main utility lies in the fact that it links to the source code of key classes. This became a recurring theme - the ability to read and understand source code was essential.

The official source for help is StackOverflow. There is a large amount of Q&A knowledge in the github issues for the three.js project from before mrdoob decided that questions should be asked on StackOverflow.

Lee Stemkoski's list of examples is a little-known resource, that, unlike other examples, build on one another. This is probably the best sequence of examples out there, thorough and well-thought. Someone could start here and leave with the knowledge they need for any WebGL project.

Lee Stremkoski's three.js examples

My project was complicated by the fact that I was working with the dev branch in order to get the EllipseCurve object, which I essentially re-implemented specifically for astronomical orbits.

Challenges and Lessons

  1. Orbits - this problem was specific to my project, but I wound up poring through a decent amount of astronomy literature to understand how to correctly render orbits. My model computes basic Keplerian orbits using information mostly contained in this primer. I used an infinite series approximation for true anomaly after discovering that the Equation of Center approximation was inaccurate for some highly inclined orbits (eg. 3200 Phaethon).

  2. Editing TrackballControls for scroll support - this wasn't too hard, but I added a zoom on mouse scroll. It was like binding any other event in JavaScript, but required the willingness to edit existing library source. It was important to keep in mind cross-browser differences in scroll events.

  3. Optimize for performance - keep in mind that many browsers and weaker machines don't handle WebGL or canvas very gracefully. In my project, the number of asteroids is limited and their orbits are computed lazily. I also take into account that some asteroids require less computation in order to get a smooth curve, depending on their orbits.

  4. Always use radians - this goes without saying, but working with astronomy examples that used degrees led to occasional slip-ups.

Conclusion

Finally, an interactive 3D (well, 4D) orbit viewer that isn't a Java applet! I'm looking forward to packaging it and making it more general-use, perhaps for some sort of educational application.

The takeaway: it's easy to program cool graphics in the browser, and you should too.