blob: 2e687e415dc931feca6b16eaeeeb151c97c47c44 [file] [log] [blame]
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);
}