Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs.
Stunning Procedural Skies in WebGL - Part 2
This is the second of three technical articles detailing my recent work on improving the look and customizability of the skies in Cloud Party. This post includes code!
This is the second of three technical articles detailing my recent work on improving the look and customizability of the skies in Cloud Party. Part 1 covered background and research on sky dome rendering; part 2 (this one) will go into detail on the implementation and optimization of the sky dome rendering technique; and part 3 will cover the details of the cloud system.
Recall from last time that our goal is to create a sky renderer that looks good (not necessarily correct, but believable), is customizable by users, reacts to changes in sun angle, and runs very fast. Speed is important because we want to run and look good on a wide variety of GPUs, and ideally the sky rendering should not take up very much of our frame time.
If you missed it last time, go check out the demo!
The code in this article is licensed under a Creative Commons Attribution 3.0 Unported License.
Initial Implementation
The first simplification to the scattering problem we are going to make is to only compute single-scattering (assuming light bounces of particles in the atmosphere at most one time) instead of multiple-scattering (where light bounces around many times). We are also going to assume that the density of the atmosphere is constant.
With these simplifications, using the ATI paper I referenced last time I wrote the following (completely unoptimized) GLSL functions:
vec3 beta_rayleigh, beta_mie;
float g_mie;
float PI = 3.14159265359;
vec3 calcExtinction(float dist) {
return exp(-dist * (beta_rayleigh + beta_mie));
}
vec3 calcScattering(float cos_theta) {
// phase for rayleigh scattering
float phase_rayleigh = (3.0 / (16.0 * PI)) * (1.0 + cos_theta * cos_theta);
// phase for mie scattering, using Henyey/Greenstein approximation
float phase_mie = (1.0 / (4.0 * PI)) * (1.0 - g_mie) * (1.0 - g_mie);
phase_mie /= pow(1.0 + g_mie * g_mie - 2.0 * g_mie * cos_theta, 3.0 / 2.0);
return (phase_rayleigh * beta_rayleigh + phase_mie * beta_mie) / (beta_rayleigh +
beta_mie);
}
Additionally we need to calculate the distance through the atmosphere for a particular position and vector. To do this I use a ray-sphere intersection test, which I simplified by assuming the position is located inside the atmosphere:
float PLANET_RADIUS = 6371000;
float ATMO_RADIUS = PLANET_RADIUS + 100000;
float opticalDepth(in vec3 position, in vec3 ray) {
// the incoming position is in a space with the origin on the surface of the planet
// convert to a space with the origin at the center of the planet