Procedural Generated 2D Starfield & Planet

Started by
23 comments, last by taylorswift 6 years, 9 months ago

Some progresses have been made since the last time.

I've replaced the blur filter which cause an undesirable loss of brightness by a bloom filter which on the contrary, enhance stars' brightness, which is what I wanted.

More importantly, I have implemented a first attempt of a gas cloud based on the simplex noise shader --- inspired by the many examples available on shadertoy.com (thanks again @JTippetts).

If find the results quite convincing although there is room for improvement. This is not really possible for the moment to control the shape of the gas cloud, and I can hardly define the main color of it. I guess I need to dig more into the math of this shader... ^_^

starfield20170712.png.11a1369b6e840a485e39ac970febf474.png

starfield20170713.png.824ec1653f37a205eb2bd5e2ba38831f.png

Feel free to try it onlinehttp://yahiko.developpez.com/apps/Starfield/

To make it works, with the gas cloud and the bloom effect, your browser and your GPU (or integrated Intel Graphics) need to support WebGL.

Any feedback and suggestion are welcome to improve the overall aspect.

Cheers! :)

Advertisement
On 7/13/2017 at 11:16 AM, yahiko00 said:

Some progresses have been made since the last time.

I've replaced the blur filter which cause an undesirable loss of brightness by a bloom filter which on the contrary, enhance stars' brightness, which is what I wanted.

More importantly, I have implemented a first attempt of a gas cloud based on the simplex noise shader --- inspired by the many examples available on shadertoy.com (thanks again @JTippetts).

If find the results quite convincing although there is room for improvement. This is not really possible for the moment to control the shape of the gas cloud, and I can hardly define the main color of it. I guess I need to dig more into the math of this shader... ^_^

starfield20170712.png.11a1369b6e840a485e39ac970febf474.png

starfield20170713.png.824ec1653f37a205eb2bd5e2ba38831f.png

Feel free to try it onlinehttp://yahiko.developpez.com/apps/Starfield/

To make it works, with the gas cloud and the bloom effect, your browser and your GPU (or integrated Intel Graphics) need to support WebGL.

Any feedback and suggestion are welcome to improve the overall aspect.

Cheers! :)

That looks so much nicer congratulations! If I had any suggestions I would add some more high frequency detail to the gas clouds and fix the star color selection because the big yellow stars in the front and the faint blue stars in the back look very weird to me. You have basically no red stars and way, way too many blue stars. Blue stars are actually less common compared to white, yellow, orange, and red stars.

Thanks. Since I'm brand new in the world of shaders, my gas clouds are far from perfect, and I'm not sure to understand what you mean @taylorswift by "high frequencies" nor how to implement these. My background is not really CG, unfortunately.

Here is the fragment shader I use to generate my nebulae. Anyone who has suggestions to improve this shader both in terms of rendering aspect and/or performance is welcome.


// nebulae.frag.glsl
// inspired from: https://www.shadertoy.com/view/lslSDS

precision mediump float;

uniform vec2  iResolution;
uniform float iGlobalTime;
uniform float redPow;
uniform float greenPow;
uniform float bluePow;
uniform float noiseColor;

#define PI 3.141592653589793

// Simplex Noise by IQ
vec2 hash(vec2 p) {
	p = vec2(dot(p, vec2(127.1, 311.7)),
			 dot(p, vec2(269.5, 183.3)));

	return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(in vec2 p) {
    const float K1 = 0.366025404; // (sqrt(3) - 1) / 2;
    const float K2 = 0.211324865; // (3 - sqrt(3)) / 6;

	vec2 i = floor(p + K1 * (p.x + p.y));
	
    vec2 a = p - i + K2 * (i.x + i.y);
    vec2 o = (a.x > a.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); // vec2 of = 0.5 + 0.5 * vec2(sign(a.x - a.y), sign(a.y - a.x));
    vec2 b = a - o + K2;
	vec2 c = a - 1.0 + 2.0 * K2;

    vec3 h = max(0.5 - vec3(dot(a, a), dot(b, b), dot(c, c)), 0.0);

	vec3 n = h * h * h * h * vec3(dot(a, hash(i + 0.0)), dot(b, hash(i + o)), dot(c, hash(i + 1.0)));

    return dot(n, vec3(70.0));
}

const mat2 m = mat2(0.80, 0.60, -0.60, 0.80);

float fbm4(in vec2 p) {
    float f = 0.0;
    f += 0.5000 * noise(p); p = m * p * 2.02;
    f += 0.2500 * noise(p); p = m * p * 2.03;
    f += 0.1250 * noise(p); p = m * p * 2.01;
    f += 0.0625 * noise(p);
    return f;
}

float marble(in vec2 p) {
	return cos(p.x + fbm4(p));
}

float dowarp (in vec2 q, out vec2 a, out vec2 b) {
	float ang = 0.;
	ang = 1.2345 * sin(33.33); // 0.015 * iGlobalTime;
	mat2 m1 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));
	ang = 0.2345 * sin(66.66); // 0.021 * iGlobalTime;
	mat2 m2 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));

	a = vec2(marble(m1 * q), marble(m2 * q + vec2(1.12, 0.654)));

	ang = 0.543 * cos(13.33); // 0.011 * iGlobalTime;
	m1 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));
	ang = 1.128 * cos(53.33); // 0.018 * iGlobalTime;
	m2 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));

	b = vec2(marble(m2 * (q + a)), marble(m1 * (q + a)));

	return marble(q + b + vec2(0.32, 1.654));
}

void main() {
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

	vec2 q = 2. * uv - 1.;
	q.y *= iResolution.y / iResolution.x;

	// camera	
	vec3 rd = normalize(vec3(q.x, q.y, 1.));

	// Nebulae Background
	q.x = 0.5 + atan(rd.z, rd.x) / (2. * PI);
	q.y = 0.5 - asin(rd.y) / PI + 0.512 + 0.0001 * iGlobalTime;
	q *= 2.34;

	vec2 wa = vec2(0.);
	vec2 wb = vec2(0.);
	float f = dowarp(q, wa, wb);
	f = 0.5 + 0.5 * f;

	// Colorization
	vec3 col = vec3(f);
	float wc = f;
	col = vec3(wc, wc * wc, wc * wc * wc);             // increase: R, G, B
	wc = abs(wa.x);
	col -= vec3(wc * wc, wc, wc * wc * wc);            // decrease: G, R, B
	wc = abs(wb.x);
	col += vec3(wc * wc * wc, wc * wc, wc);            // increase: B, G, R
	col *= 0.7;                                        // decrease all RGB components: more black, less white
	col.r = pow(col.r, redPow);                        // high pass filter for red
	col.g = pow(col.g, greenPow);                      // high pass filter for green
	col.b = pow(col.b, bluePow);                       // high pass filter for blue
	col = smoothstep(0., 1., col);                     // Smoothen color gradients
	//col = 0.5 - (1.4 * col - 0.7) * (1.4 * col - 0.7); // color translation
	col = 0.75 * sqrt(col);                            // increase all RGB components: less black, more white
	col *= 1. - noiseColor * fbm4(8. * q);             // add noise
	col = clamp(col, 0., 1.);

	// Vignetting
	// vec2 r = -1.0 + 2.0 * uv;
	// float vb = max(abs(r.x), abs(r.y));
	// col *= (0.15 + 0.85 * (1.0 - exp(-(1.0 - vb) * 30.0)));

	gl_FragColor = vec4(col, 1.0);
}

 

About star color selection, this is a parameter I can easily change through the GUI of my generator. But I agree, colors shown above are not realistic.

By now, I am working on (fast) planet generation. If anyone has some hints or ressources about this, I am obviously interested.

Cheers! :)

On 7/15/2017 at 0:58 PM, yahiko00 said:

Thanks. Since I'm brand new in the world of shaders, my gas clouds are far from perfect, and I'm not sure to understand what you mean @taylorswift by "high frequencies" nor how to implement these. My background is not really CG, unfortunately.

Here is the fragment shader I use to generate my nebulae. Anyone who has suggestions to improve this shader both in terms of rendering aspect and/or performance is welcome.



// nebulae.frag.glsl
// inspired from: https://www.shadertoy.com/view/lslSDS

precision mediump float;

uniform vec2  iResolution;
uniform float iGlobalTime;
uniform float redPow;
uniform float greenPow;
uniform float bluePow;
uniform float noiseColor;

#define PI 3.141592653589793

// Simplex Noise by IQ
vec2 hash(vec2 p) {
	p = vec2(dot(p, vec2(127.1, 311.7)),
			 dot(p, vec2(269.5, 183.3)));

	return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(in vec2 p) {
    const float K1 = 0.366025404; // (sqrt(3) - 1) / 2;
    const float K2 = 0.211324865; // (3 - sqrt(3)) / 6;

	vec2 i = floor(p + K1 * (p.x + p.y));
	
    vec2 a = p - i + K2 * (i.x + i.y);
    vec2 o = (a.x > a.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); // vec2 of = 0.5 + 0.5 * vec2(sign(a.x - a.y), sign(a.y - a.x));
    vec2 b = a - o + K2;
	vec2 c = a - 1.0 + 2.0 * K2;

    vec3 h = max(0.5 - vec3(dot(a, a), dot(b, b), dot(c, c)), 0.0);

	vec3 n = h * h * h * h * vec3(dot(a, hash(i + 0.0)), dot(b, hash(i + o)), dot(c, hash(i + 1.0)));

    return dot(n, vec3(70.0));
}

const mat2 m = mat2(0.80, 0.60, -0.60, 0.80);

float fbm4(in vec2 p) {
    float f = 0.0;
    f += 0.5000 * noise(p); p = m * p * 2.02;
    f += 0.2500 * noise(p); p = m * p * 2.03;
    f += 0.1250 * noise(p); p = m * p * 2.01;
    f += 0.0625 * noise(p);
    return f;
}

float marble(in vec2 p) {
	return cos(p.x + fbm4(p));
}

float dowarp (in vec2 q, out vec2 a, out vec2 b) {
	float ang = 0.;
	ang = 1.2345 * sin(33.33); // 0.015 * iGlobalTime;
	mat2 m1 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));
	ang = 0.2345 * sin(66.66); // 0.021 * iGlobalTime;
	mat2 m2 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));

	a = vec2(marble(m1 * q), marble(m2 * q + vec2(1.12, 0.654)));

	ang = 0.543 * cos(13.33); // 0.011 * iGlobalTime;
	m1 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));
	ang = 1.128 * cos(53.33); // 0.018 * iGlobalTime;
	m2 = mat2(cos(ang), -sin(ang), sin(ang), cos(ang));

	b = vec2(marble(m2 * (q + a)), marble(m1 * (q + a)));

	return marble(q + b + vec2(0.32, 1.654));
}

void main() {
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

	vec2 q = 2. * uv - 1.;
	q.y *= iResolution.y / iResolution.x;

	// camera	
	vec3 rd = normalize(vec3(q.x, q.y, 1.));

	// Nebulae Background
	q.x = 0.5 + atan(rd.z, rd.x) / (2. * PI);
	q.y = 0.5 - asin(rd.y) / PI + 0.512 + 0.0001 * iGlobalTime;
	q *= 2.34;

	vec2 wa = vec2(0.);
	vec2 wb = vec2(0.);
	float f = dowarp(q, wa, wb);
	f = 0.5 + 0.5 * f;

	// Colorization
	vec3 col = vec3(f);
	float wc = f;
	col = vec3(wc, wc * wc, wc * wc * wc);             // increase: R, G, B
	wc = abs(wa.x);
	col -= vec3(wc * wc, wc, wc * wc * wc);            // decrease: G, R, B
	wc = abs(wb.x);
	col += vec3(wc * wc * wc, wc * wc, wc);            // increase: B, G, R
	col *= 0.7;                                        // decrease all RGB components: more black, less white
	col.r = pow(col.r, redPow);                        // high pass filter for red
	col.g = pow(col.g, greenPow);                      // high pass filter for green
	col.b = pow(col.b, bluePow);                       // high pass filter for blue
	col = smoothstep(0., 1., col);                     // Smoothen color gradients
	//col = 0.5 - (1.4 * col - 0.7) * (1.4 * col - 0.7); // color translation
	col = 0.75 * sqrt(col);                            // increase all RGB components: less black, more white
	col *= 1. - noiseColor * fbm4(8. * q);             // add noise
	col = clamp(col, 0., 1.);

	// Vignetting
	// vec2 r = -1.0 + 2.0 * uv;
	// float vb = max(abs(r.x), abs(r.y));
	// col *= (0.15 + 0.85 * (1.0 - exp(-(1.0 - vb) * 30.0)));

	gl_FragColor = vec4(col, 1.0);
}

 

About star color selection, this is a parameter I can easily change through the GUI of my generator. But I agree, colors shown above are not realistic.

High frequency just means add more octaves of noise to your FBM. However I’d recommend storing your noise in a texture; generating it in the shader might be okay for one octave of perlin noise but when you get more complex its faster to prebake it.

By the way, I would steer clear of simplex-lattice additive gradient noise; it’s okay for two dimensions but when you scale it up to three or more dimensions the implementation gets very hairy. Take it from someone who maintains the Swift procedural noise library. Plus, a lot of popular noise recipes assume you’re working with a 3D classical gradient noise generator (3D cubic-lattice interpolated gradient noise). Contrary to the claims of CS theoreticians, classical gradient noise is much faster than simplex noise.

Quote

By now, I am working on (fast) planet generation. If anyone has some hints or ressources about this, I am obviously interested.

Cheers! :)

Welcome to my hell. Procedural planet generation is no small task, it’s one of the more challenging feats in the procedural generation world, and there are a lot of poor-quality tutorials that give ugly results. How much detail do you need? If you only need marble-sized renders, it’s relatively easy to get something that looks good. If you need to fly over them, get ready to spend a few months getting it right.

This topic is closed to new replies.

Advertisement