#version 300 es precision highp float; uniform vec3 iResolution; uniform float iTime; uniform sampler2D iChannel1; out vec4 FragColor; const float PI = 3.1415927f; float sdTorus(vec3 p, vec2 t) { vec2 q = vec2(length(p.xz) - t.x, p.y); return length(q) - t.y; } void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = (fragCoord.xy / iResolution.xy) * 2.0f - 1.0f; uv.x *= iResolution.x / iResolution.y; // camera vec3 lookAt = vec3(0.0f, -0.1f, 0.0f); float eyer = 2.0f; float eyea = 0.0f; float eyea2 = -0.24f * PI * 2.0f; vec3 ro = vec3(eyer * cos(eyea) * sin(eyea2), eyer * cos(eyea2), eyer * sin(eyea) * sin(eyea2)); vec3 front = normalize(lookAt - ro); vec3 left = normalize(cross(normalize(vec3(0.0f, 1.0f, -0.1f)), front)); vec3 up = normalize(cross(front, left)); vec3 rd = normalize(front * 1.5f + left * uv.x + up * uv.y); // black hole const vec3 bh = vec3(0.0f); const float bhr = 0.1f; float bhmass = 5.0f * 0.001f; // premul G // integration vec3 p = ro; vec3 pv = rd; const float dt = 0.02f; vec3 col = vec3(0.0f); float noncaptured = 1.0f; vec3 c1 = vec3(0.5f, 0.46f, 0.4f); vec3 c2 = vec3(1.0f, 0.8f, 0.6f); // fixed iteration count, early-out when captured const int MAX_STEPS = 175; // original ~200 (1 / 0.005) for(int i = 0; i < MAX_STEPS; ++i) { if(noncaptured <= 0.0001f) break; p += pv * (dt * noncaptured); // gravity: compute bhv and reuse vec3 bhv = bh - p; float r2 = dot(bhv, bhv) + 1e-6f; // prevent div0 float invLen = inversesqrt(r2); // normalize(bhv) = bhv * invLen // acceleration magnitude = bhmass / r2 // combine: accel = normalize(bhv) * (bhmass / r2) pv += bhv * (invLen * (bhmass / r2)); // capture factor (reuse distance) float dist = sqrt(r2); noncaptured = smoothstep(0.0f, 0.666f, dist - bhr); // accretion disc texture float dr = length(bhv.xz); float da = atan(bhv.x, bhv.z); // keep same ordering as original // compact texture coordinate generation: float angleScale = 0.01f + (dr - bhr) * 0.002f; vec2 ra = vec2(dr * 10.0f, (da * angleScale + 2.0f * PI + iTime * 0.005f) * 20.0f); // sample lower frequency (reduce texture cost / cache pressure) float tx = texture(iChannel1, ra * vec2(0.1f, 0.5f)).r; float radial = max(0.0f, tx + 0.05f); float falloff = 4.0f / (0.001f + (dr - bhr) * 50.0f); // color mix: avoid pow by squaring float sq = (dr - bhr); sq = sq * sq; vec3 dcol = mix(c2, c1, sq) * radial * falloff; // torus SDF: scale p once vec3 ps = p * vec3(1.0f, 25.0f, 1.0f) - bh; float tor = sdTorus(ps, vec2(0.8f, 0.99f)); float torMask = smoothstep(0.0f, 1.0f, -tor); col += max(vec3(0.0f), dcol * torMask * noncaptured); // simple brightening term (reuse r2) col += vec3(1.0f, 0.9f, 0.85f) * (1.0f / r2) * 0.0033f * noncaptured; } fragColor = vec4(col, 1.0f); } void main() { mainImage(FragColor, gl_FragCoord.xy); }