简体   繁体   English

打开和关闭 animation 模型的可见属性会干扰 ThreeJS 动画/渲染循环吗?

[英]Turning animation model's visible property on and off interferes with ThreeJS animate/render loop?

In my ThreeJS app (r124) I have a GLB animation model that I attach a Spotlight to, to make it look like a camera drone with a light that turns on and off.在我的ThreeJS应用程序 (r124) 中,我有一个 GLB animation model 我附加了一个聚光灯,让它看起来像一架带灯的无人机,可以打开和关闭。 I have a function to "turn on the light" and the only thing it does is make the light visible by setting its visible property to true :我有一个 function 来“打开灯”,它唯一做的就是通过将其visible属性设置为true来使灯可见:

    this.turnOnLight = function(bUseLaserScan=false) {
        if (self.CAC.modelInstance.visible === false)
            self.CAC.modelInstance.visible = true;
    }

Unfortunately, there is something about doing this that interferes with the render/animate loop.不幸的是,这样做会干扰渲染/动画循环。 Every time I turn on the light ( or off) there is a long pause in the 200-300 millisecond range, freezing everything in the scene for that length of time.每次我开灯(关灯)时,都会有一段 200-300 毫秒范围内的长时间停顿,将场景中的所有东西都冻结了那么长的时间。 In other words, the render loop is not being called for 200-300 milliseconds.换句话说,渲染循环在 200-300 毫秒内未被调用。 If I comment out the statements that change the light's visibility property, the pause/delay goes away.如果我注释掉改变光的可见性属性的语句,暂停/延迟就会消失。

Why would simply turning a model's visibility property on and off freeze the scene, and how can I fix this problem?为什么简单地打开和关闭模型的可见性属性会冻结场景,我该如何解决这个问题?

If I was loading a large model or something every time I turn on the light sub-object, I could see this happening.如果我每次打开光子对象时都加载一个大的 model 或其他东西,我可以看到这种情况发生。 But how is this happening when all I am doing is setting the visible property to TRUE?但是,当我所做的只是将visible属性设置为 TRUE 时,这是怎么发生的呢?

Here is how I construct the Spotlight and attach it to the camera drone main animation object. Note, self.CAC.modelInstance is simply an object I use to aggregate ThreeJS objects and properties common to all my animation models.以下是我构建 Spotlight 并将其附加到相机无人机主 animation object 的方法。注意, self.CAC.modelInstance只是一个 object,我用来聚合所有 animation 模型共有的 ThreeJS 对象和属性。


function AnimatedLight(
    modelName,
    modelDeclJsonObj,
    threeJsLightObjectName='PointLight',
    topLevelAnimationModelObj=null,
    bAddLensSphere=false,
    bAddLaserScan=false) {
    const self = this;

    this._buildTheLight = function(
        threeJsLightObjectName,
        modelDeclJsonObj) {
        const methodName = self.constructor.name + '::' + `_buildTheLight`;
        const errPrefix = '(' + methodName + ') ';

        let retLightObj = null;

        if (misc_shared_lib.isEmptySafeString(threeJsLightObjectName))
            throw new Error(`${errPrefix}The threeJsLightObjectName parameter is empty.`);

        if (!misc_shared_lib.isNonNullObjectAndNotArray(modelDeclJsonObj))
            throw new Error(`${errPrefix}The modelDeclJsonObj is not a valid object.`);

        //  Provide default values for the properties the modelDeclJsonObj 
        //   object doesn't have.
        //
        // Default color is RED.
        let color = typeof modelDeclJsonObj.color !== 'undefined' ? modelDeclJsonObj.color : 0xf41321; // 0xffffff;
        let intensity = typeof modelDeclJsonObj.intensity !== 'undefined' ? modelDeclJsonObj.intensity : 8.6; //  1.0;
        let distance = typeof modelDeclJsonObj.distance !== 'undefined' ? modelDeclJsonObj.distance : 0.0;
        let decay = typeof modelDeclJsonObj.decay !== 'undefined' ? modelDeclJsonObj.decay : 1.0;

        // These properties are only for spot-lights, which have an inner angle.
        // NOTE: We store angles in the JSON model initialization declaration
        //  object because they are easier to specify then angles expressed
        //  in radians.

        let angle = (0.8 * MAX_ANGLE_FOR_SPOTLIGHT); // Math.PI / 3;  // Default value.

        if (typeof modelDeclJsonObj.inner_angle_in_degrees !== 'undefined') {
            if (typeof modelDeclJsonObj.inner_angle_in_degrees !== 'number')
                throw new Error(`${errPrefix} The "inner_angle_in_degrees" property in the model JSON declaration object is not a number.`);
            angle = THREE.MathUtils.degToRad(modelDeclJsonObj.inner_angle_in_degrees);
        }

        let penumbra = 0;

        if (typeof modelDeclJsonObj.penumbra_angle_in_degress !== 'undefined') {
            if (typeof modelDeclJsonObj.penumbra_angle_in_degress !== 'number')
                throw new Error(`${errPrefix} The "penumbra_angle_in_degress" property in the model JSON declaration object is not a number.`);

            // ThreeJS uses a range of 0 to 1.0 for the penumbra angle, so
            //  we divide by 180 to rescale the provided value.
            penumbra = Math.min(THREE.MathUtils.degToRad(modelDeclJsonObj.penumbra_angle_in_degress / 180.0), 1);
        }

        // Build the correct ThreeJS light object given the specified
        //  light object type.
        if (threeJsLightObjectName === 'PointLight') {
            retLightObj = new THREE.PointLight(color, intensity, distance, decay);
        }
        else if (threeJsLightObjectName === 'DirectionalLight') {
            retLightObj = new THREE.DirectionalLight(color, intensity);
        }
        else if (threeJsLightObjectName === 'SpotLight') {
            // Create a mini-menu for this type of light.
            retLightObj = new THREE.SpotLight(color, intensity, distance, angle, penumbra, decay);

            // Is a lens sphere desired?
            if (bAddLensSphere) {
                // Yes.  Create it.

                // .................... BEGIN: SUB-OBJECT - Lens Sphere ............

                const radius = 3;
                self.lensSphereObj =
                    new THREE.Mesh(
                        new THREE.SphereBufferGeometry(radius, 20, 20),
                        new THREE.MeshPhongMaterial({color: 0xFF0000}));

                // Add it to the top level animation model.
                // self.CAC.modelInstance.add(cameraLensObj);

                // Add it to the spotlight object.
                retLightObj.add(self.lensSphereObj);

                // .................... END  : SUB-OBJECT - Lens Sphere ............
            }
        }
        else
            throw new Error(`${errPrefix}Invalid threeJsLightObjectName value: ${threeJsLightObjectName}`);

        return retLightObj;
    }

    /**
     * Makes the light visible.  
     */
    this.turnOnLight = function(bUseLaserScan=false) {
        if (self.CAC.modelInstance.visible === false)
            self.CAC.modelInstance.visible = true;
    }

    /**
     * Makes the light invisible.
     */
    this.turnOffLight = function() {
        if (self.CAC.modelInstance.visible === true)
            self.CAC.modelInstance.visible = false;
    }

    this.setTargetForSpotLight = function(targetObj) {
        self.CAC.modelInstance.target = targetObj;
    }

    /**
     * This method must be called to initialize this
     *  animated light for animation.  
     */
    this.initializeModel = function (
        parentAnimManagerObj,
        initModelArgs= null,
        funcCallWhenInitialized = null,
    ) {
        const methodName = self.constructor.name + '::' + `initializeModel`;
        const errPrefix = '(' + methodName + ') ';

        // Store the reference to the AnimationManager() object
        //  that owns us in the CommonAnimationObject we
        //  aggregate.
        self.CAC.parentAnimManagerObj = parentAnimManagerObj;

        // Create a ThreeJS light object from the model (light)
        //  declaration in the JSON declarations block.
        self.CAC.modelInstance = self._buildTheLight(threeJsLightObjectName, initModelArgs);

        self.lensSphereObj.position.set(
            0,
            0,
            -20);

        // Animated lights don't use a model loader.
        self.CAC.modelLoader = null;

        // Set the initial position.
        self.CAC.modelInstance.position.x = initModelArgs.initialPos_X;
        self.CAC.modelInstance.position.y = initModelArgs.initialPos_Y;
        self.CAC.modelInstance.position.z = initModelArgs.initialPos_Z;

        // Add the model to the scene.
        g_ThreeJsScene.add(self.CAC.modelInstance);

        // Finish the initialization process..
        self.CAC.initializeModelCompletely(null);

        // Execute the desired callback function, if any.
        if (funcCallWhenInitialized)
            funcCallWhenInitialized(self);
    }

The answer is a bit complicated.答案有点复杂。 Three.js uses a loop in your material's shader code to render each source of light. Three.js 在材质的着色器代码中使用一个循环来渲染每个光源。 For example, if you have 3 spot lights you'd see something like this:例如,如果您有 3 个聚光灯,您会看到如下内容:

for (int i = 0; i < 3; i++ ) {
    // Perform spotlight calculations
}

When you remove a light, the engine re-compiles the shader code so it only loops twice: i < 2 .当您移除灯光时,引擎会重新编译着色器代码,因此它只会循环两次: i < 2 This has to be done for every material on the scene, so depending on your scene complexity and your graphics card, the new shader compilation might create a bottleneck.必须对场景中的每种材质都执行此操作,因此根据场景的复杂性和显卡,新的着色器编译可能会造成瓶颈。

To get around this, I recommend you simply change the light intensity to 0 to turn it off.为了解决这个问题,我建议您只需将光强度更改为 0即可将其关闭。 That way you don't modify the shader, it just changes the value of a variable.这样您就不会修改着色器,它只会更改变量的值。

this.toggleLight = function() {
    if (light.intensity === 0) {
        light.intensity = 1; // ... or whatever value you need
    } else {
        light.intensity = 0;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM