Hey!

First a bit of background.

I’m a huge Elite and space sim fan! Sometimes i get cravings to play First Encounters again, but usually when i do, i get so disappointed by how primitive it is nowadays and don’t get very far.

A while back i got this urge again, and decided to check what else is out there.

I found this: http://pioneerspacesim.net/

An open source “clone”, or something similar, of First Encounters. Rather than playing it, i went on and checked some youtube vids of someone playing it. The guy flew between star systems and landed on planets, watching sunrises and sunsets. It looked quite nice, nice atmospheric shader and all. But the worlds were barren. Pretty natural, i guess, most worlds are barren. But it was something else as well, there were never any clouds. This felt wrong. If there’s a decent atmosphere (which some planets claimed to have), there should at least be hints of clouds sometimes.

This annoyed me, and i started contemplating the matter. I wanted to do planet stuff.

The other day, i stumbled across @aaronperkins: http://hub.jmonkeyengine.org/forum/topic/jmeplanet/

I’ve been watching that video several times now, being equally impressed each time (not saying how much i’d like to try it in my non-delivered Oculus Rift).

An excellent start for some kind of space simulation.

Still, no clouds!

So i thought, what would it take to add clouds to a planet like that?

What i came up with was this (not saying i invented it, i’m pretty sure it’s a common way to create clouds. There might even be something like this on the forum).

I made a custom shader, with two textures.

One has three layers of clouds, sparse, medium and dense/overcast stored in the r,g and b channels.

The shader selects these using either a second rgb texture, or vertex colors (in case you’d like something more flexible).

I started to struggle a bit with uploading it to bitbucket, but it seems they don’t like subversion. I can post the code and textures somewhere in case anyone wants to implement it.

http://youtu.be/fYQqJI8uQ0k

Clouds.j3md

MaterialDef Colored Textured {

MaterialParameters {

Texture2D Clouds

Texture2D Selector

Boolean VertexColor (UseVertexColor)

Boolean SelectFromTexture

Float LowerAlphaThreshold : 0.35

Float UpperAlphaThreshold : 0.6

Int CloudTextureWidth : 512

Int SelectorWidth : 256

Float WindSpeed : 0.01;

}

```
Technique {
LightMode MultiPass
VertexShader GLSL100: MatDefs/Clouds.vert
FragmentShader GLSL100: MatDefs/Clouds.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
ViewMatrix
NormalMatrix
Time
}
Defines {
USE_VERTEXCOLOR : VertexColor
USE_SELECTOR_TEX : SelectFromTexture
}
}
}
```

Clouds.vert

```
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat4 g_ViewMatrix;
uniform vec4 g_LightColor;
uniform vec4 g_LightPosition;
uniform mat3 g_NormalMatrix;
uniform vec4 g_AmbientLightColor;
attribute vec3 inPosition;
attribute vec2 inTexCoord;
attribute vec3 inNormal;
varying vec2 texCoord;
uniform float g_Time;
varying float time;
varying vec4 ambientColor;
varying vec4 lightColor;
varying vec4 vLightDir;
varying vec3 vNormal;
varying vec3 vViewDir;
varying float inside;
//uniform sampler2D m_Selector;
#ifdef USE_VERTEXCOLOR
attribute vec4 inColor;
varying vec4 vertColor;
#endif
void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir){
float posLight = step(0.5, color.w);
vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight);
lightDir = vec4(normalize(tempVec), 1.0);
}
void main(){
texCoord = inTexCoord;
time = g_Time;
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
vec3 wvNormal = normalize(g_NormalMatrix * inNormal);
vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz,clamp(g_LightColor.w,0.0,1.0)));
wvLightPos.w = g_LightPosition.w;
lightColor = g_LightColor;
vec3 wvPosition = (g_WorldViewMatrix * vec4(inPosition, 1.0)).xyz;
lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);
lightColor.w = 1.0;
vNormal = wvNormal;
vViewDir = normalize(-wvPosition);
#ifdef USE_VERTEXCOLOR
vertColor = inColor;
#endif
float len = length(g_CameraPosition - inPosition.xyz);
inside = len;
ambientColor = vec3(0.2, 0.2, 0.2) * g_AmbientLightColor.rgb;
}
```

Clouds.frag

varying vec2 texCoord;

uniform sampler2D m_Clouds;

uniform sampler2D m_Selector;

uniform float m_LowerAlphaThreshold;

uniform float m_UpperAlphaThreshold;

uniform int m_CloudTextureWidth;

uniform int m_SelectorWidth;

uniform float m_WindSpeed;

varying float time;

```
varying vec3 ambientColor;
varying vec4 lightColor;
varying vec4 vLightDir;
varying vec3 vNormal;
varying vec3 vViewDir;
#ifdef USE_VERTEXCOLOR
varying vec4 vertColor;
#endif
vec2 computeLighting(in vec3 wvNorm, in vec3 wvLightDir){
float diffuseFactor = max(0.0, dot(wvNorm, wvLightDir));
float specularFactor = 0.0;//lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess);
float att = vLightDir.w;
return vec2(diffuseFactor, specularFactor) * vec2(att);
}
void main(){
float t = time * 0.1;
float y = mod(t / m_CloudTextureWidth, m_CloudTextureWidth);
float x = mod((t / m_CloudTextureWidth - y + t * m_WindSpeed), m_CloudTextureWidth);
vec4 cloud = texture2D(m_Clouds, vec2(texCoord.x + x, texCoord.y + y));
vec4 pick;
vec4 cloud2;
#ifdef USE_VERTEXCOLOR
pick = vertColor;
#else ifdef USE_SELECTOR_TEX
t *= 10;
float y2 = mod(t / m_SelectorWidth, m_SelectorWidth);
float x2 = mod((t / m_SelectorWidth - y2), m_SelectorWidth);
pick = texture2D(m_Selector, vec2(texCoord.x + x2, texCoord.y + y2));
#endif
float c1 = cloud.r * pick.r;
float c2 = cloud.g * pick.g;
float c3 = cloud.b * pick.b;
float prod = c1 + c2 + c3;
prod = min(prod, 1.00);
vec4 lightDir = vLightDir;
lightDir.xyz = normalize(lightDir.xyz);
vec3 normal = vNormal;// * vViewDir; // vec3(0.0, 1.0, 0.0);//
vec2 light = computeLighting(normal, lightDir.xyz);
float threshold = m_UpperAlphaThreshold - m_LowerAlphaThreshold;
prod = min(1.0, prod);
if(prod < m_LowerAlphaThreshold){
prod = 0.0;
} else if(prod > m_UpperAlphaThreshold){
prod = 1.0;
} else {
prod = prod / threshold - m_LowerAlphaThreshold / threshold;
}
gl_FragColor = vec4(vec3(prod * light.x ) + ambientColor.rgb, prod * pick.a);
}
```

Clouds.j3m

Material My Material : MatDefs/Clouds.j3md {

MaterialParameters {

Selector : Repeat Textures/Selection.jpg

Clouds : Textures/Clouds.png

}

AdditionalRenderState {

Blend Alpha

FaceCull Off

Wireframe Off

}

}

TestCode:

public void simpleInitApp() {

flyCam.setMoveSpeed(40);

Sphere cloudSphere = new Sphere(50, 50, 105.2f);

```
cloudSphere.setTextureMode(Sphere.TextureMode.Polar);
Geometry cloudGeom = new Geometry("Clouds", cloudSphere);
Material cloudMat = assetManager.loadMaterial("Materials/Clouds.j3m");
//cloudMat.getAdditionalRenderState().setAlphaTest(true);
cloudGeom.setMaterial(cloudMat);
cloudGeom.setQueueBucket(RenderQueue.Bucket.Transparent);
cloudMat.setBoolean("SelectFromTexture", true);
// cloudMat.setBoolean("VertexColor", true);
// float[] colors = new float[cloudSphere.getVertexCount() * 4];
//
// for(int i = 0; i < colors.length; i+= 4){
// colors[i] = 0.0f;
// colors[i+1] = 0.0f;
// colors[i+2] = 1.0f;
// colors[i+3] = 1f;
// }
// cloudSphere.setBuffer(VertexBuffer.Type.Color, 4, colors);
// cloudSphere.updateCounts();
rootNode.attachChild(cloudGeom);
Sphere b = new Sphere(20, 20, 100);
Geometry geom = new Geometry("Box", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geom.setMaterial(mat);
rootNode.attachChild(geom);
}
```