A surface is transparent if we can see other surfaces through it. Since surfaces that lie behind it may contribute to its color, transparency is a global lighting feature: Interaction of light between objects must be taken into account. 1
In the real world, transparent objects are often refractive, meaning that light changes direction as it passes into or through the object. Accounting for refraction is computationally expensive, so real-time systems assume non-refractive transparency. They assume light moves in straight lines without changing direction.
A non-transparent object is said to be opaque. Rendering a scene of opaque surfaces is straightforward. The standard method is depth-buffering, also known as z-buffering. Surface depths — distance from the viewer to the surface — are maintained in a depth buffer of the same shape as the color buffer whose pixels store the images we see. The depth buffer is initialized to maximum depth, that of the background. Triangles making up the surfaces in a scene are processed in any order. To process a triangle, we paint its fragments into the color buffer conditionally: A fragment f is painted to the pixel p it covers just if f has less depth (is closer to the viewer) than the surface whose color appears in pixel p. When every triangle has been processed, the color buffer displays the surfaces visible to the viewer and the depth buffer stores their depth, pixel by pixel.
This method works for opaque surfaces, but not when some of the surfaces are transparent. Suppose a transparent sphere lies in front of an opaque box, so that the sphere’s depth is less than the box’s depth. When rendered, we should see the box through the sphere. Suppose the sphere’s triangles are processed first. When this is completed, the color buffer will display the sphere and the depth buffer will store its (small) depth values. Next the box’s triangles are processed. However, they would not be painted into the color buffer at all because their depth values are greater than those of the sphere which are already stored in the depth buffer. We would not see the box through the sphere.
One solution is to process the opaque surfaces first, followed by the transparent surfaces. The opaque surfaces provide an opaque ‘background’ image onto which the transparent surfaces are overlaid. Only those transparent surfaces that are closer to the viewer than the opaque surfaces contribute.
Start by processing the opaque surfaces using standard depth buffering. At this point, the color buffer displays the visible opaque objects and the depth buffer stores their depths, pixel by pixel. Next, process the transparent surfaces ordered from far to near (that is, by decreasing depth). The surface triangles are conditionally blended with the values stored in the color buffer: For each fragment f of the current triangle, if the depth of f is less than the depth of the pixel p it covers, blend the color of f with the color stored at p and write this new color into pixel p. 2 As transparent surfaces are processed far to near, successive overlays accumulate on the opaque surfaces.
The following program displays eight transparent cubes whose opacity is set with the slider. The material dropdown selects the side property of the cubes’ material. Using FrontSide, only outward-facing sides of faces are rendered, whereas inward-facing faces (which face the cubes’ interior) are discarded. The cubes appear more like solids. Using DoubleSide, both sides of the faces are rendered, so it appears that light has its effect both as it enters and exits each cube’s surface, and so the cubes appear rather to have empty interiors.
Threejs materials have a transparent property whose Boolean value indicates whether the mesh it belongs to is transparent. If transparent is true, the material’s opacity property determines how transparent it is; if false, opacity is ignored. Transparency is expensive, so transparent is false by default, requiring the program to set it explicitly if desired. In my program, setting the opaque checkbox makes a random box opaque by clearing its material’s transparent property.
- Strictly speaking, a surface that is truly transparent would be invisible, so we use transparent to refer to a surface that is semi-transparent or translucent. ↩
- Suppose fragment f has color and opacity , where means is opaque and means is completely transparent. To blend with the color of the pixel it covers, the pixel’s new color becomes The new color is an average of the fragment’s color weighted by its opacity and the pixel’s current color weighted by the fragment’s transparency. The greater the fragment’s opacity, the more its own color shows and the less the current color shows through. ↩