1

在我的项目中,我知道有 3 个不显眼的层。

  1. 背景
  2. 中地
  3. 前景

这些层中的每一层都可能具有截然不同的单位比例和可能重叠的几何形状。但我不希望它们渲染得好像它们占据了相同的“空间”。我希望它们以逻辑层顺序呈现,而不是它们在 3D 空间中的顺序。

例如,假设背景是一个狭窄的管子,半径为 10 个单位,我将它放在相机上以实现类似隧道的效果。然后我想在远离相机的场景中放置一个大立方体作为Foreground ,每边 100 个单位。

在这种情况下,立方体和隧道相交并相互遮蔽。我正在寻找一种方法来渲染整个隧道,然后渲染整个立方体,然后将渲染的立方体放在渲染隧道的顶部。而且我希望该立方体中的纹理/着色器中的任何 alpha 透明度都被干净地合成,显示透明像素后面的渲染隧道。

所以:

  1. 我是在描述一种存在的技术或功能吗?如果有,那叫什么?
  2. WebGL 可以做到这一点吗?
  3. three.js 可以做到这一点吗?
  4. 与一次性渲染整个帧相比,这会导致性能大幅下降吗?
  5. 如何在 three.js 中构建我的渲染来设置它?

图形编程仍然是新的*GL,如果我的词汇不准确,请见谅。迂腐的词汇更正表示赞赏,因为它会帮助我谷歌!

4

3 回答 3

3

在对这个例子进行一些逆向工程之后,我想我管理了这个:http: //mrdoob.github.com/three.js/examples/webgl_rtt.html

基本上

renderer.autoClear = false;

然后不是像这样渲染一个场景:

render: function() {
  renderer.render(scene, camera);
}

我在渲染多个场景之前手动清除:

render: function() {
  renderer.clear()
  renderer.render(background, camera);
  renderer.render(midground,  camera);
  renderer.render(foreground, camera);
}

但是,仍然不能完全确定性能影响。

于 2012-12-09T07:35:25.427 回答
1

首先,您可以渲染 3 个不同的画布并设置它们的 z-index 和位置,使它们重叠,浏览器将合成它们。

如果您想在 1 个画布中完成所有操作,那么基本上您只需在绘制一些内容后清除深度缓冲区即可。

drawStuffInBack();

// clear the depth (and stencil buffers)
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

drawStuffInMiddle();

// clear the depth (and stencil buffers)
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

drawStuffInFront();
于 2012-12-10T08:14:22.513 回答
0

以下是一个工作示例,它有一个VisualLayers用于管理任意数量层的类,它使用renderer.autoClear = falseAlex Waynes 和 gman 的答案所暗示的技术和清除深度技术。

这种方法很好,因为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>

codepen 上的示例

于 2020-10-09T06:42:58.533 回答