A = -100

B = 0.5

See the full code in action here. My first
two experiments with gravitational lensing were: Lensing,
where I put a gravitational lensing effect on top of someone else's star field; and Cosmos,
which features two simple FBM-based starfields. Both of them used a simple and arbitrary trick. This lensing
method, which is more apparent in Cosmic Storm
because of the background, rotates the ray direction by the correct
angle of deflection as it passes the blackhole. This means that the effect isn't perfect because the
path of the ray is a corner, not a curve.

This shader uses volumetric rendering, meaning rays are fired from a virtual camera through each pixel and as
they pass through space they accumulate color. In my last post about the Hopf
Fibration I mentioned that it would be really cool to learn about fluid dynamics because there is a
certain solution to the Navier-Stokes equations whose flow lines are the projected circles of the Hopf
fibration. I haven't really learned very much about fluid dynamics, but I got inspired by this shader's technique for rendering a 2D vector field.
It uses Euler's method or the Runge-Kutta method to approximate the flow of an individual point under the
velocity field. It samples a texture at two points along the flow and interpolates between the two colors based
on time to make it look like the texture is moving. You can tell something similar is being used here because
if you don't move the camera and look carefully you'll see that the image is periodic. Here's my 3D version of
the color interpolation in GLSL:

```
vec3 interpolateColor(vec3 p){
```

float t1 = fract(0.5*time);

float t2 = fract(t1 + 0.5);

vec3 c1 = makeColor(approxFlow(p, t1 + 0.3));

vec3 c2 = makeColor(approxFlow(p, t2 + 0.3));

t1 = 2.0*abs(t1 - 0.5);

return mix(c1, c2, t1);

}

where `t1`

and `t2`

define the points in time where the flow will be approximated. ```
approxFlow(vec3
p, float t)
```

approximates the flow of point `p`

at time `t`

; it uses 5
iterations of the Runge-Kutta method. `t1`

is set to `2.0*abs(t1 - 0.5)`

before linearly
interpolating so that the color at `t2`

fades in as the color at `t1`

fades out and vice
versa. `makeColor(vec3 p)`

makes a color based on the pressure and density of the solution to the
Navier-Stokes equations. So here is the solution:
$$v(x,y,z) = A \left(a^2+x^2+y^2+z^2\right)^{-2} \left( 2(-ay+xz), 2(ax+yz) , a^2-x^2-y^2+z^2 \right),$$
$$p(x,y,z) = -A^2B \left(a^2+x^2+y^2+z^2\right)^{-3},$$
$$\rho(x,y,z) = 3B\left(a^2+x^2+y^2+z^2\right)^{-1}$$
where $v$ is velocity, $p$ is pressure, $\rho$ is density, $a$ is the distance to the unit circle in the $xy$
plane, and $A$ and $B$ are the arbitrary constants which you can manipulate using the sliders above!

`makeColor`

squishes pressure and density into the range $(0,\,1)$ with a logistic curve. It then
calculates the hue of the color from the pressure, high pressure meaning more blue and lower pressure meaning
more red, the reasoning being that high pressure corresponds to higher temperatures which in turn corresponds
to higher frequency light being emitted from whatever gas is swirling around this blackhole. The brightness
of the color is the based off of the density, with the reasoning being that regions with more atoms will emit
more light.

This was a fun technique to experiment with, but it makes me more curious about the physics behind fluids and
how fluid simulations are actually carried out.