Solving the 1-Pixel Limit: Implementing Thick Lines in Three.js
Stop struggling with the 1-pixel limit in WebGL. Learn how to use MeshLine in Three.js to create thick, consistent 3D paths and ribbons with custom screen-space widths.
25 Sept 2025, 06:27 UTC

The WebGL Line Width Limitation
If you have ever tried to increase the lineWidth property of a THREE.LineBasicMaterial, you likely noticed it does nothing. In most modern browsers and operating systems, WebGL implementations hard-code the line width to exactly 1 pixel. This makes it impossible to create bold borders, thick cables, or highlighted paths using standard line primitives.
To achieve visual thickness, you must stop treating lines as mathematical vectors and start treating them as 3D geometry. The most efficient way to do this without manually building thousands of cylinders is using the MeshLine approach, which converts line segments into screen-space ribbons (quads) that always face the camera.
How MeshLine Works
Unlike a standard line, a MeshLine generates a mesh of triangles that follow the path of your points. It uses a custom shader to expand the line width in screen space, meaning the line maintains a consistent pixel thickness regardless of how far the camera is from the object.
Because this is not part of the core Three.js library, it is typically implemented via three-stdlib or a custom shader. The process involves creating a MeshLine object, feeding it a series of Vector3 points, and applying a MeshLineMaterial.
Implementation Example
This example assumes you are using a build system (like Vite or Webpack) and have installed three-stdlib. This implementation creates a thick, colored path between three coordinates.
import * as THREE from 'three';
import { MeshLine, MeshLineMaterial } from 'three-stdlib';
// 1. Define the path points
const points = [
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(5, 2, 0),
new THREE.Vector3(10, 0, 0)
];
// 2. Initialize MeshLine and add points
const line = new MeshLine();
points.forEach(p => line.addPoint(p));
// 3. Configure the material
// Note: lineWidth is in pixels
const material = new MeshLineMaterial({
color: new THREE.Color(0x00ff00),
lineWidth: 0.1, // Adjusted based on coordinate scale and DPI
transparent: true,
opacity: 0.8
});
// 4. Create the mesh and add to scene
const mesh = new THREE.Mesh(line, material);
scene.add(mesh);
Execution Details
- Execution Environment: Client-side browser bundle.
- Permissions: Standard WebGL context access.
- Expected Result: A visible green ribbon following the defined points, maintaining thickness as the camera moves.
- Risk: Using extremely high
lineWidthvalues on very short segments can cause "spiking" artifacts at the joints.
Trade-offs and Performance
While MeshLine solves the visual problem, it introduces new engineering constraints compared to LineBasicMaterial:
| Metric | LineBasicMaterial | MeshLine |
|---|---|---|
| GPU Overhead | Very Low | Moderate (more vertices) |
| Visual Control | None (1px only) | High (Custom width/color) |
| Depth Sorting | Standard | Potential flickering at intersections |
The High-DPI Challenge
A common issue is that lineWidth may appear different on a Retina display versus a standard 1080p monitor. To ensure consistency, you should scale your lineWidth by the window.devicePixelRatio. If the line looks too thin on mobile, multiply your material width by the pixel ratio during the initialization phase.
Verification and Testing
To verify the implementation is working correctly, perform these three checks:
- Zoom Test: Move the camera far away from the line. If the line remains the same pixel thickness on screen, the screen-space shader is working. If it gets smaller, you are likely using a 3D tube geometry instead of a MeshLine.
- DPI Check: Open the scene on both a high-density mobile screen and a desktop monitor. If the thickness varies significantly, implement the
devicePixelRatiomultiplier. - Vertex Load: For datasets exceeding 10,000 points, monitor the frame rate (FPS). If performance drops, use a simplification algorithm (like Ramer-Douglas-Peucker) to reduce the number of points before adding them to the
MeshLine.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.