gkjohnson / three-mesh-bvh

A BVH implementation to speed up raycasting and enable spatial queries against three.js meshes.
https://gkjohnson.github.io/three-mesh-bvh/example/bundle/raycast.html
MIT License
2.38k stars 247 forks source link

Could not create Web Worker #524

Closed playbyan1453 closed 1 week ago

playbyan1453 commented 1 year ago

Describe the bug

Initializing GenerateMeshBVHWorker resulting on error even when I not using it, also I'm not really sure if this even a bug. When using generate method resulting on undefined error too. I am very new to the Node js and stuffs I wanted to make my own path tracer in three js and the basic ray tracer is done but I wanted parallelized computing of the BVH in case I tried to use denser meshes I also followed the examples but still no luck. Also trying to run the examples locally and installing node.js with npm is not working too. And I tried to run mozilla worker example and running it locally and it is working as intended.

To Reproduce

Steps to reproduce the behavior:

  1. Copy the src directory to your project
  2. Then copy the simple gpu path tracing example
  3. I used npm and the three js installation don't work so I copied this second option

Code

<script async src="https://unpkg.com/es-module-shims/dist/es-module-shims.js"></script>
<script type="importmap">
{
    "imports": {
        "three": "https://unpkg.com/three/build/three.module.js",
        "three/addons/": "https://unpkg.com/three/examples/jsm/",
        "stats.js": "https://cdnjs.cloudflare.com/ajax/libs/stats.js/17/Stats.js"
    }
}
</script>

the gpuPathTracingSimple.js

new GLTFLoader().load('bunny.glb', gltf => { // Load a bunny model
    gltf.scene.traverse(c => {
        if(c.isMesh && c.name == 'bunny') {
            mesh = c;
        }
    });
    mesh.material = material;
    scene.add(mesh);

        // Uncaught Error: GenerateMeshBVHWorker: Could not create Web Worker.
    // const generator = new GenerateMeshBVHWorker();
    let bvh = new MeshBVH(mesh.geometry, bvhparams); // The working one
    mesh.geometry.boundsTree = bvh;
    shMaterial.uniforms.bvh.value.updateFrom(bvh);
    shMaterial.uniforms.normalAttribute.value.updateFrom(mesh.geometry.attributes.normal);
});

The full script

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import Stats from 'stats.js';
import {
    MeshBVH, MeshBVHUniformStruct, FloatVertexAttributeTexture,
    shaderStructs, shaderIntersectFunction, SAH, MeshBVHVisualizer
} from './scripts/index.js';
import { GenerateMeshBVHWorker } from './scripts/workers/GenerateMeshBVHWorker.js';

const params = {
    resolutionScale: 0.5 / window.devicePixelRatio,
    smoothImageScaling: false,
    enableRaytracing: true,
    smoothNormals: true,
    visualizeBounds: false,
    displayParents: false,
    useEdge: true,
    visualBoundsDepth: 10
};

let renderer, camera, scene, gui, stats;
let fsQuad, mesh, material, helper, clock;

init();
updateHelpers();
render();

function init() {
    // renderer setup
    renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setClearColor(0x404040);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.toneMapping = THREE.ReinhardToneMapping;
    renderer.toneMappingExposure = 1.5;
    renderer.outputEncoding = THREE.sRGBEncoding;
    document.body.appendChild(renderer.domElement);

    // scene setup
    scene = new THREE.Scene();

    const light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(1, 1, 1);
    scene.add(light);
    scene.add(new THREE.AmbientLight(0xffffff, 0.051));

    // camera setup
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 100);
    camera.position.set(0, 0, 6);
    camera.updateProjectionMatrix();

    // stats setup
    stats = new Stats();
    document.body.appendChild(stats.dom);

    material = new THREE.MeshStandardMaterial({ color: 0x9f9f9f, roughness: 0.5, side: THREE.DoubleSide });
    clock = new THREE.Clock();

    const shMaterial = new THREE.ShaderMaterial({
        defines: {
            SMOOTH_NORMALS: 1
        },

        uniforms: {
            bvh: { value: new MeshBVHUniformStruct() },
            normalAttribute: { value: new FloatVertexAttributeTexture() },
            cameraWorldMatrix: { value: new THREE.Matrix4() },
            invProjectionMatrix: { value: new THREE.Matrix4() },
        },

        vertexShader: /* glsl */`
            varying vec2 vUv;
            void main() {
                vec4 mvPosition = vec4(position, 1.0);
                mvPosition = modelViewMatrix * mvPosition;
                gl_Position = projectionMatrix * mvPosition;

                vUv = uv;
            }
        `,

        fragmentShader: /* glsl */`
            precision highp isampler2D;
            precision highp usampler2D;
            ${ shaderStructs }
            ${ shaderIntersectFunction }

            uniform mat4 cameraWorldMatrix;
            uniform mat4 invProjectionMatrix;
            uniform sampler2D normalAttribute;
            uniform BVH bvh;
            varying vec2 vUv;

            void main() {
                // get [-1, 1] normalized device coordinates
                vec2 ndc = vUv * 2.0 - 1.0;
                vec3 ro, rd;
                ndcToCameraRay(ndc, cameraWorldMatrix, invProjectionMatrix, ro, rd);
                rd = normalize(rd);

                // hit results
                uvec4 faceIndices = uvec4(0u);
                vec3 faceNormal = vec3(0);
                vec3 barycoord = vec3(0);
                float side = 1.0;
                float dist = 0.0;

                // get intersection
                bool didHit = bvhIntersectFirstHit(bvh, ro, rd, faceIndices, faceNormal, barycoord, side, dist);

                #if SMOOTH_NORMALS
                    vec3 nor = textureSampleBarycoord(normalAttribute, barycoord, faceIndices.xyz).xyz * side;
                #else
                    vec3 nor = faceNormal;
                #endif

                // set the color
                gl_FragColor = !didHit ? vec4(rd, 1.0) : vec4(nor, 1.0);
            }
        `
    });

    fsQuad = new FullScreenQuad(shMaterial);
    fsQuad.outputEncoding = THREE.sRGBEncoding;

    const bvhparams = {
        maxLeafTris: 1,
        strategy: SAH
    };

    new GLTFLoader().load('bunny.glb', gltf => {
    gltf.scene.traverse(c => {
        if(c.isMesh && c.name == 'bunny') {
            mesh = c;
        }
    });
    mesh.material = material;
    scene.add(mesh);

    const generator = new GenerateMeshBVHWorker();
    let bvh = new MeshBVH(mesh.geometry, bvhparams);
    mesh.geometry.boundsTree = bvh;
    shMaterial.uniforms.bvh.value.updateFrom(bvh);
    shMaterial.uniforms.normalAttribute.value.updateFrom(mesh.geometry.attributes.normal);
});

    new OrbitControls(camera, renderer.domElement);

    gui = new GUI();
    const resolutionFolder = gui.addFolder('Resolution');
    resolutionFolder.add(params, 'resolutionScale', 0.25, 1, 0.25).onChange(resize);
    resolutionFolder.add(params, 'smoothImageScaling');
    const raytracingFolder = gui.addFolder('Ray Tracing');
    raytracingFolder.add(params, 'enableRaytracing');
    raytracingFolder.add(params, 'smoothNormals').onChange(v => {
        fsQuad.material.defines.SMOOTH_NORMALS = Number(v);
        fsQuad.material.needsUpdate = true;
        mesh.material.flatShading = !v;
        mesh.material.needsUpdate = true;
    });
    const meshFolder = gui.addFolder('BVH Visualizer');
    meshFolder.add(params, 'visualizeBounds').onChange(updateHelpers);
    meshFolder.add(params, 'displayParents').onChange(updateHelpers);
    meshFolder.add(params, 'useEdge').onChange(updateHelpers);
    meshFolder.add(params, 'visualBoundsDepth', 1, 20, 1).onChange(updateHelpers);
    gui.open();

    window.addEventListener('resize', resize, false);
    resize();
}

function updateHelpers() {
    const shouldDisplayBounds = params.visualizeBounds && mesh.geometry.boundsTree;
    if(helper && !shouldDisplayBounds) {
        scene.remove(helper);
        helper = null;
    }
    if(!helper && shouldDisplayBounds) {
        helper = new MeshBVHVisualizer(mesh);
        helper.color.set(0xffffff);
        scene.add(helper);
    }
    if(helper) {
        helper.displayParents = params.displayParents;
        helper.displayEdges = params.useEdge;
        let opacity = params.useEdge ? 0.5 : 0.25;
        helper.opacity = params.displayParents ? opacity / params.visualBoundsDepth : opacity;
        helper.depth = params.visualBoundsDepth;
        helper.update();
    }
}

function resize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    const w = window.innerWidth;
    const h = window.innerHeight;
    const dpr = window.devicePixelRatio * params.resolutionScale;
    renderer.setSize(w, h);
    renderer.setPixelRatio(dpr);
}

function render() {
    renderer.domElement.style.imageRendering = params.smoothImageScaling ? 'auto' : 'pixelated';

    stats.update();
    requestAnimationFrame(render);

    const delta = clock.getDelta();

    if(mesh && params.enableRaytracing) {
        camera.updateMatrixWorld();

        // update material
        const uniforms = fsQuad.material.uniforms;
        uniforms.cameraWorldMatrix.value.copy(camera.matrixWorld);
        uniforms.invProjectionMatrix.value.copy(camera.projectionMatrixInverse);

        // render float target
        fsQuad.render(renderer);
    } else {
        renderer.render(scene, camera);
    }
}

Live example

-

Expected behavior

The BVH Should generated and the data sent to GPU

Screenshots

error

Platform:

Innovgame commented 1 year ago

You can try it like this

generator.worker = new Worker(workerUrl);
playbyan1453 commented 1 year ago

Haven't tried that, but still not working somehow. It returns same error, probably it's the way I hosted the project and missing something with npm packages.

Innovgame commented 1 year ago

The reason for this problem is that the URL for initializing the worker in the constructor of GenerateMeshBVHWorker is incorrect. You can initialize the worker externally and then assign it to the worker property of GenerateMeshBVHWorker.

gkjohnson commented 1 year ago

Unfortunately web workers seem to give very poor errors when running into issues.

import { GenerateMeshBVHWorker } from './scripts/workers/GenerateMeshBVHWorker.js';

Without understanding your whole file structure it's difficult but if you've copied this worker to your local directory you'll need to make sure that this generateAsync.worker.js file is copied to the same directory in the correct relative file location, as well.

playbyan1453 commented 1 year ago

It is kinda fixed now updating the importmap to

<script type="importmap">
{
    "imports": {
        "three": "https://unpkg.com/three/build/three.module.min.js",
        "three/examples/jsm/": "https://unpkg.com/three/examples/jsm/",
        "three-mesh-bvh": "https://unpkg.com/three-mesh-bvh/build/index.module.js",
        "three-mesh-bvh/src/workers/": "https://unpkg.com/three-mesh-bvh/src/workers/"
    }
}
</script>

and change the gpuPathTracingSimple.js import path to three-mesh-bvh, but now I cannot access the async because I have not set CORS header yet.

gkjohnson commented 1 year ago

now I cannot access the async because I have not set CORS header yet.

Yes browsers do not let you load workers from remote urls. This browser limitation is why the files need to be copied to your own server. You need to copy all the files in the "workers" directory and make sure that the worker path in the "GenerateMeshBVHWorker" class is correctly pointing to your local "generateAsync.worker.js" file. From there I recommend adding print statements to both files and checking the network tab to make sure they're both loading correctly first.

playbyan1453 commented 1 year ago

Both were loaded, while trying to do the first method like generator.worker = new Worker(workerUrl); printing it to the console seems like has no difference with the original worker and pointing it to the local file basically doing things from the start.

gkjohnson commented 1 year ago

If you provide a zip directory in addition to how you're running a local serve I can take a look if it's still not working.

playbyan1453 commented 1 week ago

threejs-ray-tracing.zip

As today I forgot about this, Basically I created a new project then installed three, three-mesh-bvh, and run it using npm run dev command, thinking about building my project it seems it had circular dependency loop which vite does not support because I give the import to /node_modules/three-mesh-bvh/src/workers/ParallelMeshBVHWorker.js.

playbyan1453 commented 1 week ago

If I understand GenerateMeshBVHWorker do not have this issue when building, because in the parallelMeshBVH.worker.js line 12 seems like the culprit because it gives depends on itself.

gkjohnson commented 1 week ago

The provided zip project works just fine for me.

in the parallelMeshBVH.worker.js line 12 seems like the culprit because it gives depends on itself.

I'm not seeing any issues with vite but either way this is a valid dependency for creating a new worker. If there are issues with certain build setups it should be reported with the bundler.

playbyan1453 commented 1 week ago

ah okay thank you

playbyan1453 commented 1 week ago

so this was a problem with vite.