Anti-aliasing lines

I am using Line2, LineGeometry, LineMaterial to create lines with some thickness that can work across all browsers. However, I am struggling with getting the line resolution right. There is always some flickering of the lines. I am using SMAA, but even without it the behavior is the same.

Any idea how can I keep a clean line without the flickering behavior?

Peek 2025-02-04 23-24

Hard to say without looking at your specific setup… BUT

Sometimes this happens because your renderer.setSize( doesn’t match the canvas’ actual size… so the sizzle you’re seeing are pixels being dropped.

Also check that antialias:true is set on your renderer constructor.

Hello! Here’s what I am doing.

    container = document.getElementById('threejs_container');
    const width = CANVAS_WIDTH;
    const height = CANVAS_HEIGHT;
    const aspectRatio = width / height;

    camera = new THREE.OrthographicCamera(width / - 5.5, width / 5.5, height / 5.5, height / - 5.5, 0.01, 1000);
    camera.position.z = DEFAULT_CAMERA_DIST * 0.01;
    camera.position.x = 0.0;
    camera.position.y = 0.0;
    camera.updateProjectionMatrix();

    let loader = new THREE.BufferGeometryLoader();
    scene = new THREE.Scene();

    renderer = new THREE.WebGLRenderer({ canvas: canvas,  context: gl, antialias: true, alpha: true });
    // renderer.autoClear = false;
    renderer.setPixelRatio(aspectRatio);
    renderer.setSize(width, height);
    renderer.setClearColor(0xffffff, 0.0);

    controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.15;
    controls.zoomSpeed = 1.0;

    transform = new TransformControls(camera, renderer.domElement);
    transform.setMode("translate");

    let sphere = new THREE.SphereGeometry(5, 30, 30);
    let sphereMaterial = new THREE.MeshPhongMaterial({color: 0x00ff00});
    sphereMaterial.depthTest = true;
    sphereMesh = new THREE.Mesh(sphere, sphereMaterial);
    // scene.add(sphereMesh);

    renderer.shadowMap.enabled = false;

    let ambLight = new THREE.AmbientLight(0xffffff);
    ambLight.intensity = 1.5;
    scene.add(ambLight);

    const outputPass = new OutputPass();
    composer = new EffectComposer(renderer);
    var pixelRatio = renderer.getPixelRatio();
    var renderPass = new RenderPass(scene, camera);
    composer.addPass(renderPass);

    outlinePass = new OutlinePass(new THREE.Vector2(width, height), scene, camera);
    outlinePass.edgeGlow = 0.0;
    outlinePass.edgeStrength = 5.0;
    outlinePass.edgeThickness = 0.5;
    outlinePass.visibleEdgeColor.set(0xf7f7f7);
    outlinePass.hiddenEdgeColor.set(0xae0000);
    // composer.addPass(outlinePass);

    var smaaPass = new SMAAPass(CANVAS_WIDTH * pixelRatio, CANVAS_HEIGHT * pixelRatio);
    composer.addPass(smaaPass);

    composer.addPass(outputPass);

    container.appendChild(renderer.domElement);

That doesn’t look right. Your setting the pixelRatio as the aspect ratio. I would comment that line out.
The aspect ratio is used to set the camera aspect, not pixel ratio.

You also don’t have a resize handler so your renderer is set to a fixed size… which is ok, but just make sure the canvas isn’t being resized by CSS etc.

Its also unclear what the size of the EffectComposer is being set to… I’m guessing by default it adopts the size of the renderer passed in… so that’s probably fine… but if you do add a resize handler later, you will need to both:

renderer.setSize(width,height)
composer.setSize(width,height)

Other than that… nothing obvious stands out to me… comment out that devicePixelRatio = and see if that helps, and we can take it from there.

I removed the pixelRatio thing, but that makes it worse. The strange thing is, if I set the pixelRatio to 2.0 it gets much better, but the sizzling is still there.

I am just using renderer.render(scene, camera) in the loop, not the composer. Using the composer actually makes it much worse.

By the way, “threejs_container” is just a div in the HTML that I am adding the renderer to. Do I need to do that differently?

const canvas = document.createElement('canvas');
    let gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
    const isWebGL2 = !!canvas.getContext('webgl2');
    canvas.width = CANVAS_WIDTH;
    canvas.height = CANVAS_HEIGHT;

    container = document.getElementById('threejs_container');
    const width = CANVAS_WIDTH;
    const height = CANVAS_HEIGHT;

    setInteractivity(true);
    ui.hideSpinner();

    camera = new THREE.OrthographicCamera(width / - 5.5, width / 5.5, height / 5.5, height / - 5.5, -500, 500);
    camera.position.z = DEFAULT_CAMERA_DIST * 0.01;
    camera.position.x = 0.0;
    camera.position.y = 0.0;
    camera.updateProjectionMatrix();

    let loader = new THREE.BufferGeometryLoader();
    scene = new THREE.Scene();
    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    // renderer.autoClear = false;  // Disable automatic clearing
    // renderer.setPixelRatio(2.0);
    renderer.setSize(width, height);
    renderer.setClearColor(0xffffff, 0.0);
    renderer.shadowMap.enabled = false;
    container.appendChild(renderer.domElement);

    controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.15;
    controls.zoomSpeed = 1.0;

    transform = new TransformControls(camera, renderer.domElement);
    transform.setMode("translate");

    let ambLight = new THREE.AmbientLight(0xffffff);
    ambLight.intensity = 2.0;
    scene.add(ambLight);
    directionalLight = new THREE.DirectionalLight(0xE08222, 1);
    targetObject = new THREE.Object3D();
    targetObject.position.set(5, 0, 5);
    scene.add(targetObject);
    cameraLight = new THREE.DirectionalLight(0xffffff, 3.0);
    scene.add(cameraLight);

    Snapping.initializeSnapGizmos();

    // const outputPass = new OutputPass();
    composer = new EffectComposer(renderer);
    // var pixelRatio = renderer.getPixelRatio();
    // var renderPass = new RenderPass(scene, camera);
    // composer.addPass(renderPass);
    // composer.setSize(width, height);
    outlinePass = new OutlinePass(new THREE.Vector2(width, height), scene, camera);
    outlinePass.edgeGlow = 0.0;
    outlinePass.edgeStrength = 5.0;
    outlinePass.edgeThickness = 0.5;
    outlinePass.visibleEdgeColor.set(0xf7f7f7);
    outlinePass.hiddenEdgeColor.set(0xae0000);
    // composer.addPass(outlinePass);
    // var smaaPass = new SMAAPass(CANVAS_WIDTH * pixelRatio, CANVAS_HEIGHT * pixelRatio);
    // composer.addPass(smaaPass);
    // composer.addPass(outputPass);

Here’s what it looks like with setPixelRatio(2.0)

Peek 2025-02-06 22-08

And here it is without it. Notice the pronounced sizzling:

Peek 2025-02-06 22-09

You gotta make sure that CANVAS_WIDTH
and CANVAS_HEIGHT
Match the width and height of “container” in your setup.

Normally we don’t create a canvas, we just let threes create it for us with the renderer constructor, and then add it to the dom after renderer creation with something like:
document.body.appendChild(renderer.domElement)

Normally we also have a window “resize” handler that takes special care to call renderer.resize(
with the width and height of the display or window, and, importantly, resizes camera being used to match the current display size, and then updating the cameras projection matrix.

If you don’t have these things, your rendering is going to look messed up and sizzly because the renderer is rendering a different set of pixels than is on your display.

If you’re forcably setting devicePIxel ratio to 2, you may just be hiding/warping the problem you’re having, by forcing the rendererer to renderer 4x more pixels than neccesary.

Look at the source code for any of the examples and follow the initialization patterns there.

For instance this example i randomly picked: three.js/examples/webgl_clipping.html at 79497a2c9b86036cfcc0c7ed448574f2d62de64d · mrdoob/three.js · GitHub