Three.js中的透明对象

81

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

现在的问题是Three.js使第一个球变成了透明。我在我的程序中定义了球1但没有定义第二个球。如果我先定义球1,则它变成透明的,但是球2完全不透明。如果我先定义球2,则这是透明的。将它们添加到场景的顺序并不重要。

我在下面包含了一个最小化的程序,演示了发生了什么情况(没有动画)。在其当前状态下,只有球1可见,而且它不透明。如果我先定义球1再定义球2,那么球1就变成了透明的,但是球2不再透明。将球2的半径更改为1.2,然后将隐藏球1。

有没有办法使两个球都透明?

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);
4个回答

191
你的两个球体都是透明的,而且现在还是透明的。发生的问题是较小的球体没有被渲染出来。
WebGL中的透明度比较棘手。您可以通过搜索该问题来了解更多相关信息。
但是你碰到了一个与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到底是做什么的?它有哪些缺点(性能、新bug等)? - Maël Nison
我认为深度写信号表示对象应该编写自己的深度,这意味着它们根据与相机的距离来确定它们在场景中的深度。如果您禁用它,则可以覆盖新深度。 - Jack Franzen
6
r.71版本中的renderOrder功能对我有用。注意:您必须在子对象上设置它,设置在父对象上不适用于子对象。 - Brian
8
加入 depthWrite: false 是解决方案,谢谢!现在我正在使用:var material_plane = new THREE.MeshBasicMaterial({color: drawcol, side: THREE.DoubleSide, opacity: 0.5, transparent: true, depthWrite: false}); - Avatar
1
depthwrite 为我解决了这个问题。 - Jeff - Software Results
显示剩余6条评论

12

有几点评论。

首先。 如果您要提出一个期望人们审查代码的问题,请将其放在 JSFiddle 上。如果这样做,您将得到更多人的关注。话虽如此,这里是稍微修改过的版本,可作为今后问题的指南。 JSFiddle示例

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,并改变了添加到场景中的球的顺序。这是因为下面两个链接中的信息:


1
谢谢您抽出时间检查我的代码并回复。将rendered.sortObjects设置为false会使最终结果依赖于我添加对象到场景的顺序,正如WestLangley在后面的答案中所解释的那样。问题是我想对场景进行动画处理,对于某些帧来说最好先画一个球体,而对于其他一些帧则另一个球体先画。 - cefstat

3
对我来说,使用透明像素可以解决“云着色器”方面的问题。而使用其他方法则会出现图像伪影。
1/ 线框模型:
    <meshBasicMaterial wireframe />

在此处输入图片描述

2/ 不透明度与透明度:

<meshBasicMaterial opacity="0" transparent="true" thickness="0" transmission="0" depthTest={true}/>

在此输入图片描述

3/带有透明纹理的地图:

    import { useLoader } from '@react-three/fiber'
    import pixTrans from './assets/pix.png' // transparent pixel
    const [textureTrans] = useLoader(THREE.TextureLoader, [pixTrans]) 
    
    return (
        <mesh position={[0, 2, 0]}>
          <boxGeometry args={[3, 5, 3]}/>
          <meshBasicMaterial map={textureTrans} alphaTest="0.5" />
        </mesh>
    )

enter image description here


1

就我所知,使用上述方法无法解决相同的问题,但我发现以下方式可行:

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纹理。
我无法解释为什么其他建议的方法对我没有起作用,但我希望上述内容能帮助处于类似位置的人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接