Felt like sharing a little so that you guys at least get something out of this project. Here's the complete and un-edited (and un-commented) frag shader for the stars. There's probably some weird stuff in here because it was born of rapid iterations, and it's hard to read code, but if you can figure out how to plug it in to Duality then you should be able to get some nice stars on the screen.

Note: I did not author the noise functions themselves. Pretty much everything else though.

**Code:**

#define PI 3.1415926535897932384626433832795

uniform sampler2D sceneTex;

uniform float u_time;

uniform float u_radius;

uniform vec3 u_color;

uniform vec2 u_center;

uniform vec2 u_k;

float rand(vec2 co){

return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);

}

float nsin(float co){

return (1.0 + sin(co)) / 2.0;

}

float snoise(vec3 uv, float res) // by trisomie21

{

const vec3 s = vec3(1e0, 1e2, 1e4);

uv *= res;

vec3 uv0 = floor(mod(uv, res))*s;

vec3 uv1 = floor(mod(uv+vec3(1.), res))*s;

vec3 f = fract(uv); f = f*f*(3.0-2.0*f);

vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z,

uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z);

vec4 r = fract(sin(v*1e-3)*1e5);

float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);

r = fract(sin((v + uv1.z - uv0.z)*1e-3)*1e5);

float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);

return mix(r0, r1, f.z)*2.-1.;

}

vec4 mod289(vec4 x)

{

return x - floor(x * (1.0 / 289.0)) * 289.0;

}

vec4 permute(vec4 x)

{

return mod289(((x*34.0)+1.0)*x);

}

vec4 taylorInvSqrt(vec4 r)

{

return 1.79284291400159 - 0.85373472095314 * r;

}

vec2 fade(vec2 t) {

return t*t*t*(t*(t*6.0-15.0)+10.0);

}

// Classic Perlin noise

float cnoise(vec2 P)

{

vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);

vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);

Pi = mod289(Pi); // To avoid truncation effects in permutation

vec4 ix = Pi.xzxz;

vec4 iy = Pi.yyww;

vec4 fx = Pf.xzxz;

vec4 fy = Pf.yyww;

vec4 i = permute(permute(ix) + iy);

vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;

vec4 gy = abs(gx) - 0.5 ;

vec4 tx = floor(gx + 0.5);

gx = gx - tx;

vec2 g00 = vec2(gx.x,gy.x);

vec2 g10 = vec2(gx.y,gy.y);

vec2 g01 = vec2(gx.z,gy.z);

vec2 g11 = vec2(gx.w,gy.w);

vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));

g00 *= norm.x;

g01 *= norm.y;

g10 *= norm.z;

g11 *= norm.w;

float n00 = dot(g00, vec2(fx.x, fy.x));

float n10 = dot(g10, vec2(fx.y, fy.y));

float n01 = dot(g01, vec2(fx.z, fy.z));

float n11 = dot(g11, vec2(fx.w, fy.w));

vec2 fade_xy = fade(Pf.xy);

vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);

float n_xy = mix(n_x.x, n_x.y, fade_xy.y);

return 2.3 * n_xy;

}

// Classic Perlin noise, periodic variant

float pnoise(vec2 P, vec2 rep)

{

vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);

vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);

Pi = mod(Pi, rep.xyxy); // To create noise with explicit period

Pi = mod289(Pi); // To avoid truncation effects in permutation

vec4 ix = Pi.xzxz;

vec4 iy = Pi.yyww;

vec4 fx = Pf.xzxz;

vec4 fy = Pf.yyww;

vec4 i = permute(permute(ix) + iy);

vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;

vec4 gy = abs(gx) - 0.5 ;

vec4 tx = floor(gx + 0.5);

gx = gx - tx;

vec2 g00 = vec2(gx.x,gy.x);

vec2 g10 = vec2(gx.y,gy.y);

vec2 g01 = vec2(gx.z,gy.z);

vec2 g11 = vec2(gx.w,gy.w);

vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));

g00 *= norm.x;

g01 *= norm.y;

g10 *= norm.z;

g11 *= norm.w;

float n00 = dot(g00, vec2(fx.x, fy.x));

float n10 = dot(g10, vec2(fx.y, fy.y));

float n01 = dot(g01, vec2(fx.z, fy.z));

float n11 = dot(g11, vec2(fx.w, fy.w));

vec2 fade_xy = fade(Pf.xy);

vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);

float n_xy = mix(n_x.x, n_x.y, fade_xy.y);

return 2.3 * n_xy;

}

float fbm(vec2 P, int octaves, float lacunarity, float gain)

{

float sum = 0.0;

float amp = 1.0;

vec2 pp = P;

int i;

for(i = 0; i < octaves; i+=1)

{

amp *= gain;

sum += amp * cnoise(pp);

pp *= lacunarity;

}

return sum;

}

float pattern_t( in vec2 p, out vec2 q, out vec2 r , in float time)

{

float l = 4.3;

float g = 0.4;

int oc = 2;

q.x = fbm( p + vec2(time,-time),oc,l,g);

q.y = fbm( p + vec2(-5.2*time,1.3*time) ,oc,l,g);

r.x = fbm( p + 4.0*q + vec2(1.7,9.2),oc,l,g );

r.y = fbm( p + 4.0*q + vec2(8.3,2.8) ,oc,l,g);

return fbm( p + 4.0*r ,oc,l,g);

}

void main() {

float v = 0.0;

vec2 uv = gl_TexCoord[0].xy;

vec2 texCoord = vec2(uv);

vec4 color = texture2D(sceneTex, texCoord);

vec4 result = vec4(color);

result.xyz = u_color;

vec2 p = -2.0 + 4.0 * texCoord.xy;

float r = distance(vec2(0.5, 0.5), uv);

r = sqrt(dot(p, p));

if(r < 1.0)

{

float f = ((1.0 - sqrt(1.0 - r)) / r);

texCoord.x = p.x * f;

texCoord.y = p.y * f;

}

texCoord *= u_k - u_k / 2.0;

if(color.a > 0.5)

{

vec2 qq;

vec2 r;

float ca0 = pattern_t(texCoord * 0.5, qq, r, u_time / 32.0) / 2.0;

result.xyz += ca0;

}

else

{

vec2 qq;

vec2 r;

float ca0 = pattern_t(texCoord * 0.1, qq, r, u_time / 128.0) * 1.0;

float ca1 = pattern_t(texCoord * 0.5, qq, r, u_time / 64.0) * 1.0;

float d = clamp(0.5 - distance(vec2(0.5, 0.5), uv), 0.0, 1.0);

result.a = pow(0.1 * (smoothstep(0.0, 0.4, (ca0 * pow(d, 5) * 1e3))), 1.0) + pow(d, 1.3);

texCoord.x = p.x;

texCoord.y = p.y;

result.a += pow(0.1 * (smoothstep(0.1, 0.4, (ca1 * pow(d, 5) * 1e3))), 1.0);

}

gl_FragColor = result;

}

You're free to use this code, I'm actually working on a better looking, faster performing version (I can't give away all of my secret sauce

). But the core concepts remain the same.

If you know nothing about shaders, the long and short of it is that this code is run for every pixel on the screen occupied by a star's sprite. Through a whole bunch of math it turns a totally blank sprite into a pretty, animated star. It might look like a lot of code to be running per-pixel, but it's what graphics cards are made for. Your jaw would drop at the amount of shader code used in modern AAA games. These stars when occupying the entire screen run at a ridiculously high FPS on my R9 390X. Even so, you could optimize this code a

lot.

Edit: You're also encouraged to play with the code! Poke and prod, see what each thing does. Duality supports hot-loading the shaders while they are running in the editor, so it's super easy to see what effect you're having instantly. Here's a cool effect I stumbled on:

I really like the red ones