简体   繁体   中英

Three.js - Map multiple images to a sphere and control each one

I have a 3D sphere that I want to map an array of images onto, and I want to be able to control each individual image ie fading out/in each image independently. I'll provide an example image of what I'm trying to achieve as I feel like that's the best way to explain it.

在此处输入图片说明

So as you can see above, 8 images per column and 16(?) per row.

I have been able to recreate the above image by simply mapping that image to a SphereGeometry , however I would like to be able to dynamically swap out images, and fade them in at different times.

What I've tried so far / My ideas:

  • I tried pushing 8 test images to an array and using that as the material map, and then looping through each face of the SphereGeometry and assigning a material index of 1 through 8 and then resetting after every 8 using modulo, but that didn't work:

     function createGlobe() { var geomGlobe = new THREE.SphereGeometry(40, 32, 16); var l = geomGlobe.faces.length; imageArray.push(new THREE.MeshBasicMaterial({map: texture1})); imageArray.push(new THREE.MeshBasicMaterial({map: texture2})); imageArray.push(new THREE.MeshBasicMaterial({map: texture3})); imageArray.push(new THREE.MeshBasicMaterial({map: texture4})); imageArray.push(new THREE.MeshBasicMaterial({map: texture5})); imageArray.push(new THREE.MeshBasicMaterial({map: texture6})); imageArray.push(new THREE.MeshBasicMaterial({map: texture7})); imageArray.push(new THREE.MeshBasicMaterial({map: texture8})); for (var i = 0; i < l; i++) { geomGlobe.faces[i].materialIndex = i % 8; } Globe = new THREE.Mesh(geomGlobe, imageArray); scene.add(Globe); } 
  • I think I need to count every 4 or 8 faces and then set the material index for each one of those faces to be the same so that they all use the same image, but I'm not sure if the faces line up correctly in that way.

So essentially what I need:

A way to dynamically add images to a sphere in an 8 per column, 16 per row fashion, and the ability to manipulate each one of those images individually.

Any help is very appreciated because I'm very stuck!

I recommend making a large canvas and using that as your texture, then animating your transitions into the canvas, followed by setting texture.needsUpdate = true to update it on the GPU.

You may find that the texture updating takes too much time.. in which case, you could try making 2 canvasses+spheres.. and crossfade between them by changing the frontmost ones opacity.

Below is a snippet showing one way to fade one sphere into another with some randomly filled canvasses..

 var renderer = new THREE.WebGLRenderer(); var w = 300; var h = 200; renderer.setSize(w, h); document.body.appendChild(renderer.domElement); var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 45, // Field of view w / h, // Aspect ratio 0.1, // Near 10000 // Far ); camera.position.set(15, 10, 15); camera.lookAt(scene.position); controls = new THREE.OrbitControls(camera, renderer.domElement); var light = new THREE.PointLight(0xFFFF00); light.position.set(20, 20, 20); scene.add(light); var light1 = new THREE.AmbientLight(0x808080); light1.position.set(20, 20, 20); scene.add(light1); var light2 = new THREE.PointLight(0x00FFFF); light2.position.set(-20, 20, -20); scene.add(light2); var light3 = new THREE.PointLight(0xFF00FF); light3.position.set(-20, -20, -20); scene.add(light3); var sphereGeom = new THREE.SphereGeometry(5, 16, 16); function rnd(rng) { return (Math.random() * rng) } function irnd(rng) { return rnd(rng) | 0 } function randomCanvasTexture(sz) { var canv = document.createElement('canvas'); canv.width = canv.height = sz; var ctx = canv.getContext('2d') for (var i = 0; i < 100; i++) { ctx.fillStyle = `rgb(${irnd(256)},${irnd(256)},${irnd(256)})` ctx.fillRect(irnd(sz), irnd(sz), 32, 32) } var tex = new THREE.Texture(canv); tex.needsUpdate = true; return tex; } var material = new THREE.MeshLambertMaterial({ color: 0x808080, map: randomCanvasTexture(256) }); var mesh = new THREE.Mesh(sphereGeom, material); var mesh1 = mesh.clone() mesh1.material = mesh.material.clone() mesh1.material.transparent = true; mesh1.material.opacity = 0.5; mesh1.material.map = randomCanvasTexture(256) scene.add(mesh); scene.add(mesh1); renderer.setClearColor(0xdddddd, 1); (function animate() { mesh1.material.opacity = (Math.sin(performance.now() * 0.001) + 1) * 0.5 requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); })(); 
 <script src="https://threejs.org/build/three.min.js"></script> <script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script> 

Without gunning for any optimizations, one could try something like this:

textures.forEach( tex=>{

 const s = mySphere.clone()
 s.material = s.material.clone()

 tex.offset.copy(someOffset)
 tex.repeat.copy(someRepeat)

 tex.wrapS = tex.wrapT = THREE.ClampToEdgeWrapping // or something like that

 s.material.map = tex
 s.material.transparent = true

 scene.add(s)
})

The idea is to just draw the same sphere over and over, but masked with different offsets. It might not work with just the .map but it might work with alphaMap which is either all black or all white.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM