Build the effect with a WebGL2 canvas and a GLSL ES 3.00 fragment shader: render a dark center, warp a star field or sky around it, and add an optional glowing accretion disc. The coordinate warp described here is a visual approximation—not a validated simulation of light moving through a black hole’s gravitational field.
What the shader needs to draw
A fragment shader runs for each pixel in the rendered surface and supplies that pixel’s color. That makes it possible to create a procedural effect without first building a textured 3D scene. A practical black-hole effect combines three visual elements:
- The shadow: a dark central region.
- Lensing-like distortion: a remapping of the background coordinates around the shadow.
- Supporting detail: a star field or sky, with an optional bright accretion disc.
For a reference implementation, Eric Bruneton’s black_hole_shader describes a WebGL2 shader for a non-rotating black hole with an accretion disc and background stars. Its repository identifies a BSD license; review the license terms and credit the project if you reuse its code.
Set up a WebGL2 canvas and shader program
Request a webgl2 context, then draw a fullscreen surface using two triangles or an equivalent fullscreen primitive. Link a vertex shader to a fragment shader. In WebGL2, the shader source uses GLSL ES 3.00 syntax, including #version 300 es; the fragment shader should declare its precision and an explicit color output.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl2");
if (!gl) {
document.body.textContent = "WebGL2 is unavailable in this browser.";
}
This checks context creation only; you still need to compile both shaders, check their compile status, link the program, and check the link status before drawing. WebGL2 accepts GLSL ES 3.00 shaders with restrictions and is not entirely backwards-compatible with WebGL1. See the Khronos WebGL 2.0 Specification and the WebGL2 Fundamentals fullscreen-shader example.
Pass the values the fragment shader needs
Use uniforms for changing values such as canvas resolution, elapsed time, and the black-hole center. If the background is an image or rendered sky texture, pass it as a texture and sample it in the fragment shader with a sampler2D and texture. A procedural star field can instead be computed in the shader. The WebGL2 Fundamentals guide to shaders and GLSL covers fragment outputs, uniforms, and texture sampling.
Map pixels around the black-hole center
Convert each fragment’s pixel coordinates into centered coordinates, correcting for the canvas aspect ratio so a circular shadow does not become an ellipse on a wide screen. Subtract the chosen center point, then calculate the radial distance and angle from the center. These values provide a convenient basis for deciding whether a pixel falls inside the shadow, near the disc, or in the surrounding background.
There is no single radius or coordinate formula that fits every composition. Tune the center and shadow radius against the rendered canvas, and keep the resolution and aspect-ratio correction in sync when the canvas is resized.
Draw the shadow and warp the background
Use a radial threshold to make the interior dark. For pixels outside that region, sample the background at remapped coordinates: change the sample position as a function of distance from the center so stars or other background features bend around the shadow. A smooth falloff can keep the transition from looking like a hard circular boundary.
This coordinate remapping can suggest gravitational lensing, but it does not by itself calculate the path of light through curved spacetime. Describe and tune it as an artistic approximation unless the shader implements the relevant light-ray equations and its result is validated.
Rank #4
Add an optional accretion disc and stars
Accretion disc
Make the disc a separate component from the shadow and background warp. Define a thin band around the center, then shade it with a radial gradient and procedural variation. You can animate that variation with the time uniform if motion improves the appearance; keep the disc’s shape and color treatment independent enough to adjust without changing the shadow.
Bruneton’s WebGL2 black-hole shader is a concrete reference for a disc-and-stars composition. Its existence does not validate any particular simplified disc equation.
Best Value
Star field or sky
Use either a sampled sky texture or a procedural star field as the background that the warp remaps. Stars give the distortion clear visual reference points; a smooth background can make the same warp harder to see.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Choose between a visual effect and a geodesic-based renderer
The right approach depends on whether the goal is a stylized visual or a more physically motivated rendering. These projects demonstrate different methods, not a controlled performance comparison.
| Approach | What it establishes | Complexity and portability | Performance evidence |
|---|---|---|---|
| Stylized coordinate warp | A dark region, remapped background, and optional procedural disc or stars; suitable for an artistic effect. | Can be implemented in a WebGL2 fragment shader with a fullscreen surface. Use WebGL2 and GLSL ES 3.00 syntax. | No benchmark is established; measure in the target browser and GPU environment. |
| Schwarzschild geodesic integration | The oseiskar black-hole project describes integrating an ODE for Schwarzschild geodesics in GLSL on the GPU, using WebGL and three.js to compute light-ray paths. | Implements a more physically motivated approach and uses WebGL with three.js; expect more involved modeling than a simple coordinate remap. | No controlled benchmark or performance guarantee is established. |
Resize, test, and debug the effect
- Check context creation: confirm that
getContext("webgl2")returns a context before using WebGL methods. - Read shader and program logs: inspect compile and link status and their associated logs when rendering fails.
- Verify uniforms: check uniform locations and confirm that resolution, time, and center values are updated as intended.
- Inspect coordinate normalization: confirm that centered coordinates use the current canvas size and aspect ratio.
- Test resizing and target devices: update the canvas drawing size and resolution uniform, then check the appearance and GPU cost in the browsers and hardware you intend to support.
- Use WebGL2 syntax: do not assume WebGL1 shader code or desktop GLSL will work unchanged.
For a deeper explanation of fragment outputs and GLSL in WebGL2, consult WebGL2 Fundamentals.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

