Blackhole Shader in Unity

This is a quick tutorial on creating a “blackhole” shader effect in Unity. This tutorial uses Shader Forge (a popular visual shader scripting tool), which I highly recommend, but otherwise the mapping between the nodes and the code is very straightforward, so it could be a good exercise to try and create this shader in code as you follow along.

Blackhole shader in motion.

In a new scene, I first set a different skybox so that the distortion effect will be more visible. Then, I create a new “Unlit” shader with Shader Forge.

Initial setup.

In the editor screen, we then see a single Color node feeding in to the Emission input. The Emission controls the output color regardless of the lighting.

The Fresnel Node

Remove the Color node, and add in a new Fresnel node (you can hold the letter “F” for “Fresnel” and select the node from the menu that pops up).

The Fresnel node.

The Fresnel node returns a value of 1 when the surface normal is perpendicular to the view direction, and 0 when the surface normal is parallel to the view direction (facing the camera).

The Fresnel effect can also be achieved by taking one minus the dot product of the normal vector and the view vector. Similarly, the length of the cross product of these vectors can also be used (though it will produce a slightly different result because of floating-point precision).

This Fresnel node will be used for: (1) creating the refraction effect, and (2) creating a multiplicative mask for the “hole” part.

Let’s start with the refraction effect.

The Refraction Effect

To create the refraction we will be using the Scene Color node to get the image of the rendered scene behind our object.

Plugging the Scene Color node into the Emission makes our mesh disappear! It’s still there, though now it has the same color as the scene behind it so it’s perfectly camouflaged.

The Scene Color can be distorted by displacing the scene coordinates (UVs) toward the center of our sphere. To get these displacement amounts we can utilize the surface normal vectors.

Normal-based displacement values.

To get the displacement values, we will first negate the normals so that they point inward. Next, the normals must be transformed from Local space to View space (relative to the Camera). Otherwise you will get different displacement amounts as you rotate the object. For example, we want a normal vector pointing to the left of the camera to have an X value of -1, rather than the value it would have relative to the object. The last step is to mask out just the X and Y values (as R and G in the component mask), which will be used to displace the U and V scene coordinates.

Adding the displacement values straight to the Scene UVs.

What we need to do next is to control the amount of displacement using the Fresnel node. We do this by multiplying the displacement values with the Fresnel output before adding them to the Scene UVs.

Displacement values multiplied with the Fresnel node output.
The result of the above setup.

This is close, but there should be a soft transition into the refraction instead of the hard edge we’re getting. What we need to do is to have the refraction start out strong in the center and fade out toward the edges. This can be done by first inverting the Fresnel values with the One Minus node, and then raising them to a Power. This has the effect of pushing the black area inward more.

Calculation for Fresnel values which will control the refraction.
Final refraction effect result.

The Black Hole

The next step is to mask out the black hole area, which is relatively simple. First, we’re just going to multiply the Fresnel values by some amount (controllable by a Slider), then Round these values to 0 or 1 so that a hard edge is formed.

As it turns out, the result has values higher than 1 (floating-point math), so we need to clamp the output between 0 and 1.

All Together

To compose the full effect, we just need to multiply the black hole mask with the distorted Scene Color output.

Final setup.

This produces the final result.

Notes

  • As I mentioned previously, the displacement calculation when added to the Scene UVs without modification has a similar effect to flipping the Scene UVs (except a bit more extreme). A smoother distortion can be achieved by simply remapping a copy of the the Scene UVs to create the displacement amounts.
Alternative displacement value calculation.
  • The black hole mask is dependent on the Fresnel effect, it may be better to use a separate Fresnel node, or some other method so that the hole part isn’t changed if you only want to modify the refraction.
  • The shader depends on a using a sphere mesh. You can apply this to other objects, the best ones will be convex with small faces. Using the calculation from the first note item, and some distance-based calculations instead of Fresnel, you could probably use a billboarded circle mesh or quad instead.
  • Objects in front of the blackhole will be incorrectly distorted as well. This is because the blackhole will be rendered after the opaque objects. In other words, an object in front of the blackhole will be part of the Scene Color, which we are assuming to be behind the blackhole. I can think of no simple solution for this, you just need to be very careful of what angles the player will view the object.
Incorrect distortions.

That’s all, thanks for reading!