Sphere Shader
Make a plane look like a sphere
This is how I can use a shader to make a quad look like a sphere. This is cheaper to render in many cases, and can be indistinguishable from a real sphere.

The shader calculates tangent space normals on the plane which make it look similar to a real 3D sphere.
Drawbacks
I like seeing where a technique fails, which can help explain how it works as well, so let’s break it! 😁




Benefits
So what are the positive aspects of the technique?
- It’s cheaper to render than a sphere of similar roundness built using more geometry.
- It looks perfectly round.
Use cases
With real-time graphics, I need to balance several factors against each other to find a good use case. To do that, I’ll list some issues and potential solutions:
- Being too close to lights looks weird.
- Keep point lights away.
- Intersecting with other objects looks weird.
- Ensure it doesn’t intersect with anything.
- Receiving shadows looks weird.
- Disable receiving shadows on the object?
A potential solution to all these issues is also to keep the object small or moving, this makes the drawbacks harder to notice. Maybe it’s useful for water droplets in a particle effect, for instance.
Another potential solution is to develop the technique further to make the 3D position of the fragments correct. I have a link to a technique that does this at the end of the post.
It could also work well for distant objects where it covers a small space on the screen, or I can control the lighting conditions and what geometry it intersects with. Maybe it could be used as the last level of detail for some object?
It could work better if the game has certain properties. For instance, if the game doesn’t use point lights, or if the game is 2D, or it doesn’t use shadow maps.
Implementation
This shader is made to work with the default quad in Godot. If I’m using something else, I may need to make some changes. For the effect to work, the quad must always face the camera. I’ll include code for this in the shader as well.
Theory
The amount that the normal faces to the right or up increases linearly with the x and y coordinates. This means I already have my x and y components in the UVs!
That leaves me two things to figure out:
- The Z component.
- This can be calculated from the UV coordinates.
- Discarding fragments in a circle.
- I get what I need for this when calculating the Z component.
Let’s look at an implementation.
Implementation
This implementation is written in Godot’s shading language, which is very similar to GLSL, which is fairly similar to HLSL. The shader is made to be applied to a standard Godot quad, which I get by creating a MeshInstance3D
, and setting its Mesh
to be a New QuadMesh
.
shader_type spatial;
// We use the vertex shader to make the quad face the camera.
// This doesn't have very detailed comments because it isn't the focus.
void vertex() {
// Prepare our directions.
vec3 plane_to_cam = normalize(CAMERA_POSITION_WORLD - NODE_POSITION_WORLD);
const vec3 WORLD_UP = vec3(0, 1, 0);
vec3 plane_right = normalize(cross(WORLD_UP, plane_to_cam));
vec3 plane_up = cross(plane_to_cam, plane_right);
// Build the rotation matrix.
mat4 billboard = mat4(
vec4(plane_right, 0.0),
vec4(plane_up, 0.0),
vec4(plane_to_cam, 0.0),
MODEL_MATRIX[3]
);
// Apply the matrix.
MODELVIEW_MATRIX = VIEW_MATRIX * billboard;
}
// This is where the sphere illusion is created.
void fragment() {
// Bring input UVs into the -1 to 1 range.
vec2 uv = UV * 2.0 - 1.0;
// Do the dot product separately so I can branch on the result
// and reuse it later.
float uv_dot = dot(uv, uv);
// If the dot product is over 1, it's outside of the circle.
if(uv_dot > 1.0) {
discard;
}
// Calculate the Z value from the dot product.
float z = sqrt(1.0 - uv_dot);
// Flip UV.y because that's what Godot wants for tangent space normals.
uv.y *= -1.0;
// Set the normal.
NORMAL.rgb = vec3(uv, z);
}
If the original plane is entirely outside the view, it will be frustum-culled even though our rotated plane is still inside the view, causing it to pop in and out of existence in some cases. We can solve this by setting GeometryInstance3D
-> Geometry
-> Custom AABB
to these values:
- Set
x
,y
, andz
to-0.5
. - Set
w
,h
, andd
to1
.
These values will only work for uniformly scaled planes. If I want non-uniform scaling, more work needs to be done, but I’m not covering that in this post.
More spheres
This post showed the basics of using shaders to make a plane look like a sphere. If I want to go deeper, check out these posts:
- Diablo 3 — Resource Bubbles by Simon Trümpler is a treasure trove for sphere rendering.
- Rendering a Sphere on a Quad by Ben Golus uses more advanced techniques to create a sphere that doesn’t suffer from the drawbacks of the technique in this post.