1

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.

osswmi
  • 69
  • 2
  • 9
  • 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 Jun 20 '19 at 19:16

1 Answers1

1

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>
TheJim01
  • 8,411
  • 1
  • 30
  • 54
  • Thank you. I took your code and modified it so that I could animate the opacity over a Tween and it work just as expected. – osswmi Jun 21 '19 at 13:29
  • As a footnote, the above code will apply opacity changes to ALL group objects that reference the same instance of a material in Three.JS. Therefor to prevent having a change in opacity from applying itself to more than just the group you meant to target, make sure all individual materials inside a group reference their own instance of a Three.JS material, otherwise the opacity will cascade to all meshes whose material is a shared instance of a material. – osswmi Jun 28 '19 at 21:59
  • A note on your footnote... :) You actually had the right reason, that if your meshes share a material, they share it in every way, properties and all. It doesn't have anything to to with the groups, which _will_ respond individually, provided all the meshes have unique materials. – TheJim01 Jun 28 '19 at 23:25
  • Additionally, this might be cheating, but I have discovered that if I encase the instantiation of the material in double quotes, like so: `myNameSpace.MyMaterial = "new THREE.MeshStandardMaterial({color: 0x456089, roughness: 0.5, metalness: 1, side: THREE.DoubleSide, transparent: true });";` then I can make an "eval()" call to the string the contains the instantiate call to the material like so: `new THREE.Mesh(geom, eval(myNameSpace.MyMaterial));` This allows only one declaration to be made and keeps the materials in each group independent. So far it works. – osswmi Jun 29 '19 at 02:24
  • Sorry for the late response. Use of `eval` is generally discouraged for security and performance reasons. https://stackoverflow.com/questions/86513/why-is-using-the-javascript-eval-function-a-bad-idea Instead, look into `Material.clone()` which creates a copy of a certain material. If you have further questions about this, please create a new question. – TheJim01 Jul 12 '19 at 13:05
  • Thanks for the update. I did what you suggested and it is also working perfectly. – osswmi Jul 15 '19 at 12:15