0.00
60.0 fps

blackhole

enjoy. (you need to make some optimizations)

Log in to post a comment.

#version 300 es
precision highp float;

uniform float iTime;
uniform vec2 iResolution;
uniform vec4 iMouse;

in vec2 vScreen;
out vec4 fragColor;

const float PI = 3.14159265359;
const int ITERATIONS = 240;
const float INV_ITERATIONS = 0.004166666667;
const float FAR_DISTANCE = 15.0;
const vec3 MAIN_COLOR = vec3(1.0);
const vec3 WARM_LIGHT = vec3(1.0, 0.78, 0.42);

void mainImage(out vec4 outColor, in vec2 fragCoord);

void main()
{
    vec2 fragCoord = (vScreen * 0.5 + 0.5) * iResolution;
    mainImage(fragColor, fragCoord);
}

float saturate(float value)
{
    return clamp(value, 0.0, 1.0);
}

vec3 saturate(vec3 value)
{
    return clamp(value, vec3(0.0), vec3(1.0));
}

float hash31(vec3 value)
{
    return fract(sin(dot(value, vec3(127.1, 311.7, 74.7))) * 43758.5453123);
}

float rand(vec2 coord)
{
    return fract(sin(dot(coord, vec2(12.9898, 78.233))) * 43758.5453);
}

float noise(vec3 point)
{
    vec3 cell = floor(point);
    vec3 local = fract(point);
    local = local * local * (3.0 - 2.0 * local);

    float n000 = hash31(cell + vec3(0.0, 0.0, 0.0));
    float n100 = hash31(cell + vec3(1.0, 0.0, 0.0));
    float n010 = hash31(cell + vec3(0.0, 1.0, 0.0));
    float n110 = hash31(cell + vec3(1.0, 1.0, 0.0));
    float n001 = hash31(cell + vec3(0.0, 0.0, 1.0));
    float n101 = hash31(cell + vec3(1.0, 0.0, 1.0));
    float n011 = hash31(cell + vec3(0.0, 1.0, 1.0));
    float n111 = hash31(cell + vec3(1.0, 1.0, 1.0));

    float x00 = mix(n000, n100, local.x);
    float x10 = mix(n010, n110, local.x);
    float x01 = mix(n001, n101, local.x);
    float x11 = mix(n011, n111, local.x);
    float y0 = mix(x00, x10, local.y);
    float y1 = mix(x01, x11, local.y);

    return mix(y0, y1, local.z) * 2.0 - 1.0;
}

float fbm(vec3 point)
{
    float amplitude = 0.52;
    float sum = 0.0;
    float normalizer = 0.0;

    for (int i = 0; i < 5; i++)
    {
        sum += (noise(point) * 0.5 + 0.5) * amplitude;
        normalizer += amplitude;
        point = point * 2.03 + vec3(11.7, 5.3, 17.1);
        amplitude *= 0.5;
    }

    return sum / normalizer;
}

float discCurve(float x)
{
    float x2 = x * x;
    float x4 = x2 * x2;
    return x4 * (1.0 - x) * (8.8 + 2.4 * x);
}

float sdTorus(vec3 point, vec2 torus)
{
    vec2 q = vec2(length(point.xz) - torus.x, point.y);
    return length(q) - torus.y;
}

vec3 rotate(vec3 point, float x, float y, float z)
{
    mat3 matX = mat3(
        1.0, 0.0, 0.0,
        0.0, cos(x), sin(x),
        0.0, -sin(x), cos(x)
    );
    mat3 matY = mat3(
        cos(y), 0.0, -sin(y),
        0.0, 1.0, 0.0,
        sin(y), 0.0, cos(y)
    );
    mat3 matZ = mat3(
        cos(z), sin(z), 0.0,
        -sin(z), cos(z), 0.0,
        0.0, 0.0, 1.0
    );

    return matY * matZ * matX * point;
}

vec3 starField(vec3 direction)
{
    vec3 p = normalize(direction);
    vec2 uv = vec2(atan(p.x, p.z) / (2.0 * PI) + 0.5, asin(p.y) / PI + 0.5);
    vec2 grid = floor(uv * vec2(900.0, 450.0));
    float cell = rand(grid);
    float star = smoothstep(0.997, 1.0, cell);
    float twinkle = 0.55 + 0.45 * sin(iTime * (1.5 + cell * 5.0) + cell * 30.0);
    vec3 color = mix(vec3(0.82, 0.88, 1.0), vec3(1.0, 0.86, 0.62), rand(grid + 13.0));

    return color * star * twinkle * 0.45;
}

void rotateCamera(inout vec3 rayDir, inout vec3 cameraPos)
{
    vec2 mouse = iMouse.xy / max(iResolution.xy, vec2(1.0));
    if (iMouse.z <= 0.0)
    {
        mouse = vec2(0.35, 0.35);
    }

    vec3 angle = vec3(mouse.y * 0.05 + 0.05, 1.0 + mouse.x, -0.45);
    rayDir = rotate(rayDir, angle.x, angle.y, angle.z);
    cameraPos = rotate(cameraPos, angle.x, angle.y, angle.z);
}

void warpSpace(inout vec3 rayDir, vec3 rayPos)
{
    float distSq = dot(rayPos, rayPos);
    float warpFactor = 1.0 / (distSq + 0.000001);
    vec3 singularityVector = normalize(-rayPos);
    float warpAmount = 5.0;

    rayDir = normalize(rayDir + singularityVector * warpFactor * warpAmount * INV_ITERATIONS);
}

void addHaze(inout vec3 color, vec3 rayPos, float alpha)
{
    float torusDist = abs(sdTorus(rayPos + vec3(0.0, -0.05, 0.0), vec2(1.0, 0.01)));
    float torusDistSq = torusDist * torusDist;
    float rayLength = length(rayPos);
    float sharpBloom = 1.0 / (torusDistSq + 0.001);
    float softBloom = 1.0 / (torusDistSq * 0.22 + 0.018);
    float wideBloom = 1.0 / (torusDistSq * 0.045 + 0.09);
    float haloDist = abs(rayLength - 1.05);
    float photonHalo = 1.0 / (haloDist * haloDist * 28.0 + abs(rayPos.y) * 14.0 + 1.0);
    float occlusion = rayLength < 0.5 ? 0.0 : 1.0;

    vec3 bloomColor = MAIN_COLOR * sharpBloom * 2.15;
    bloomColor += WARM_LIGHT * softBloom * 0.48;
    bloomColor += vec3(1.0, 0.68, 0.28) * wideBloom * 0.13;
    bloomColor += vec3(1.0, 0.86, 0.58) * photonHalo * 0.7;

    color += bloomColor * occlusion * (2.9 * INV_ITERATIONS) * (1.0 - alpha);
}

void addGasDisc(inout vec3 color, inout float alpha, vec3 rayPos, vec3 rayDir)
{
    float discRadius = 3.2;
    float discWidth = 5.3;
    float discInner = discRadius - discWidth * 0.5;
    float distFromCenter = length(rayPos);
    float distFromDisc = rayPos.y;
    float radialGradient = 1.0 - saturate((distFromCenter - discInner) / discWidth * 0.5);
    float discMask = discCurve(radialGradient);
    float coverage = discMask;
    float discThickness = 0.1 * radialGradient;

    coverage *= saturate(1.0 - abs(distFromDisc) / max(discThickness, 0.0001));

    float fadeBase = abs(distFromCenter - discInner) + 0.4;
    float fadeSq = fadeBase * fadeBase;
    float fade = fadeSq * fadeSq * 0.04;
    float bloomFactor = 1.0 / (distFromDisc * distFromDisc * 40.0 + fade + 0.00002);
    float inverseGradient = 1.0 - radialGradient;
    float dustGlow = 1.0 / (inverseGradient * inverseGradient * 290.0 + 0.002);

    vec3 hotCore = WARM_LIGHT * dustGlow * 8.2;
    float radialGradientSq = radialGradient * radialGradient;
    vec3 bloom = MAIN_COLOR * bloomFactor * sqrt(bloomFactor);
    bloom *= mix(vec3(1.65, 0.98, 0.62), vec3(1.0, 0.84, 0.54), vec3(radialGradientSq));
    bloom *= mix(vec3(1.7, 0.48, 0.09), vec3(1.05, 0.92, 0.72), vec3(sqrt(radialGradient)));

    vec3 dustColor = mix(hotCore, bloom * 150.0, saturate(1.0 - coverage));
    coverage = saturate(coverage * 0.7 + bloomFactor * bloomFactor * 0.1);
    if (coverage < 0.01)
    {
        return;
    }

    vec3 radialCoords;
    radialCoords.x = distFromCenter * 1.425 + 0.55;
    radialCoords.y = atan(-rayPos.x, -rayPos.z) * 1.425;
    radialCoords.z = distFromDisc * 1.425;

    float speed = 0.06;
    vec3 coords = radialCoords;
    coords.y += iTime * speed;
    float structure = fbm(coords * 3.0);
    coords.y -= iTime * speed * 2.0;
    structure *= fbm(coords * 6.0);
    coords.y += iTime * speed * 2.0;
    structure *= fbm(coords * 12.0);

    vec3 fineCoords = radialCoords + vec3(30.0);
    fineCoords.y -= iTime * speed;
    float fineDust = fbm(fineCoords * 8.0);
    fineDust *= fbm(fineCoords * 20.0 + vec3(0.0, iTime * speed, 0.0));

    float spiral = sin(radialCoords.y * 7.0 - distFromCenter * 5.0 + iTime * 0.35) * 0.5 + 0.5;
    float filament = smoothstep(0.18, 0.95, structure * 0.72 + spiral * 0.28);

    dustColor *= 0.05 + filament * 1.95;
    coverage *= fineDust * discMask;
    coverage = saturate(coverage * 1200.0 * INV_ITERATIONS);

    vec3 orbitalTangent = normalize(vec3(-rayPos.z, 0.0, rayPos.x));
    float doppler = saturate(dot(rayDir, orbitalTangent) * 0.5 + 0.5);
    vec3 recedingColor = vec3(1.25, 0.44, 0.10);
    vec3 approachingColor = vec3(1.7, 1.25, 0.78);
    float dopplerCurve = doppler * sqrt(doppler);
    dustColor *= mix(recedingColor, approachingColor, dopplerCurve);
    dustColor *= 0.78 + doppler * doppler * 0.72;

    color = (1.0 - alpha) * max(dustColor, vec3(0.0)) * coverage + color;
    alpha = (1.0 - alpha) * coverage + alpha;
}

vec3 toneMap(vec3 color)
{
    color *= 230.0;
    color += max(color - 0.012, vec3(0.0)) * 0.32;
    color = pow(color, vec3(1.42));
    color = color / (1.0 + color);
    color = pow(color, vec3(1.0 / 1.42));
    color = color * color * (3.0 - 2.0 * color);
    color = pow(color, vec3(1.2, 1.12, 1.04));
    color = saturate(color * 1.01);

    return pow(color, vec3(0.7 / 2.2));
}

void mainImage(out vec4 outColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;
    float aspect = iResolution.x / iResolution.y;
    vec2 jitter = vec2(rand(uv + sin(iTime)), rand(uv + 17.0 + sin(iTime))) / iResolution.xy;
    vec2 eyeUv = uv + jitter;

    vec3 rayDir = normalize(vec3((eyeUv * 2.0 - 1.0) * vec2(aspect, 1.0), 6.0));
    vec3 cameraPos = vec3(0.0, 0.0, -10.0);
    vec2 mouse = iMouse.xy / max(iResolution.xy, vec2(1.0));
    cameraPos.x += (iMouse.z > 0.0 ? mouse.x : 0.35) * 3.0 - 1.5;

    rotateCamera(rayDir, cameraPos);

    vec3 color = starField(rayDir) * 0.00035;
    float alpha = 0.0;
    float dither = rand(uv + sin(iTime)) * 2.0;
    vec3 rayPos = cameraPos + rayDir * dither * FAR_DISTANCE * INV_ITERATIONS;

    for (int i = 0; i < ITERATIONS; i++)
    {
        warpSpace(rayDir, rayPos);
        rayPos += rayDir * FAR_DISTANCE * INV_ITERATIONS;

        float singularity = length(rayPos);
        if (singularity < 0.42)
        {
            color *= smoothstep(0.1, 0.42, singularity);
            alpha = 1.0;
            break;
        }

        addGasDisc(color, alpha, rayPos, rayDir);
        addHaze(color, rayPos, alpha);
    }

    float ring = abs(sdTorus(rayPos, vec2(1.0, 0.02)));
    color += vec3(1.0, 0.83, 0.55) * 0.00008 / (ring * ring + 0.003);

    vec2 centeredUv = (uv * 2.0 - 1.0) * vec2(aspect, 1.0);
    float screenRadius = length(centeredUv);
    float lensRing = abs(screenRadius - 0.19);
    float lensBloom = 0.0000018 / (lensRing * lensRing + 0.00045);
    lensBloom += 0.0000009 / (screenRadius * screenRadius + 0.08);
    color += vec3(1.0, 0.74, 0.36) * lensBloom * (1.0 - alpha * 0.35);

    outColor = vec4(toneMap(color * 0.0001), 1.0);
}