Points De-Rendering At Specific Angles

Problem is as the title states… Points Material generated de-render at certain camera angles. Specifically those when zoomed out. My code is as follows:

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import {
    computeBoundsTree, disposeBoundsTree, acceleratedRaycast,
} from 'three-mesh-bvh';

THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
THREE.Mesh.prototype.raycast = acceleratedRaycast;

const scene = new THREE.Scene();

var loader = new GLTFLoader();
loader.load(
    "./Earth.glb",
    function(gltf) {
        const model = gltf.scene.children[0];
        model.material.side = THREE.DoubleSide;
        model.material.normalMapType = THREE.ObjectSpaceNormalMap;

        model.rotateX(-Math.PI / 2);

        let geom = model.geometry;
        geom.computeVertexNormals();
        geom.center();
        geom.computeBoundsTree();

        const material = new THREE.MeshBasicMaterial({ 
            color: '0x000000',
            transparent: true, 
            side: THREE.DoubleSide,
            opacity: 0
        });
        const mesh = new THREE.Mesh(geom, material);
        scene.add(mesh);

        // Call your fillWithPoints function here if needed
        const randomPoints = fillWithPoints(mesh, 2000); 

        // Separate points into two groups for texture0 and texture1
        const points0 = [];
        const points1 = [];
        for (let i = 0; i < randomPoints.length; i += 3) {
            if (Math.random() < 0.5) {
                points0.push(randomPoints[i], randomPoints[i + 1], randomPoints[i + 2]);
            } else {
                points1.push(randomPoints[i], randomPoints[i + 1], randomPoints[i + 2]);
            }
        }

        // Create BufferGeometries for the two groups of points
        const pointsGeometry0 = new THREE.BufferGeometry();
        pointsGeometry0.setAttribute('position', new THREE.BufferAttribute(new Float32Array(points0), 3));

        const pointsGeometry1 = new THREE.BufferGeometry();
        pointsGeometry1.setAttribute('position', new THREE.BufferAttribute(new Float32Array(points1), 3));

        // Create a custom texture with green 1's
        const canvas1 = document.createElement('canvas');
        canvas1.width = 64;
        canvas1.height = 64;
        const context1 = canvas1.getContext('2d');
        context1.clearRect(0, 0, canvas1.width, canvas1.height); // Clear the canvas to make it transparent
        context1.fillStyle = 'green';
        context1.font = '48px Arial';
        context1.fillText('1', 10, 50);

        const texture1 = new THREE.CanvasTexture(canvas1);
        texture1.needsUpdate = true;

        // Create a custom texture with green 0's
        const canvas0 = document.createElement('canvas');
        canvas0.width = 64;
        canvas0.height = 64;
        const context0 = canvas0.getContext('2d');
        context0.clearRect(0, 0, canvas0.width, canvas0.height); // Clear the canvas to make it transparent
        context0.fillStyle = 'green';
        context0.font = '48px Arial';
        context0.fillText('0', 10, 50);

        const texture0 = new THREE.CanvasTexture(canvas0);
        texture0.needsUpdate = true;

        // Create Points objects with the generated BufferGeometries and custom textures
        const pointsMaterial0 = new THREE.PointsMaterial({ 
            size: 50, 
            sizeAttenuation: true,     // Enable size attenuation
            map: texture0, 
            transparent: true,
            alphaTest: 0.5,
            blending: THREE.AdditiveBlending,
            depthWrite: false,          // Enable depth writing
            depthTest: true
        });

        const pointsMaterial1 = new THREE.PointsMaterial({ 
            size: 50, 
            sizeAttenuation: true,
            map: texture1, 
            transparent: true,
            alphaTest: 0.5,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            depthTest: true
        });

        const pointsMesh0 = new THREE.Points(pointsGeometry0, pointsMaterial0);
        const pointsMesh1 = new THREE.Points(pointsGeometry1, pointsMaterial1);

        scene.add(pointsMesh0);
        scene.add(pointsMesh1);
    }
);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 3000);
camera.position.z = 1000;
camera.position.y = 1000;
camera.position.x = 0;

const renderer = new THREE.WebGLRenderer({
    alpha: true,
    antialias: true
});
renderer.setClearColor(0x000000, 0); // Set background to transparent
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; 
controls.dampingFactor = 0.25; 

const particleRadius = 1; // Define your particle radius

function fillWithPoints(mesh, count) {
    var ray = new THREE.Raycaster();
    ray.firstHitOnly = true;

    let meshInvMatrix = new THREE.Matrix4();
    meshInvMatrix.copy(mesh.matrixWorld).invert();
    let localRay = new THREE.Ray();

    mesh.geometry.computeBoundingBox();
    let bbox = mesh.geometry.boundingBox;
    let center = new THREE.Vector3();
    bbox.getCenter(center);
    let bsize = new THREE.Vector3();
    bbox.getSize(bsize);

    let points = [];
    let pointsStart = [];
    let pointsDelay = []; //[0..1]

    var dir = new THREE.Vector3(1, 1, 1); // Use a single direction
    var v = new THREE.Vector3();
    var vps = new THREE.Vector3();
    let counter = 0;
    while (counter < count) {
        v.set(
            THREE.MathUtils.randFloat(bbox.min.x, bbox.max.x),
            THREE.MathUtils.randFloat(bbox.min.y, bbox.max.y),
            THREE.MathUtils.randFloat(bbox.min.z, bbox.max.z)
        );
        if (isInside(v)) {
            vps.setFromSphericalCoords(
                Math.random() * particleRadius,
                Math.random() * Math.PI,
                Math.random() * Math.PI * 2
            ).setY(bbox.min.y);
            pointsStart.push(vps.x, vps.y, vps.z);
            pointsDelay.push((v.y - bbox.min.y) / bsize.y);

            points.push(v.clone());
            counter++;
        }
    }

    function isInside(v) {
        // Set the ray origin and direction in world space
        ray.set(v, dir);
        
        // Find intersections with the mesh in world space
        let intersects = ray.intersectObjects([mesh]);

        // Copy the ray to localRay and transform it to the local space of the mesh
        localRay.copy(ray.ray).applyMatrix4(meshInvMatrix);

        console.log(`Intersections: ${intersects.length}`);

        if (intersects.length > 0) {
            const face = intersects[0].face;
            const direction = localRay.direction;

            if (face && direction) {
                const dotProd = face.normal.dot(direction);
                console.log(`Dot product: ${dotProd}`);
                if (dotProd > 0) {
                    return true;
                }
            }
        }
        return false;
    }

    console.log(points);

    // Convert the array of THREE.Vector3 objects to a flat array of numbers
    const flatPoints = [];
    for (let i = 0; i < points.length; i++) {
        flatPoints.push(points[i].x, points[i].y, points[i].z);
    }

    return new Float32Array(flatPoints);
}

function animate() {
    requestAnimationFrame(animate);
    controls.update(); 

    // scene.traverse((object) => {
    //     if (object.isPoints) {
    //         object.lookAt(camera.position);
    //     }
    // });

    renderer.render(scene, camera);
}
animate();

If you notice you will see that I have a custom texture created for each particle (a green 1 or 0). I am not sure if because I created it on a canvas that is having issues. Maybe it would best if I used a png instead? If anyone has ideas let me know… Feel free to use and test out this code. Itll fill any model you plug into it with green 1’s/0’s (like the matrix). I’ve seen one other person with this issue as well who suggested clamping the points, but I am ensure how to proceed with that.

pointsMesh0.frustumCulled =
pointsMesh1.frustumCulled = false;

Can you clarify your question? What does “de-rendering” mean?

Just at certain angles the particles would become essentially invisible. My issue was though that my original mesh was not actually invisible… This made it clip at certain angles…

1 Like

Thank you! My issue ended up being my mesh was still visible even though opacity and transparency was set to true. Seting mesh.visible = false fixed everything. But going to add this just in case nothing else gets de-rendered.