In standard OpenGL ES rendering, objects outside the clip volume are discarded before reaching the fragment shader. This includes primitives that lie outside the near and far depth planes defined by the projection matrix. However, what if you need to render objects that are beyond the far plane without having them clipped? In desktop OpenGL, the GL_DEPTH_CLAMP
feature exists for this very purpose. Unfortunately, OpenGL ES does not support this functionality natively.
The Problem
Suppose your scene uses a standard perspective projection with a near plane at 0.1 and a far plane at 10.0. Now imagine an object placed far beyond the far plane at z = 100. Normally, this object would be clipped and discarded because its depth lies outside the acceptable clip-space range.
This poses a problem for certain applications such as:
- Visualizing overlays that should always render regardless of camera distance.
- Rendering special effects or faraway markers.
- Doing post-processing tricks that rely on drawing geometry beyond the typical depth range.
The Solution: Clamp gl_Position.z
A simple workaround involves manually clamping the depth component of the vertex position in clip space, like so:
void main() {
vec4 pos = ModelViewProjectionMatrix * vec4(attr_Vertex, 1.0);
pos.z = clamp(pos.z, -pos.w, pos.w); // Clamp z to avoid near/far clipping
gl_Position = pos;
}
This line ensures that the clip-space z-value always remains within the [-w, w]
range, which corresponds to the NDC z-range of [0, 1] after the perspective divide in OpenGL ES.
Why It Works
During the rasterization stage, OpenGL ES performs clipping in clip space before perspective division. If the z-value of gl_Position
is outside [-w, w]
, the primitive gets clipped. By clamping z
to [-w, w]
, we ensure the vertex always survives this stage, effectively mimicking what GL_DEPTH_CLAMP
does in desktop OpenGL.
Visual Example
Imagine a line segment with one vertex inside the view frustum and one far beyond it:
Before Clamping:
Frustum X = Discarded
----------------------
/ \ /
/ \ /
| V1 (inside) ------------ X (z=100)
\ /
\--------------------/
After Clamping:
Frustum
----------------------
/ \ /
/ \ /
| V1 (inside) ------------ C (z=pos.w)
\ /
\--------------------/
(C: clamped vertex)
We can generate a simple diagram to illustrate this if needed.
Side Effects and Considerations
While this trick allows objects to remain visible, it does come with trade-offs:
- Distorted depth testing: Since the real z-value is no longer accurate, any depth-based effects like soft particles or fog may misbehave.
- Z-fighting risks: Bringing very distant geometry into the limited depth buffer range can reduce precision.
- Perspective artifacts: Extreme clamping can flatten depth, causing perspective issues for large scenes.
Final Thoughts
While OpenGL ES lacks GL_DEPTH_CLAMP
, a well-placed clamp()
in your vertex shader can go a long way in bypassing clipping without any extensions or hacks. This is a handy trick to keep in your toolbox for cross-platform rendering—especially when working on mobile GPUs where behavior may vary or depth ranges are strict.