uniform FragInfo { vec2 resolution; float time; float speed; float planet_size; float show_normals; float show_noise; float seed_value; } frag_info; in vec2 v_screen_position; out vec4 frag_color; float inverseLerp(float v, float min_value, float max_value) { return (v - min_value) / (max_value - min_value); } float remap(float v, float in_min, float in_max, float out_min, float out_max) { float t = inverseLerp(v, in_min, in_max); return mix(out_min, out_max, t); } float saturate(float x) { return clamp(x, 0.0, 1.0); } // Copyright (C) 2011 by Ashima Arts (Simplex noise) // Copyright (C) 2011-2016 by Stefan Gustavson (Classic noise and others) // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: The above copyright // notice and this permission notice shall be included in all copies or // substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", // WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR // THE USE OR OTHER DEALINGS IN THE SOFTWARE. // https://github.com/ashima/webgl-noise/tree/master/src vec3 mod289(vec3 x) { return x - floor(x / 289.0) * 289.0; } vec4 mod289(vec4 x) { return x - floor(x / 289.0) * 289.0; } vec4 permute(vec4 x) { return mod289((x * 34.0 + 1.0) * x); } vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - r * 0.85373472095314; } vec4 snoise(vec3 v) { const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); // First corner vec3 i = floor(v + dot(v, vec3(C.y))); vec3 x0 = v - i + dot(i, vec3(C.x)); // Other corners vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; vec3 i1 = min(g.xyz, l.zxy); vec3 i2 = max(g.xyz, l.zxy); vec3 x1 = x0 - i1 + C.x; vec3 x2 = x0 - i2 + C.y; vec3 x3 = x0 - 0.5; // Permutations i = mod289(i); // Avoid truncation effects in permutation vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0)); // Gradients: 7x7 points over a square, mapped onto an octahedron. // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) vec4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) vec4 x_ = floor(j / 7.0); vec4 y_ = floor(j - 7.0 * x_); vec4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; vec4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; vec4 h = 1.0 - abs(x) - abs(y); vec4 b0 = vec4(x.xy, y.xy); vec4 b1 = vec4(x.zw, y.zw); vec4 s0 = floor(b0) * 2.0 + 1.0; vec4 s1 = floor(b1) * 2.0 + 1.0; vec4 sh = -step(h, vec4(0.0)); vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; vec3 g0 = vec3(a0.xy, h.x); vec3 g1 = vec3(a0.zw, h.y); vec3 g2 = vec3(a1.xy, h.z); vec3 g3 = vec3(a1.zw, h.w); // Normalize gradients vec4 norm = taylorInvSqrt(vec4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); g0 *= norm.x; g1 *= norm.y; g2 *= norm.z; g3 *= norm.w; // Compute noise and gradient at P vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); vec4 m2 = m * m; vec4 m3 = m2 * m; vec4 m4 = m2 * m2; vec3 grad = -6.0 * m3.x * x0 * dot(x0, g0) + m4.x * g0 + -6.0 * m3.y * x1 * dot(x1, g1) + m4.y * g1 + -6.0 * m3.z * x2 * dot(x2, g2) + m4.z * g2 + -6.0 * m3.w * x3 * dot(x3, g3) + m4.w * g3; vec4 px = vec4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)); return 42.0 * vec4(grad, dot(m4, px)); } // The MIT License // Copyright © 2013 Inigo Quilez // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: The above copyright // notice and this permission notice shall be included in all copies or // substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", // WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR // THE USE OR OTHER DEALINGS IN THE SOFTWARE. // https://www.youtube.com/c/InigoQuilez // https://iquilezles.org/ // // https://www.shadertoy.com/view/Xsl3Dl vec3 hash3(vec3 p) // replace this by something better { p = vec3(dot(p, vec3(127.1, 311.7, 74.7)), dot(p, vec3(269.5, 183.3, 246.1)), dot(p, vec3(113.5, 271.9, 124.6))); return -1.0 + 2.0 * fract(sin(p) * 43758.5453123); } float noise(in vec3 p) { vec3 i = floor(p); vec3 f = fract(p); vec3 u = f * f * (3.0 - 2.0 * f); return mix( mix(mix(dot(hash3(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)), dot(hash3(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x), mix(dot(hash3(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)), dot(hash3(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y), mix(mix(dot(hash3(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)), dot(hash3(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x), mix(dot(hash3(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)), dot(hash3(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z); } float fbm6(vec3 p, float persistence, float lacunarity, float exponentiation) { float amplitude = 0.5; float frequency = 1.0; float total = 0.0; float normalization = 0.0; p = p + frag_info.seed_value; for (int i = 0; i < 6; ++i) { float noiseValue = snoise(p * frequency).w; total += noiseValue * amplitude; normalization += amplitude; amplitude *= persistence; frequency *= lacunarity; } total /= normalization; total = total * 0.5 + 0.5; total = pow(total, exponentiation); return total; } float fbm2(vec3 p, float persistence, float lacunarity, float exponentiation) { float amplitude = 0.5; float frequency = 1.0; float total = 0.0; float normalization = 0.0; p = p + frag_info.seed_value; for (int i = 0; i < 2; ++i) { float noiseValue = snoise(p * frequency).w; total += noiseValue * amplitude; normalization += amplitude; amplitude *= persistence; frequency *= lacunarity; } total /= normalization; total = total * 0.5 + 0.5; total = pow(total, exponentiation); return total; } vec3 GenerateStarGrid(vec2 pixel_coords, float cell_width, float star_radius, float seed, bool twinkle) { // fract() gives you 0.0 to 1.0 for the cell_width // - 0.5 will move the origin from the bottom left to the cetner vec2 cell_coords = fract(pixel_coords / cell_width) - 0.5; // "each cell is now scaled in terms of pixels" cell_coords *= cell_width; vec2 cell_id = (floor(pixel_coords / cell_width) + seed) / 100.0; // "(x,y)" vec3 cell_hash_value = hash3(vec3(cell_id, 0.0)); // [-1, 1] - note; "z" is unused right now. // Hash gives you -1 to 1; saturate clamps to 0,1. This will effectivly // kill some of the stars (wanted) vs remap giving you a star per-cell float starBrighness = saturate(cell_hash_value.z); // -1,1 -> 0->1 // Get a star position and vec2 star_position = vec2(0.0); star_position += cell_hash_value.xy * (cell_width * 0.5 - star_radius * 4.0); // float distance_to_star = length(cell_coords); // stars in the middle float distance_to_star = length(cell_coords + star_position); // better falloff float glow = exp(-2.0 * distance_to_star / star_radius); if (twinkle) { // verticle and horizontal flare float noise_sample = noise(vec3(cell_id, frag_info.time * 1.5)); float twinkle_size = remap(noise_sample, -1.0, 1.0, 1.0, 0.1) * star_radius * 6.0; vec2 abs_distance = abs(cell_coords - star_position); // manhattan distance to cell center. // horizontal float twinkleValue = smoothstep(star_radius * 0.25, 0.0, abs_distance.y) * smoothstep(twinkle_size, 0.0, abs_distance.x); // vertical twinkleValue += smoothstep(star_radius * 0.25, 0.0, abs_distance.x) * smoothstep(twinkle_size, 0.0, abs_distance.y); glow += twinkleValue; } return vec3(glow * starBrighness); } vec3 GenerateStars(vec2 pixel_coords) { vec3 stars = vec3(0.0); float size = 4.0; float cell_width = 500.0; for (float i = 0.0; i <= 2.0; i++) { stars += GenerateStarGrid(pixel_coords, cell_width, size, i + frag_info.seed_value, true); size *= 0.5; cell_width *= 0.35; } for (float i = 3.0; i <= 5.0; i++) { stars += GenerateStarGrid(pixel_coords, cell_width, size, i + frag_info.seed_value, false); size *= 0.5; cell_width *= 0.35; } return stars; } // 2D circle float sdfCircle(vec2 p, float r) { return length(p) - r; } float map(vec3 pos) { return fbm6(pos, 0.5, 2.0, 4.0); } vec3 calcNormal(vec3 pos, vec3 n) { // if you sample the noise field along each axis, a small amount of distance // away from the position you're interested, you'll get a gradient vec2 e = vec2(0.0001, 0.0); /* -500.0 was added without comment, but it gives bump */ return normalize(n + -500.0 * vec3(map(pos + e.xyy) - map(pos - e.xyy), map(pos + e.yxy) - map(pos - e.yxy), map(pos + e.yyx) - map(pos - e.yyx))); } mat3 rotateY(float radians) { float s = sin(radians); float c = cos(radians); return mat3( // split c, 0.0, s, // 1 0.0, 1.0, 0.0, // 2 -s, 0.0, c); // 3 } vec3 DrawPlanet(vec2 pixel_coords, vec3 color, float planet_size, float atmosphere_thickness, float rotation_speed) { vec3 planet_color = vec3(1.0); // Get a nice big 2D circle and the distance to the edge. float d = sdfCircle(pixel_coords, planet_size); if (d <= 0.0) { // inside the planet. float x = pixel_coords.x / planet_size; float y = pixel_coords.y / planet_size; // surface area of a sphere is x^2 + y^2 + z^2 = 1, so... // z = 1 - x^2 - y^2 float z = sqrt(1.0 - x * x - y * y); // sping around; right round... mat3 planet_rotation = rotateY(frag_info.time * rotation_speed); // veiw space normal; vec3 view_normal = vec3(x, y, z); vec3 worldspace_position = planet_rotation * view_normal; vec3 worldspace_normal = planet_rotation * normalize(worldspace_position); vec3 wsViewDir = planet_rotation * vec3(0.0, 0.0, 1.0); vec3 noise_coord = worldspace_position * 2.0; float noise_sample = fbm6(noise_coord, 0.5, 2.0, 4.0); float moistureMap = fbm2(noise_coord * 0.5 + vec3(20.0), 0.5, 2.0, 4.0); vec3 shallowWaterColor = vec3(0.01, 0.09, 0.55); // light blue vec3 deepWater = vec3(0.09, 0.26, 0.57); vec3 waterColor = mix(shallowWaterColor, deepWater, smoothstep(0.02 /*deep*/, 0.06 /* shallow */, noise_sample)); vec3 coast_land = vec3(0.5, 1.0, 0.3); vec3 jungle_land = vec3(0.0, 0.7, 0.0); vec3 land_color = mix(coast_land, jungle_land, smoothstep(0.05, 0.1, noise_sample)); vec3 sandyColor = vec3(1.0, 1.0, 0.5); land_color = mix(sandyColor, land_color, smoothstep(0.05, 0.1, moistureMap)); // Put in some mountains and snow... vec3 mountainColor = vec3(0.5); land_color = mix(land_color, mountainColor, smoothstep(0.1, 0.2, noise_sample)); vec3 snowColor = vec3(1.0); land_color = mix(land_color, snowColor, smoothstep(0.15, 0.3, noise_sample)); // Take care of the poles... land_color = mix(land_color, vec3(0.9), smoothstep(0.6, 0.9, abs(view_normal.y))); planet_color = mix(waterColor, land_color, smoothstep(0.05, 0.06, noise_sample)); // Lighting // Check out the previous sections on lighting. // specularity of water vs land is different. float water_selector = smoothstep(0.05, 0.06, noise_sample); vec2 spec_params = mix(vec2(0.5, 32.0), // land vec2(0.01, 2.0), // sea water_selector); vec3 worldspace_light_direction = planet_rotation * normalize(vec3(0.5, 1.0, 0.5)); // update: make water flat - though from space we should see some waves // (to-do) vec3 worldspace_surface_normal = mix(worldspace_normal, calcNormal(noise_coord, worldspace_normal), water_selector); if (frag_info.show_normals > 0.0) { planet_color = worldspace_surface_normal; } float wrap = 0.05; // float dp = max(0.0, dot(worldspace_light_direction, // worldspace_surface_normal)); // dot product nvida surface scattering // trick float dp = max( 0.0, (dot(worldspace_light_direction, worldspace_surface_normal) + wrap) / (1.0 + wrap)); vec3 darkRed = vec3(0.25, 0.0, 0.0); vec3 lightColor = mix(darkRed, vec3(0.75), smoothstep(0.05, 0.5, dp)); vec3 ambient = vec3(0.002); // lets not have complete darkness vec3 diffuse = lightColor * dp; vec3 r = normalize( reflect(-worldspace_light_direction, worldspace_surface_normal)); float phongValue = max(0.0, dot(wsViewDir, r)); phongValue = pow(phongValue, spec_params.y); vec3 specular = vec3(phongValue) * spec_params.x * diffuse; vec3 planetShading = planet_color * (diffuse + ambient) + specular; planet_color = planetShading; // Fresnel for atmosphere float fresnel = smoothstep(1.0, 0.1, view_normal.z); // z == from camera. // "blue halo goes around the dark side of the planet // fresnel = pow(fresnel, 8.0); fresnel = pow(fresnel, 8.0) * dp; planet_color = mix(planet_color, vec3(0.0, 0.5, 1.0), fresnel); if (frag_info.show_noise > 0.0) { planet_color = vec3(noise_sample); } } // (color -> planet_color) when "d" is between 0 and -1 (inside the circle) color = mix(color, planet_color, smoothstep(0.0, -1.0, d)); // Atmospheric glow // 40 pixels outside the planet if (d < atmosphere_thickness && d >= -1.0) { // color = vec3(1.0); // draw the halo float planetSizeAtmos = planet_size + atmosphere_thickness; mat3 planet_rotation = rotateY(frag_info.time * rotation_speed); float x = pixel_coords.x / planetSizeAtmos; float y = pixel_coords.y / planetSizeAtmos; float z = sqrt(1.0 - x * x - y * y); vec3 normal = planet_rotation * vec3(x, y, z); float lighting = dot(normal, normalize(vec3(0.5, 1.0, 0.5))); lighting = smoothstep(-0.15, 1.0, lighting); // same as above; just no wrap. vec3 glow_color = vec3(0.05, 0.3, 0.9) * exp(-0.01 * d * d) * lighting * 0.75; color += glow_color; } return color; } void main() { vec2 vUvs = (gl_FragCoord.xy - 0.5) / frag_info.resolution; vUvs.y = 1.0 - vUvs.y; // flip flutter's upside down. vec2 pixel_coords = (vUvs - 0.5) * frag_info.resolution; vec3 color = vec3(0.0); color = GenerateStars(pixel_coords); color = DrawPlanet(pixel_coords, color, frag_info.planet_size, frag_info.planet_size * 0.1, frag_info.speed); frag_color = vec4(pow(color, vec3(1.0 / 2.2)), 1.0); }