Three.js:如何将对象放在屏幕顶部,大小相同

时间:2016-12-26 19:45:27

标签: javascript canvas three.js

我是关于threejs的新手,我不知道这个问题是绝对基本的还是太先进了。 所以,我想把一些对象放在屏幕的左上角。下一个对象总是进入下一个正确的位置。我需要每个对象看起来大小相同。如何计算其3d位置和比例(如果需要)

我找到了一个可能对我有用的解决方案:Three.js: Show world coordinate axes in corner of scene但是我认为第二个画布的开销太大了 - 它覆盖了原始画布。

我认为创建一个球体,其位置是相机位置,半径大于屏幕相机距离,并且用raycaster找到交点,这就是重点。这是一个好方法吗?

我的对象是箭头(多边形和拉伸),它们显示我的用户的选定方向。所以这个助手是一个信息框,但我不想覆盖原始场景。例如,屏幕顶部的10-12箭头。它需要相同的尺寸才能成为美丽。

2 个答案:

答案 0 :(得分:5)

如果你想创建一个平视显示器(HUD),一种方法是覆盖第二个精灵场景,用正交相机渲染。

使用的编码模式如下:

camera = new THREE.PerspectiveCamera( ... );
cameraOrtho = new THREE.OrthographicCamera( ... );

scene = new THREE.Scene();
sceneOrtho = new THREE.Scene(); // overlay scene

renderer = new THREE.WebGLRenderer();
renderer.autoClear = false; // to allow overlay

并且,在渲染循环中:

renderer.clear();
renderer.render( scene, camera );
renderer.clearDepth();
renderer.render( sceneOrtho, cameraOrtho );

请参阅http://threejs.org/examples/webgl_sprites.html

three.js r.83

答案 1 :(得分:0)

以下是一个有效的示例,该示例具有一个VisualLayers类,用于管理任意数量的层,并且使用了renderer.autoClear = false和清除深度技术。

这种方法很好,因为renderOrder对象没有被修改(这是另一种方法),因此不会引入其他不同的问题。

尝试使用UI中的选项来查看其功能:

// @ts-check

////////////////////////
// LAYER SYSTEM
////////////////////////

/** @typedef {{name: string, backingScene: THREE.Scene, order: number}} Layer */

class VisualLayers {
    /**
     * @type {Array<Layer>}
     * @private
     */
    __layers = [];

    constructor(
        /** @private @type {THREE.WebGLRenderer} */ __renderer,
        /** @private @type {typeof THREE.Scene} */ __Scene = THREE.Scene
    ) {
        this.__renderer = __renderer;
        this.__Scene = __Scene;
    }

    defineLayer(/** @type {string} */ name, /** @type {number=} */ order = 0) {
        const layer = this.__getLayer(name);

        // The default layer always has order 0.
        const previousOrder = layer.order;
        layer.order = name === "default" ? 0 : order;

        // Sort only if order changed.
        if (previousOrder !== layer.order)
            this.__layers.sort((a, b) => a.order - b.order);

        return layer;
    }

    /**
     * Get a layer by name (if it doesn't exist, creates it with default order 0).
     * @private
     */
    __getLayer(/** @type {string} */ name) {
        let layer = this.__layers.find((l) => l.name === name);

        if (!layer) {
            layer = { name, backingScene: new this.__Scene(), order: 0 };
            layer.backingScene.autoUpdate = false;
            this.__layers.push(layer);
        }

        return layer;
    }

    removeLayer(/** @type {string} */ name) {
        const index = this.__layers.findIndex((l) => {
            if (l.name === name) {
                l.backingScene.children.length = 0;
                return true;
            }

            return false;
        });

        if (index >= 0) this.__layers.splice(index, 1);
    }

    hasLayer(/** @type {string} */ name) {
        return this.__layers.some((l) => l.name === name);
    }

    /** @readonly */
    get layerCount() {
        return this.__layers.length;
    }

    addObjectToLayer(
        /** @type {THREE.Object3D} */ obj,
        /** @type {string | string[]} */ layers
    ) {
        if (Array.isArray(layers)) {
            for (const name of layers) this.__addObjectToLayer(obj, name);
            return;
        }

        this.__addObjectToLayer(obj, layers);
    }

    addObjectsToLayer(
        /** @type {THREE.Object3D[]} */ objects,
        /** @type {string | string[]} */ layers
    ) {
        for (const obj of objects) {
            this.addObjectToLayer(obj, layers);
        }
    }

    /** @private @readonly */
    __emptyArray = Object.freeze([]);

    /** @private */
    __addObjectToLayer(
        /** @type {THREE.Object3D} */ obj,
        /** @type {string} */ name
    ) {
        const layer = this.__getLayer(name);
        const proxy = Object.create(obj, {
            children: { get: () => this.__emptyArray }
        });
        layer.backingScene.children.push(proxy);
    }

    removeObjectFromLayer(
        /** @type {THREE.Object3D} */ obj,
        /** @type {string | string[]} */ nameOrNames
    ) {
        if (Array.isArray(nameOrNames)) {
            for (const name of nameOrNames) {
                const layer = this.__layers.find((l) => l.name === name);
                if (!layer) continue;
                this.__removeObjectFromLayer(obj, layer);
            }
            return;
        }

        const layer = this.__layers.find((l) => l.name === nameOrNames);
        if (!layer) return;
        this.__removeObjectFromLayer(obj, layer);
    }

    /** @private */
    __removeObjectFromLayer(
        /** @type {THREE.Object3D} */ obj,
        /** @type {Layer} */ layer
    ) {
        const children = layer.backingScene.children;
        const index = children.findIndex(
            (proxy) => /** @type {any} */ (proxy).__proto__ === obj
        );

        if (index >= 0) {
            children[index] = children[children.length - 1];
            children.pop();
        }
    }

    removeObjectsFromAllLayers(/** @type {THREE.Object3D[]} */ objects) {
        for (const layer of this.__layers) {
            for (const obj of objects) {
                this.__removeObjectFromLayer(obj, layer);
            }
        }
    }

    render(
        /** @type {THREE.Camera} */ camera,
        /** @type {(layerName: string) => void} */ beforeEach,
        /** @type {(layerName: string) => void} */ afterEach
    ) {
        for (const layer of this.__layers) {
            beforeEach(layer.name);
            this.__renderer.render(layer.backingScene, camera);
            afterEach(layer.name);
        }
    }
}

//////////////////////
// VARS
//////////////////////

let camera, stats, geometry, material, object, object2, root;
let time = 0;
/** @type {THREE.Scene} */
let scene;
/** @type {THREE.WebGLRenderer} */
let renderer;
/** @type {VisualLayers} */
let visualLayers;
const clock = new THREE.Clock();
const greenColor = "#27ae60";
const options = {
    useLayers: true,
    showMiddleBox: true,
    rotate: true,
    layer2Order: 2
};

//////////////////////
// INIT
//////////////////////

~(function init() {
    setup3D();
    renderLoop();
})();

////////////////////////////////
// SETUP 3D
////////////////////////////////

function setup3D() {
    const container = document.createElement("div");
    container.id = "container";
    document.body.appendChild(container);

    // CAMERA
    camera = new THREE.PerspectiveCamera(
        70,
        window.innerWidth / window.innerHeight,
        1,
        10000
    );
    camera.position.x = 0;
    camera.position.z = 500;
    camera.position.y = 0;

    scene = new THREE.Scene();

    // RENDERERS

    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setClearColor(0x111111);
    container.appendChild(renderer.domElement);

    // LAYERS

    visualLayers = new VisualLayers(renderer);
    // Layers don't have to be defined. Adding an object to a layer will
    // automatically create the layer with order 0. But let's define layers with
    // order values.
    visualLayers.defineLayer("layer1", 1);
    visualLayers.defineLayer("layer2", 2);
    visualLayers.defineLayer("layer3", 3);

    // LIGHTS

    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
    directionalLight.position.set(300, 0, 300);
    scene.add(directionalLight);
    visualLayers.addObjectToLayer(directionalLight, [
        "layer1",
        "layer2",
        "layer3"
    ]);

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
    scene.add(ambientLight);
    visualLayers.addObjectToLayer(ambientLight, ["layer1", "layer2", "layer3"]);

    // GEOMETRY

    root = new THREE.Object3D();
    scene.add(root);

    geometry = new THREE.BoxGeometry(100, 100, 100);
    material = new THREE.MeshPhongMaterial({
        color: greenColor,
        transparent: false,
        opacity: 1
    });

    object = new THREE.Mesh(geometry, material);
    root.add(object);
    visualLayers.addObjectToLayer(object, "layer1");
    object.position.y = 80;
    object.position.z = -20;
    // object.rotation.y = -Math.PI / 5

    object2 = new THREE.Mesh(geometry, material);
    object.add(object2);
    visualLayers.addObjectToLayer(object2, "layer2");
    object2.position.y -= 80;
    object2.position.z = -20;
    object2.rotation.y = -Math.PI / 5;

    const object3 = new THREE.Mesh(geometry, material);
    object2.add(object3);
    visualLayers.addObjectToLayer(object3, "layer3");
    object3.position.y -= 80;
    object3.position.z = -20;
    object3.rotation.y = -Math.PI / 5;

    // GUI

    const pane = new Tweakpane({
        title: "VisualLayers"
    });
    pane.addInput(options, "useLayers", { label: "use layers" });
    pane.addInput(options, "showMiddleBox", { label: "show middle box" });
    pane.addInput(options, "rotate");
    pane
        .addInput(options, "layer2Order", {
            label: "layer2 order",
            options: {
                0: 0,
                2: 2,
                4: 4
            }
        })
        .on("change", () => visualLayers.defineLayer("layer2", options.layer2Order));

    // STATS
    // SEE: https://github.com/mrdoob/stats.js

    stats = new Stats();
    stats.domElement.style.position = "absolute";
    stats.domElement.style.left = "0px";
    stats.domElement.style.top = "0px";
    stats.setMode(0);
    document.body.appendChild(stats.domElement);
}

//////////////////////
// RESIZE
//////////////////////

(window.onresize = function (event) {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
})();

//////////////////////
// RAF RENDER LOOP
//////////////////////

function render() {
    stats.begin();

    if (options.rotate) {
        time += clock.getDelta();
        object.rotation.y += 0.02;
        root.rotation.y = Math.PI / 2 + (Math.PI / 6) * Math.sin(time * 0.001);
    }

    object2.visible = options.showMiddleBox;

    if (options.useLayers) {
        scene.updateWorldMatrix(true, true);
        renderer.autoClear = false;
        renderer.clear();
        visualLayers.render(camera, beforeEachLayerRender, afterEachLayerRender);
    } else {
        renderer.autoClear = true;
        renderer.render(scene, camera);
    }

    stats.end();
}

function renderLoop() {
    render();
    requestAnimationFrame(renderLoop);
}

function beforeEachLayerRender(layer) {}
function afterEachLayerRender(layer) {
    renderer.clearDepth();
}
html,
body,
#container {
    margin: 0px;
    padding: 0px;
    width: 100%;
    height: 100%;
}

canvas {
    background: transparent;
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
}
<script src="https://cdn.jsdelivr.net/npm/tweakpane@1.5.5/dist/tweakpane.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/stats.js/r11/Stats.min.js"></script>
<script src="//unpkg.com/three@0.121.1/build/three.min.js"></script>
<script src="//unpkg.com/postprocessing@6.17.4/build/postprocessing.js"></script>

example on codepen