【问题标题】:Transparent objects in ThreejsThreejs中的透明对象
【发布时间】:2013-04-06 08:57:02
【问题描述】:

我正在尝试在 Three.js 中编写一个小程序,它显示两个球体,一个在另一个球体内。 sphere2 的半径应该在 0.5 和 1.5 之间波动,而 sphere1 的半径始终为 1.0。每个球体都是透明的(不透明度:0.5),因此可以看到较大的球体中包含的较小的球体。当然,“更小”和“更大”的角色随着 sphere2 半径的变化而变化。

现在的问题是 Three.js 使我在程序中定义的第一个球体变得透明,而不是第二个球体。如果我定义第一个 sphere1 那么它变得透明但 sphere2 完全不透明。如果我定义第一个 sphere2 那么这是透明的。将它们添加到场景中的顺序无关紧要。

我在下面包含一个显示正在发生的事情的最小程序(没有动画)。在其当前状态下,只有 sphere1 可见且不透明。如果我在 sphere2 之前定义 sphere1 则 sphere1 变得透明但 sphere2 不再透明。将 sphere2 的半径更改为 1.2 将隐藏 sphere1。

有没有办法让两个球体都透明?

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);

camera.position.set(0, 0, 3);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);

var ambient = new THREE.AmbientLight( 0x555555 );
scene.add(ambient);

var light = new THREE.DirectionalLight( 0xffffff );
light.position = camera.position;
scene.add(light);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Definition 2
var geometry2 = new THREE.SphereGeometry(0.8,32,24);
var material2 = new THREE.MeshLambertMaterial({color: 0x0000ff, transparent: true, opacity: 0.5});
var sphere2 = new THREE.Mesh(geometry2, material2);

// Definition 1
var geometry1 = new THREE.SphereGeometry(1.0,32,24);
var material1 = new THREE.MeshLambertMaterial({color: 0x00ff00, transparent: true, opacity: 0.5});
var sphere1 = new THREE.Mesh(geometry1, material1);

scene.add(sphere1);
scene.add(sphere2);

renderer.render(scene, camera);

【问题讨论】:

    标签: three.js


    【解决方案1】:

    几个厘米。

    首先。如果您要问一个希望人们审查代码的问题,请将其放在 jsfiddle 中。如果你这样做,你会让更多的人偷看。话虽如此,这里是您在 jsfiddle 中的代码稍作修改的版本,请使用它作为未来问题的指南。 jsfiddle example

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
    
    camera.position.set(0, 0, 3);
    camera.lookAt(new THREE.Vector3(0, 0, 0));
    scene.add(camera);
    
    var ambient = new THREE.AmbientLight( 0x555555 );
    scene.add(ambient);
    
    var light = new THREE.DirectionalLight( 0xffffff );
    light.position = camera.position;
    scene.add(light);
    
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
    
    renderer.sortObjects = false;
    
    // Definition 2
    var geometry2 = new THREE.SphereGeometry(0.8,32,24);
    var material2 = new THREE.MeshLambertMaterial({color: 0x0000ff, transparent: true, opacity: 0.5});
    var sphere2 = new THREE.Mesh(geometry2, material2);
    
    // Definition 1
    var geometry1 = new THREE.SphereGeometry(1.0,32,24);
    var material1 = new THREE.MeshLambertMaterial({color: 0xff0000, transparent: true, opacity: 0.5});
    var sphere1 = new THREE.Mesh(geometry1, material1);
    
    scene.add(sphere2);
    scene.add(sphere1);
    
    renderer.render(scene, camera);
    

    我在您的代码中所做的更改是将 sortObjects 设置为 false,然后更改球体添加到场景中的顺序。这样做是因为接下来 2 个链接中的信息

    WebGL transparent planes Transparent texture behavior

    【讨论】:

    • 感谢您花时间检查我的代码并回复。将 rendered.sortObjects 设置为 false 使最终结果取决于我将对象添加到场景中的顺序,正如 WestLangley 在后面的回答中所解释的那样。问题是我想为场景设置动画,对于某些帧,最好先绘制一个球体,然后在其他帧之前绘制另一个球体。
    【解决方案2】:

    你的两个球体都是透明的,并且一直如此。发生的事情是较小的球体根本没有被渲染。

    WebGL 的透明度很棘手。您可以通过 Google 搜索该问题以了解更多信息。

    但您偶然发现了一个与 three.js 尤其是如何处理透明度有关的问题。

    three.js 中的WebGLRenderer 根据对象与相机的距离对对象进行排序,并按照从最远到最近的顺序渲染透明对象。 (这是很重要的一点:它根据位置对象进行排序,并按排序顺序呈现对象。)

    因此,要正确渲染两个透明对象,必须先渲染后面的对象(在您的情况下是较小的球体)。否则,由于深度缓冲区,它根本不会被渲染。

    但在您的情况下,您有两个位于同一位置的球体,因此与相机等距。这就是问题——首先渲染哪个;这是一个折腾。

    因此,为了正确渲染场景,您需要将较小的球体比较大的球体更远离相机。

    一种解决方案是将较小的球体向后移动一点。

    另一种解决方案是设置renderer.sortObjects = false。然后对象将按照它们添加到场景中的顺序进行渲染。在这种情况下,请务必先将较小的球体添加到场景中。

    第三种解决方案是设置material1.depthWrite = falsematerial2.depthWrite = false

    编辑:

    具有material.transparent = false(不透明对象)的可渲染对象在具有material.transparent = true(透明对象)的对象之前渲染。

    所以第四个解决方案是使较小的球体不透明,以便首先渲染它。

    r.71 的新功能:

    现在有一个Object3D.renderOrder 属性。在每一类对象(不透明或透明)中,对象按照object.renderOrder 指定的顺序呈现。 renderOrder 的默认值为0。注意renderOrder 不被子对象继承;您必须为每个可渲染对象设置它。

    具有相同renderOrder(并列)的对象按深度排序,如上所述。

    所以第五个解决方案是为更大的球体设置renderOrder = 1。这可能是您的最佳解决方案。

    three.js r.71

    【讨论】:

    • depthWrite 到底是做什么的?有什么缺点(性能、新错误……?)?
    • 我认为深度写入表示对象应该写入自己的深度,这意味着它们根据与相机的距离来计算场景中的深度。如果你禁用它,你可以覆盖一个新的深度
    • r.71 中的renderOrder 功能对我有用。注意:您必须在子对象上设置它。在父对象上设置它不适用于子对象。
    • 添加depthWrite: false 是解决方案,谢谢!所以我现在正在使用:var material_plane = new THREE.MeshBasicMaterial({color: drawcol, side: THREE.DoubleSide, opacity: 0.5, transparent: true, depthWrite: false});
    • depthwrite 为我修复了它
    【解决方案3】:

    对于它的价值,我无法使用上述方法解决相同的问题,但发现:

    scene = new THREE.Scene();
    group = new THREE.Group();
    scene.add( group );
    

    在我的 init() 中,然后将正面网格添加到场景中,但是要分组的背面网格解决了问题。即:

    var materialfront = new THREE.MeshPhongMaterial({ 
    opacity:1, map:texture });
    materialfront.transparent = true ;     
    materialfront.side = THREE.FrontSide ; 
    frontthing = new THREE.Mesh( geometry, materialfront );
    frontthing.renderOrder = 2;
    scene.add(frontthing);
    

    然后

    var texture2 = texture.clone();
    texture2.needsUpdate = true;
    var materialBack = new THREE.MeshPhongMaterial({
    opacity:0.1, map: texture2})
    materialBack.transparent = true ;
    materialBack.side = THREE.BackSide;
    backthing = new THREE.Mesh( geometryback, materialBack );
    backthing.renderOrder = 1;
    group.add(backthing);
    

    我的材质贴图是透明的 .png 纹理。

    我无法解释为什么其他建议的方法对我不起作用,但我希望以上内容可以帮助处于类似位置的人。

    【讨论】:

      猜你喜欢
      • 2023-02-05
      • 1970-01-01
      • 2015-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-04
      • 1970-01-01
      相关资源
      最近更新 更多