How Point Size Works in glTF Point Clouds

This article examines how point size parameters operate when rendering point cloud primitives within the glTF 2.0 specification. While glTF inherently supports point cloud geometry through its primitive rendering modes, the core specification does not define a dedicated material property for point size. As a result, point sizing relies on a combination of lower-level graphics API mechanics, runtime vertex shaders, custom attributes, and viewer-specific implementations.

Primitive Mode 0: POINTS

In glTF, point clouds are defined inside the meshes array using primitive objects where the mode property is set to 0 (POINTS). Each index or vertex position provided in the accessor array represents an individual point in 3D space rather than a vertex of an interconnected polygon or line.

While the format standardizes vertex attributes such as POSITION, COLOR_0, and NORMAL, it omits a standardized, core PBR material property for setting the visual diameter or radius of these points.

The Role of WebGL and Graphics APIs

Because core glTF delegates primitive rasterization to underlying graphics APIs (such as WebGL, WebGPU, OpenGL, or Vulkan), point size behavior defaults to the API's rendering pipeline:

  • Hardware Defaults: In standard WebGL, rendering POINTS relies on the vertex shader setting the special variable gl_PointSize. If the shader does not explicitly set or calculate this value, it defaults to 1.0, producing single-pixel points regardless of camera distance or screen resolution.
  • Hardware Limitations: APIs enforce implementation-dependent minimum and maximum point sizes. For example, WebGL devices expose an ALIASED_POINT_SIZE_RANGE. Points configured beyond these hardware limits are clamped, preventing extremely large splats unless rendered as quad instancing or triangle meshes instead of native point primitives.

Screen-Space vs. World-Space Attenuation

How point sizes scale relative to the camera depends entirely on how the runtime viewer’s shader pipeline handles attenuation:

  1. Fixed Screen-Space Sizing: The vertex shader assigns a constant scalar value to gl_PointSize. The points maintain an identical pixel size across the screen regardless of whether the camera moves closer or further away.
  2. Perspective Attenuation (World-Space Sizing): The vertex shader scales gl_PointSize inversely proportional to the camera distance (often calculated as pointSize = baseSize * (viewportHeight / -viewPosition.z)). This causes points to expand when viewed up close and shrink when viewed from a distance, simulating physical 3D volume.

Because glTF does not standardize distance attenuation formulas in its material schemas, different glTF viewers (such as Three.js, Babylon.js, or Cesium) may render the identical file with different visual densities unless explicitly overridden via client-side code.

Custom Vertex Attributes and Per-Point Sizing

To support variable point sizes within a single point cloud, creators often utilize custom application-specific vertex attributes. According to the glTF 2.0 specification, custom attributes must begin with an underscore:

  • _SIZE or _RADIUS: A floating-point scalar accessor assigned to each vertex.
  • Shader Integration: Viewers customized to support these point clouds bind the custom accessor to the vertex shader, computing gl_PointSize dynamically per vertex rather than using a uniform value for the entire mesh primitive.

Handling Point Sizes in Production

Since point size is not strictly standardized in core glTF, production workflows typically adopt one of the following approaches:

  • Engine Uniforms: Setting point size at the application level through runtime engine materials (e.g., modifying material.size in Three.js or material.pointSize in Babylon.js).
  • Instanced Meshes: Converting point clouds into instanced geometry (such as low-poly spheres or camera-facing quads) using the EXT_mesh_gpu_instancing extension. This guarantees fully deterministic, physical world-space sizing across any compliant glTF viewer at the cost of higher primitive overhead.