THREEJS:如何在不同的DIV中渲染不同的模型?

4

(我对Three.js不熟悉)


描述

我正在为多个stl模型创建一个3D查看器。每个模型都应该在单独的div中呈现,因此您将获得具有不同模型的网格。 文件名存储在json文件中。使用for循环遍历json并将文件名存储在“var filename”中。我使用该变量来完成到模型的路径。


详细信息

var filename = data[i].Filename;

使用for循环,我能够从json文件中获取所有文件名。

loader.load('model/' + filename, function(geometry) {...

我接着加上文件名来完成模型路径。
modelContainer = document.createElement('div');
modelContainer.className = modelClass;
document.getElementById(modelId).appendChild(modelContainer);

在给定的ID的div内创建一个带有类名的div。并且模型应该被渲染到带有类的div内的画布中。


问题

查看图片现在发生的情况是,代码将所有模型(此示例中的3个模型)都放入最后一个div中。原本用于模型1和2的前两个div是黑色的。

经过大量实验和分析,我仍然找不到解决方案。也许我必须将所有场景存储在一个数组中,并在渲染函数中循环所有场景?


完整的HTML和JavaScript

您可以在此处找到完整的项目代码(不包括.stl模型): https://codepen.io/indoguy/project/editor/XqNKGQ

<div class="container">
    <div class="row" id="modelGrid">
        <!-- The models will be uploaded within this grid -->
    </div>
</div>

<script src="js/three.js"></script>
<script src="js/STLLoader.js"></script>
<script src="js/WebGL.js"></script>

<script>
    var modelDiv = document.getElementById('modelGrid');
    var ourRequest = new XMLHttpRequest();

    // In this code snippit I will read the model filenames out of the json.
    // Add URL where the json is stored
    ourRequest.open('GET', 'http://localhost:8888/3D%20Modeling/3D%20Previewer/3D%20Previewer%20Library/models.json');
    ourRequest.onload = function() {
        var queueData = JSON.parse(ourRequest.responseText);
        renderHTML(queueData);
    };
    ourRequest.send();

    function renderHTML(data) {
        // 3D Model Process
        if (WEBGL.isWebGLAvailable() === false) {
            document.body.appendChild(WEBGL.getWebGLErrorMessage());
        }

        // Editable
        var divWidth = 300;
        var divHeight = 300;
        var modelId = 'modelGrid';
        var modelClass = 'col-sm';

        // Don't change
        var modelContainer;
        var camera, cameraTarget, scene, renderer;
        var cameraFov = 45;


        init(data);
        animate();


        // init
        function init(data) {

            // For loop for every model in the json file
            for (i = 0; i < data.length; i++) {

                var filename = data[i].Filename;

                // Scene
                scene = new THREE.Scene();
                scene.background = new THREE.Color(0xffe5e5);

                // Camera
                camera = new THREE.PerspectiveCamera(cameraFov, divWidth / divHeight, 1, 15);
                cameraTarget = new THREE.Vector3(0, -0.25, 0);

                // Creates a div with the name of the value of var className, inside modelGrid id
                modelContainer = document.createElement('div');
                modelContainer.className = modelClass; // Add class to the div
                document.getElementById(modelId).appendChild(modelContainer);

                // STL Loader
                var loader = new THREE.STLLoader();
                loader.load('model/' + filename, function(geometry) {
                    var material = new THREE.MeshStandardMaterial({
                        color: 0xff1919,
                        roughness: 0.5,
                        metalness: 0
                    });
                    var mesh = new THREE.Mesh(geometry, material);
                    mesh.position.set(0, -0.5, 0); // Position with the Y value
                    mesh.rotation.set(0, -Math.PI / 2, 0);
                    mesh.scale.set(0.15, 0.15, 0.15);
                    scene.add(mesh); // add model to the scene
                });

                // Lights
                scene.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444));
                var light = new THREE.DirectionalLight(0xffffff, 0.5);
                light.position.set(1, 1, 1);
                scene.add(light);

                // renderer
                renderer = new THREE.WebGLRenderer({
                    antialias: true
                });
                renderer.setClearColor(0xffffff, 1);
                renderer.setPixelRatio(window.devicePixelRatio);
                renderer.setSize(divWidth, divHeight);
                // renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.gammaInput = true;
                renderer.gammaOutput = true;
                renderer.shadowMap.enabled = true;
                modelContainer.appendChild(renderer.domElement);
                // window.addEventListener('resize', onWindowResize, false);
            }
        }

        // animate
        function animate() {
            render();
            requestAnimationFrame(animate);
        }

        // render
        function render() {
            renderer.setClearColor(0xffffff);
            var timer = Date.now() * 0.0005;
            camera.position.x = Math.cos(timer) * 3;
            camera.position.z = Math.sin(timer) * 3;
            camera.lookAt(cameraTarget);
            renderer.render(scene, camera);
        }
    };
</script>
1个回答

2
问题在于您只保存和渲染了一个three.js渲染器。在您的for循环中,这一行正在覆盖全局变量renderer,而该变量在您的render()函数中被引用:
renderer = new THREE.WebGLRenderer({
    antialias: true
});

有几种方法可以解决这个问题,包括将渲染器和相机添加到数组中,并在单个渲染函数中对它们进行迭代,或为每个渲染器创建一个新的animate()循环等。以下是第二个选项的一个简单示例:

// For loop for every model in the json file
for (i = 0; i < data.length; i++) {

    var filename = data[i].Filename;

    // EDIT Fix3: These variables must be defined inside of the for loop 
    // scope otherwise they will be shared by all loop iterations
    var modelContainer;
    var camera, cameraTarget, scene;

    // ...

    // Fix 1: Add var declaration so the variable is scoped to this
    // block and variables are not defined globally and overwritten on
    // subsequent iterations. This is needed for all the variables
    // in this block (scene, camera, etc)
    // renderer
    var renderer = new THREE.WebGLRenderer({
        antialias: true
    });

    // ...

    // Fix 2: Create an animation loop for each renderer instead
    // of a single global one
    // animate
    function animate() {
        render();
        requestAnimationFrame(animate);
    }

    // render
    function render() {
        renderer.setClearColor(0xffffff);
        var timer = Date.now() * 0.0005;
        camera.position.x = Math.cos(timer) * 3;
        camera.position.z = Math.sin(timer) * 3;
        camera.lookAt(cameraTarget);
        renderer.render(scene, camera);
    }

    animate();    

}

这个问题肯定有更简洁的解决方法,但希望这可以让你开始!如果可以或者需要进一步说明,请告诉我。

编辑:我刚注意到你的图像显示了三个模型重叠在一起。这是因为scene变量是全局声明的,并且当异步load函数被触发时,它被设置为最终场景对象。


所以我摆脱了全局变量, 在for循环的末尾调用了animate函数, 并将animate()和render()函数放置在init()函数内。因此,所有的div都有场景背景:scene.background = new THREE.Color(0xffe5e5); 但是所有3个模型仍然被渲染在最后一个div中,重叠在一起,就像您在描述中看到的图片一样。 - tiesfa
进展不错——我刚刚更新了我的帖子,加入了“修复3”,所以一定要看一下。你已经摆脱了全局变量,但场景变量仍然在for循环作用域之外,这意味着它仍然会在每次循环迭代期间被覆盖,并且你将遇到与全局变量相同的问题。将它们移动到for循环作用域内应该可以解决这个问题。 - Garrett Johnson
嘿,Garrett,我看不到你的Fix 3,而且scene var是在for循环内部创建的。我尝试分析这段代码,因为它与我想要实现的非常接近,但我还没有成功地实现它。 https://threejs.org/examples/#webgl_multiple_elements在示例中,他们将场景推入for循环中的scenes中 scenes.push(scene);然后他们在render()函数中使用foreach,在其中执行renderer.render(scene, camera);此外,我注意到渲染器在for循环之外但在init()函数内。 - tiesfa
那个例子更加复杂,旨在绕过页面上16个webgl上下文的限制,同时使用单个渲染器。如果您需要它,我会在继续处理这样的内容之前更好地了解您当前的问题。更新在代码块内的注释中标记为“EDIT Fix3:”,并将所有变量声明(modelContainerscenecameracameraTargetrenderer)移动到for循环内部。虽然场景等是在循环内实例化的,但变量是在作用域外声明的,这意味着它们将被覆盖。 - Garrett Johnson
我还看到你提到了将renderanimate函数移动到init函数内部。重要的是,它们与那些变量一起被移动到循环中,就像我在我的示例中所展示的那样。 - Garrett Johnson
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接