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…

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.