How transparency of Points object works?

Here is my code:

            scene.background = new THREE.Color(0x003300);

            var geom = new THREE.SphereBufferGeometry(50, 10, 10);

            const mat_P = new THREE.PointsMaterial({
                color: 0xff0000,
                opacity: 0.3,
                size: 10,
                transparent: true,
                fog: false,
            });

            const mat_M = new THREE.MeshBasicMaterial({
                color: 0xff0000,
                opacity: 0.3,
                transparent: true,
                fog: false,
            });

            var sphere_P = new THREE.Points(geom, mat_P);
            sphere_P.position.set(-70, 0, 0);
            scene.add(sphere_P);

            var sphere_M = new THREE.Mesh(geom, mat_M);
            sphere_M.position.set(70, 0, 0);
            scene.add(sphere_M);

And here is the result (with drawings of top of it):

For the mesh sphere it makes sense: I’m blending src = [255,0,0,0.3] with destination [0,51,0,1.0] and I get [77, 36, 0] as per
_gl.blendEquation(_gl.FUNC_ADD);
_gl.blendFuncSeparate(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA);

But the Points object color is [225,6,0], how this one comes into being?

I can even use Mesh with PointsMaterial like so:

        var sphere_M = new THREE.Mesh(geom, mat_P);
        sphere_M.position.set(70, 0, 0);
        scene.add(sphere_M);

and still get the sphere on the right and the color is correct, but PointMaterial with Points objects - I get strange result.

Can you make a jsfiddle to demonstrate the issue?

So I tried to change the library a little bit to:

  1. Make sure the blending mode is always enforced:

    function render(start, count) {

     gl.blendEquation(gl.FUNC_ADD);
     gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
    
      gl.drawElements( mode, count, type, start * bytesPerElement );
    
      info.update( count, mode, 1 );
    

    }

  2. Checked if the color coming out of the fragment shader is the one I supply [255,0,0,0.3] by adding one line of code at the very end of it:

     function WebGLShader( gl, type, string ) {
    
     	const shader = gl.createShader( type );
    
         var marker = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif";
         var replace = marker + "\nif(gl_FragColor == vec4(1.0,0.0,0.0,0.3)) gl_FragColor = vec4(1.0,0.0,1.0,1.0);";
         var inject = string.replace(marker, replace);
    
     	gl.shaderSource( shader, inject );
         gl.compileShader(shader);
    
     	return shader;
    
     }
    

I see purple color in this case, so the original color is coming out of the shader.

What else can it be?

The problem is that you are using SphereBufferGeometry as your points data source. That means multiple points will be rendered at the same location which makes the final color brighter than expected. The following fiddle demonstrates this effect: https://jsfiddle.net/qdncksr3/

2 Likes

I see, thank you!

You are probably also going to lose some points depending on the angle. For transparency to work you need to also sort your (deduped) vertices.

Yeah, I had no idea that for some reason, geometry is made out of many matching vertices.
I wrote this code that allows me to convert built-in buffer geometries into point clouds:

function geomToPC(geom, prec = 0.001) {
    var pc = [];
    var a = geom.attributes.position.array;
    var len = a.length;
    for (let i = 0; i < len; i += 3) {
        var [x, y, z] = [a[i], a[i + 1], a[i + 2]]; 
        var nova = true;
        for (let p of pc) if (Math.abs(x - p.x) < prec && Math.abs(y - p.y) < prec && Math.abs(z - p.z) < prec) {
            nova = false;
            break;
        }
        if (nova) {
            pc.push(new THREE.Vector3(x, y, z));
        }
    }
    return new THREE.BufferGeometry().setFromPoints(pc);
}

I know vertices should be rendered from back to front, I think saw something in the library code related to sorting opaque objects, not sure if it applies to individual vertices though. I need points to work with my own shaders anyway, so I’m writing a code that creates a custom expandable material from scratch on top of Material class, will see how it goes.

This library offers a few materials and objects that wrap a pair of material/geometry but then you realize that you actually need to get a bit of everything into your own object and it’s not easy to do.

If the library takes care of rendering opaque objects first and then transparent second, I’d like to implement order independent blending of points as described here:

Yeah, I had no idea that for some reason, geometry is made out of many matching vertices.

You can use the mergeVertices function in BufferGeometryUtils to dedupe verts, as well, and get a position buffer of unique vertices. You’ll have to remove the index buffer, though.

I know vertices should be rendered from back to front, I think saw something in the library code related to sorting opaque objects, not sure if it applies to individual vertices though.

Three.js (and most rendering libraries) will sort transparent objects from back to front for rendering but there’s nothing performant that can be done to sort points that are within a single geometry that is being rendered.

If the library takes care of rendering opaque objects first and then transparent second, I’d like to implement order independent blending of points as described here:

Cesium supports weighted blended transparency but just note that the result is not “correct” – it will avoid some other render order inconsistencies. It’s more of a trade off depending on what you want. There are a lot of different ways to render transparent objects with different trade offs. I posted a list of some of them here if you’re interested in other options.

1 Like

var geom = new THREE.SphereGeometry(50, 10, 10);

var sphere_P = new THREE.Points(Object.assign( new THREE.Geometry(), { vertices: geom.vertices.slice(0) } ), mat_P);

1 Like

Thanks, that’s a nice one.