1
votes

This may be more of a "you're going to have to do it the long way" type of deal, but here it goes...

When I apply CSS to control the opacity of only one HTML element that is the container of a Three.JS scene in where there are multiple elements that each are a container for their own scene, the CSS applied (even at an inline level) to only one of those elements containing a scene is being applied to all elements that contain a scene and not specifically the one targeted. This happens with any post applied CSS attribute, not just opacity.

The reason why I am attempting this approach at controlling opacity this way is that per my research there is no direct way to set an opacity on a Three.JS group object that contains 1 to N number of meshes. I am (in theory) trying not to have to define all materials with transparency set to "true" and then having to do recursive updates to all meshes in a Three.JS Group object where an animation would fade in/out.

Some of the group objects I'm working with will have many meshes defined in them. Thus, rather than update the opacity of each individual mesh itself contained within a Three.JS group object, my goal was/is to have individual scenes for each type of animation that is capable of having any amount of transparency applied to it run as is then just adjust the HTML element containing that animation's opacity property.

I've tried using one camera and multiple cameras to no avail. I've also tried nesting the containers in one additional element and setting CSS on the parent element but the same issue occurs. I have not tried using multiple renderers as from what I gather in research is that doing so is frowned upon and can lead to performance issues and context limits. The render loop also has "autoClear" set to false so that all scenes render together.

Here is the HTML syntax. You will notice that the first element has a inline style for opacity set to 0.5 and the second element has no inline styling applied:

<div class="three-js-container" id="scene-container-1" style="opacity:0.5;"></div>
<div class="three-js-container" id="scene-container-2"></div>

Here is the Javascript code:

/* Only one renderer instance is created */
var universalRenderer = new THREE.WebGLRenderer({antialias: true, alpha:true});

/* references to all containers are made */
var containerForScene1 = document.getElementById("scene-container-1");
var containerForScene2 = document.getElementById("scene-container-2");

/* two different cameras are created */
var cameraForScene1 = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.001, 1000);
var cameraForScene2 = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.001, 1000);

/* two different scenes are created, one for each element container */
var scene1 = new THREE.Scene();
scene1.userData.element = containerForScene1;

var scene2 = new THREE.Scene();
scene2.userData.element = containerForScene2;

/* the renderer is applied to both scene containers */
containerForScene1.appendChild(universalRenderer.domElement);
containerForScene2.appendChild(universalRenderer.domElement);

When both animations are played, both scenes are rendered at 1/2 opacity rather than just the first scene itself.

Is there a reason why CSS styling applied to one HTML scene containing element is applied to all of the other scene containing elements? Will I just have to suck it up and go the long way around in controlling mesh opacity?

Thanks.

1
Follow up: If I alter the code to use a separate renderer for each scene, I get the expected behavior, but is it possible to do this without instantiating multiple renders?osswmi

1 Answers

1
votes

Setting transparency for a THREE.Group:

A Group is just a container. As such, it has children, which are potentially other Groups. But you can only apply transparency to a Material, which is assigned at the Mesh level, not Groups. However, not all is lost, because you can monkey patch Group to allow you to perform the operation seamlessly.

// MONKEY PATCH
Object.defineProperty(THREE.Group.prototype, "transparent", {
  set: function(newXP) {
    this.traverse(node => {
      if (node.material) {
        node.material.transparent = newXP
        node.material.opacity = (newXP) ? 0.5 : 1
      }
    })
  }
})

// Set up the renderer

const renderer = new THREE.WebGLRenderer({
  alpha: true,
  antialias: true
})
document.body.appendChild(renderer.domElement)

renderer.setSize(window.innerWidth, window.innerHeight)

const scene = new THREE.Scene()

const size = new THREE.Vector2()
renderer.getSize(size)
const camera = new THREE.PerspectiveCamera(28, size.x / size.y, 1, 1000)
camera.position.set(0, 20, 100)
camera.lookAt(scene.position)
scene.add(camera)

camera.add(new THREE.PointLight(0xffffff, 1))

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

const axis = new THREE.Vector3(0, 1, 0)

function animate() {
  requestAnimationFrame(animate)
  camera.position.applyAxisAngle(axis, 0.005)
  camera.lookAt(scene.position)
  render()
}
animate()

// Populate the scene

const cubeGeo = new THREE.BoxBufferGeometry(5, 5, 5)

let opaqueCubes = []
let transparentCubes = []

const randomInRange = () => Math.random() * ((Math.random() <= 0.5) ? -10 : 10)

const opaqueGroup = new THREE.Group()
scene.add(opaqueGroup)
for (let i = 0; i < 10; ++i) {
  opaqueGroup.add(new THREE.Mesh(cubeGeo, new THREE.MeshPhongMaterial({
    color: "red"
  })))
  opaqueGroup.children[i].position.set(randomInRange(), randomInRange(), randomInRange())
}

const transparentGroup = new THREE.Group()
scene.add(transparentGroup)
for (let i = 0; i < 10; ++i) {
  transparentGroup.add(new THREE.Mesh(cubeGeo, new THREE.MeshPhongMaterial({
    color: "green"
  })))
  transparentGroup.children[i].position.set(randomInRange(-10, 10), randomInRange(-10, 10), randomInRange(-10, 10))
}

// Control the transparency from the input

const xparent = document.getElementById("xparent")
xparent.addEventListener("change", (e) => {
  transparentGroup.transparent = xparent.checked
})
html,
body {
  padding: 0;
  margin: 0;
  overflow: hidden;
}

#control {
  position: absolute;
  top: 0;
  left: 0;
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.js"></script>
<div id="control">
  <label>Make the green ones transparent:<input id="xparent" type="checkbox" /></label>
  <div>