仅在被遮挡时显示网格的突出轮廓

Showing highlighted silhouette of mesh ONLY when it is occluded

所以我正在尝试使用 Babylon.js 创建一个在线游戏,但是 运行 遇到了一个让我有点难过的问题,所以希望这里有人愿意帮助我。请多多包涵,我是 babylon 的新手,因为我只用过 THREE.js。现在我的游戏包含一个场景,该场景包含多个网格,多个用户表示为化身(目前由基本的圆形几何体创建)加载到环境中。我想要做的是仅当它们被任何其他物体遮挡时突出显示这些化身的轮廓,这意味着当它们没有被遮挡时它们看起来正常而没有高光但是当在物体后面时它们突出显示的轮廓可以被其他人看到(包括你自己,因为你可以看到你自己的头像)。这与许多其他视频游戏中使用的效果非常相似(请参见下面的示例)。

Example of Effect

到目前为止,基于一些谷歌搜索和论坛浏览 (Babylonjs outline through walls & https://forum.babylonjs.com/t/highlight-through-objects/8002/4),我已经想出了如何使用 Babylon.HighlighLayer 突出显示对象的轮廓,并且我知道我可以渲染对象高于其他对象通过 RenderingGroups 但我似乎无法弄清楚如何结合使用它们来创建我想要的效果。我设法做的最好的事情就是让突出显示的头像渲染高于一切,但我只需要轮廓而不是整个网格。我也受到以下事实的限制:我的场景中有许多动态加载的网格,我也在努力使事情尽可能优化。无法使用计算量大的程序。

有人知道解决这个问题的最佳方法吗?非常感谢您提供的任何建议或帮助 provide.Thanks!

所以我在 babylon 论坛上问了同样的问题,这帮助我找到了解决方案。所有功劳都归功于帮助我的那个人,但以防万一其他人遇到这个问题寻求答案,这里是该论坛问题 link https://forum.babylonjs.com/t/showing-highlighted-silhouette-of-mesh-only-when-it-is-occluded/27783/7

编辑: 好的,我想我会在这里适当地包括两个可能的解决方案以及他们的巴比伦游乐场。所有功劳都归功于 roland 和 evgeni_popov,他们在上面 linked 的论坛上提出了这些解决方案。

第一个解决方案比第二个解决方案更容易实现,但性能稍差。

克隆解决方案:https://playground.babylonjs.com/#JXYGLT%235

// roland@babylonjs.xyz, 2022

const createScene = function () {
const scene = new BABYLON.Scene(engine);

const camera = new BABYLON.ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 2, 20, new BABYLON.Vector3(0, 0, 0), scene)
camera.attachControl(canvas, true);

const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.7;

const wall = BABYLON.MeshBuilder.CreateBox('wall', { width: 5, height: 5, depth: 0.5 }, scene)
wall.position.y = 1
wall.position.z = -2

const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 2, segments: 32 }, scene)
sphere.position.y = 1

const sphereClone = sphere.clone('sphereClone')
sphereClone.setEnabled(false)

const matc = new BABYLON.StandardMaterial("matc", scene);
matc.depthFunction = BABYLON.Constants.ALWAYS;
matc.disableColorWrite = true;
matc.disableDepthWrite = true;

sphereClone.material = matc;

sphere.occlusionQueryAlgorithmType = BABYLON.AbstractMesh.OCCLUSION_ALGORITHM_TYPE_ACCURATE
sphere.occlusionType = BABYLON.AbstractMesh.OCCLUSION_TYPE_STRICT

const hl = new BABYLON.HighlightLayer('hl1', scene, { camera: camera })
hl.addMesh(sphereClone, BABYLON.Color3.Green())
hl.addExcludedMesh(wall);

let t = 0;

scene.onBeforeRenderObservable.add(() => {
    sphere.position.x = 10 * Math.cos(t);
    sphere.position.z = 100 + 104 * Math.sin(t);

    if (sphere.isOccluded) {
        sphereClone.setEnabled(true)
        sphereClone.position.copyFrom(sphere.position);
    } else {
        sphereClone.setEnabled(false)
    }

    t += 0.03;
})

return scene;
};

第二个解决方案比上面的解决方案稍微好一点,因为您不需要克隆,但涉及覆盖 AbstactMesh._checkOcclusionQuery 函数,该函数更新网格的 isOccluded 属性,这样即使被遮挡,网格也会始终呈现。如果您仅出于绘制轮廓的目的使用遮挡查询,则没有开销,但是如果您还使用它们来避免绘制被遮挡的网格,那么就会有开销,因为即使网格被遮挡也会被绘制。在这种情况下,您最好选择第一个解决方案

Non-Clone解决方案:https://playground.babylonjs.com/#JXYGLT#14

// roland@babylonjs.xyz, 2022

const createScene = function () {
const scene = new BABYLON.Scene(engine);

const camera = new BABYLON.ArcRotateCamera('camera', -Math.PI / 2, Math.PI / 2, 20, new BABYLON.Vector3(0, 0, 0), scene)
camera.attachControl(canvas, true);

const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.7;

const wall = BABYLON.MeshBuilder.CreateBox('wall', { width: 5, height: 5, depth: 0.5 }, scene)
wall.position.y = 1
wall.position.z = -2

const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 2, segments: 32 }, scene)
sphere.position.y = 1

sphere.occlusionQueryAlgorithmType = BABYLON.AbstractMesh.OCCLUSION_ALGORITHM_TYPE_ACCURATE
sphere.occlusionType = BABYLON.AbstractMesh.OCCLUSION_TYPE_STRICT

const mats = new BABYLON.StandardMaterial("mats", scene);
sphere.material = mats;

const hl = new BABYLON.HighlightLayer('hl1', scene, { camera: camera })
hl.addExcludedMesh(wall);

let t = 0;

const cur = BABYLON.AbstractMesh.prototype._checkOcclusionQuery;

scene.onDisposeObservable.add(() => {
    BABYLON.AbstractMesh.prototype._checkOcclusionQuery = cur;
});

BABYLON.AbstractMesh.prototype._checkOcclusionQuery = function() {
    cur.apply(this);
    return false;
}

scene.onBeforeRenderObservable.add(() => {
    sphere.position.x = 10 * Math.cos(t);
    sphere.position.z = 100 + 104 * Math.sin(t);

    if (sphere.isOccluded) {
        hl.addMesh(sphere, BABYLON.Color3.Green())
        mats.depthFunction = BABYLON.Constants.ALWAYS;
        mats.disableColorWrite = true;
    } else {
        hl.removeMesh(sphere);
        mats.depthFunction = BABYLON.Constants.LESS;
        mats.disableColorWrite = false;
    }

    t += 0.03;
})

return scene;
};