Come rendere una trama sempre rivolta verso la fotocamera ..?


10

Aggiornamento 5

Creato un altro violino per mostrare come dovrebbe essere. Vengono aggiunti uno skydome invisibile e una cubecamera e viene utilizzata la mappa ambientale; nel mio caso, nessuna di queste tecniche dovrebbe essere utilizzata per i motivi già citati.


Aggiornamento 4

Importante: si noti che sul retro della mesh di destinazione è presente un piano riflettente che serve ad osservare se la trama si lega correttamente alla superficie della mesh, non ha nulla a che fare con ciò che sto cercando di risolvere.


Aggiornamento 3

Creato un nuovo violino per mostrare quale NON è il comportamento previsto

  • Codice

Forse dovrei riformulare la mia domanda, ma mi manca la conoscenza per descrivere accuratamente ciò che sto cercando di risolvere, per favore aiutatemi. (Panoramica-Trasforma-Con-Trama-Guardando-Direzione-Bloccata-Sulla-Fotocamera può essere .. ?)


Aggiornamento 2

(È obsoleto quando viene applicato lo snippet di codice.)


Aggiornare

OK .. Ho aggiunto 3 metodi:

  • TransformUvaccetta una geometria e un metodo del trasformatore che gestisce la trasformazione uv. Il callback accetta un array uvs per ogni faccia e il corrispondente Face3di geometry.faces[]come suoi parametri.

  • MatcapTransformer è il callback del gestore di trasformazione uv per eseguire la trasformazione matcap.

    e

  • fixTextureWhenRotateAroundZAxis funziona come quello che ha chiamato.

Finora nessuno dei fixTexture..metodi può funzionare complessivamente, inoltre, fixTextureWhenRotateAroundXAxisnon è stato capito. Il problema rimane irrisolto, spero che ciò che è stato appena aggiunto possa aiutarti ad aiutarmi.


Sto cercando di fare in modo che la trama di una mesh sia sempre rivolta verso una telecamera prospettica attiva, indipendentemente dalle posizioni relative.

Per costruire un caso reale della mia scena e l'interazione sarebbe piuttosto complessa, ho costruito un esempio minimo per dimostrare la mia intenzione.

  • Codice
    var MatcapTransformer=function(uvs, face) {
    	for(var i=uvs.length; i-->0;) {
    		uvs[i].x=face.vertexNormals[i].x*0.5+0.5;
    		uvs[i].y=face.vertexNormals[i].y*0.5+0.5;
    	}
    };
    
    var TransformUv=function(geometry, xformer) {
    	// The first argument is also used as an array in the recursive calls 
    	// as there's no method overloading in javascript; and so is the callback. 
    	var a=arguments[0], callback=arguments[1];
    
    	var faceIterator=function(uvFaces, index) {
    		xformer(uvFaces[index], geometry.faces[index]);
    	};
    
    	var layerIterator=function(uvLayers, index) {
    		TransformUv(uvLayers[index], faceIterator);
    	};
    
    	for(var i=a.length; i-->0;) {
    		callback(a, i);
    	}
    
    	if(!(i<0)) {
    		TransformUv(geometry.faceVertexUvs, layerIterator);
    	}
    };
    
    var SetResizeHandler=function(renderer, camera) {
    	var callback=function() {
    		renderer.setSize(window.innerWidth, window.innerHeight);
    		camera.aspect=window.innerWidth/window.innerHeight;
    		camera.updateProjectionMatrix();
    	};
    
    	// bind the resize event
    	window.addEventListener('resize', callback, false);
    
    	// return .stop() the function to stop watching window resize
    	return {
    		stop: function() {
    			window.removeEventListener('resize', callback);
    		}
    	};
    };
    
    (function() {
    	var fov=45;
    	var aspect=window.innerWidth/window.innerHeight;
    	var loader=new THREE.TextureLoader();
    
    	var texture=loader.load('https://i.postimg.cc/mTsN30vx/canyon-s.jpg');
    	texture.wrapS=THREE.RepeatWrapping;
    	texture.wrapT=THREE.RepeatWrapping;
    	texture.center.set(1/2, 1/2);
    
    	var geometry=new THREE.SphereGeometry(1, 16, 16);
    	var material=new THREE.MeshBasicMaterial({ 'map': texture });
    	var mesh=new THREE.Mesh(geometry, material);
    
    	var geoWireframe=new THREE.WireframeGeometry(geometry);
    	var matWireframe=new THREE.LineBasicMaterial({ 'color': 'red', 'linewidth': 2 });
    	mesh.add(new THREE.LineSegments(geoWireframe, matWireframe));
    
    	var camera=new THREE.PerspectiveCamera(fov, aspect);
    	camera.position.setZ(20);
    
    	var scene=new THREE.Scene();
    	scene.add(mesh);
      
    	{
    		var mirror=new THREE.CubeCamera(.1, 2000, 4096);
    		var geoPlane=new THREE.PlaneGeometry(16, 16);
    		var matPlane=new THREE.MeshBasicMaterial({
    			'envMap': mirror.renderTarget.texture
    		});
    
    		var plane=new THREE.Mesh(geoPlane, matPlane);
    		plane.add(mirror);
    		plane.position.setZ(-4);
    		plane.lookAt(mesh.position);
    		scene.add(plane);
    	}
    
    	var renderer=new THREE.WebGLRenderer();
    
    	var container=document.getElementById('container1');
    	container.appendChild(renderer.domElement);
    
    	SetResizeHandler(renderer, camera);
    	renderer.setSize(window.innerWidth, window.innerHeight);
    
    	var fixTextureWhenRotateAroundYAxis=function() {
    		mesh.rotation.y+=0.01;
    		texture.offset.set(mesh.rotation.y/(2*Math.PI), 0);
    	};
    
    	var fixTextureWhenRotateAroundZAxis=function() {
    		mesh.rotation.z+=0.01;
    		texture.rotation=-mesh.rotation.z
    		TransformUv(geometry, MatcapTransformer);
    	};
    
    	// This is wrong
    	var fixTextureWhenRotateAroundAllAxis=function() {
    		mesh.rotation.y+=0.01;
    		mesh.rotation.x+=0.01;
    		mesh.rotation.z+=0.01;
    
    		// Dun know how to do it correctly .. 
    		texture.offset.set(mesh.rotation.y/(2*Math.PI), 0);
    	};
      
    	var controls=new THREE.TrackballControls(camera, container);
    
    	renderer.setAnimationLoop(function() {
    		fixTextureWhenRotateAroundYAxis();
    
    		// Uncomment the following line and comment out `fixTextureWhenRotateAroundYAxis` to see the demo
    		// fixTextureWhenRotateAroundZAxis();
    
    		// fixTextureWhenRotateAroundAllAxis();
        
    		// controls.update();
    		plane.visible=false;
    		mirror.update(renderer, scene);
    		plane.visible=true; 
    		renderer.render(scene, camera);
    	});
    })();
    body {
    	background-color: #000;
    	margin: 0px;
    	overflow: hidden;
    }
    <script src="https://threejs.org/build/three.min.js"></script>
    <script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
    
    <div id='container1'></div>

Si noti che sebbene la mesh stessa ruoti in questa dimostrazione, la mia vera intenzione è far muovere la telecamera come in orbita attorno alla mesh.

Ho aggiunto il wireframe per rendere più chiaro il movimento. Come puoi vedere, lo faccio fixTextureWhenRotateAroundYAxisper farlo correttamente, ma è solo per l'asse y. Nel mesh.rotation.ymio codice reale viene calcolato qualcosa del genere

var ve=camera.position.clone();
ve.sub(mesh.position);
var rotY=Math.atan2(ve.x, ve.z);
var offsetX=rotY/(2*Math.PI);

Tuttavia, mi manca la conoscenza di come fare fixTextureWhenRotateAroundAllAxiscorrettamente. Ci sono alcune restrizioni per risolvere questo:

  • Non è possibile utilizzare CubeCamera / CubeMap poiché i computer client potrebbero presentare problemi di prestazioni

  • Non fare semplicemente della mesh lookAtla telecamera in quanto sono eventualmente di qualsiasi tipo di geometria, non solo le sfere; trucchi come lookAte ripristinare .quaternionin una cornice sarebbe ok.

Per favore, non fraintendetemi che sto chiedendo un problema XY in quanto non ho il diritto di esporre il codice proprietario o non dovrei pagare lo sforzo di creare un esempio minimo :)


Conosci il linguaggio shader GLSL? L'unico modo per ottenere questo effetto è scrivere uno shader personalizzato che sovrascriva il comportamento predefinito delle coordinate UV.
Marquizzo,

@Marquizzo Non sono un esperto di GLSL, tuttavia, ho scavato del codice sorgente di three.js come WebGLRenderTargetCube; Posso trovare il GLSL avvolto con ShaderMaterial. Come ho detto, mi mancano le conoscenze al riguardo e al momento sarebbe troppo da bere. Credo che three.js abbia avvolto GLSL abbastanza bene e anche abbastanza leggero da pensare che possiamo ottenere cose come questa usando la libreria senza occuparci di GLSL noi stessi.
Ken Kin,

2
Siamo spiacenti, ma l'unico modo in cui posso pensare di farlo è tramite GLSL, poiché le trame sono sempre disegnate nello shader e stai cercando di cambiare il modo predefinito in cui viene calcolata la posizione della trama. Potresti avere più fortuna a fare questo tipo di domande su "come fare" su discourse.threejs.org
Marquizzo,

Posso confermare, risolvibile nella pipeline GPU da un pixel shader
Mosè Raguzzini,

Risposte:


7

Di fronte alla telecamera apparirà come:

inserisci qui la descrizione dell'immagine

O, ancora meglio, come in questa domanda , in cui viene posta la correzione opposta:

inserisci qui la descrizione dell'immagine

Per ottenere ciò, è necessario impostare un semplice shader di frammenti (come ha fatto accidentalmente l'OP):

Shader di vertice

void main() {
  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Shader di frammenti

uniform vec2 size;
uniform sampler2D texture;

void main() {
  gl_FragColor = texture2D(texture, gl_FragCoord.xy / size.xy);
}

Una simulazione di lavoro dello shader con Three.js

function main() {
  // Uniform texture setting
  const uniforms = {
    texture1: { type: "t", value: new THREE.TextureLoader().load( "https://threejsfundamentals.org/threejs/resources/images/wall.jpg" ) }
  };
  // Material by shader
   const myMaterial = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: document.getElementById('vertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent
      });
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

  const cubes = [];  // just an array we can use to rotate the cubes
  
  const cube = new THREE.Mesh(geometry, myMaterial);
  scene.add(cube);
  cubes.push(cube);  // add to our list of cubes to rotate

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;
    
    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    cubes.forEach((cube, ndx) => {
      const speed = .2 + ndx * .1;
      const rot = time * speed;
      
      
      cube.rotation.x = rot;
      cube.rotation.y = rot;      
    });
   

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
body {
  margin: 0;
}
#c {
  width: 100vw;
  height: 100vh;
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
  void main() {
    gl_Position =   projectionMatrix * 
                    modelViewMatrix * 
                    vec4(position,1.0);
  }
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
  uniform sampler2D texture1;
  const vec2  size = vec2(1024, 512);
  
  void main() {
    gl_FragColor = texture2D(texture1,gl_FragCoord.xy/size.xy); 
  }
</script>
<canvas id="c"></canvas>
  

Un'alternativa praticabile: Mappatura cubi

Qui ho modificato un jsfiddle sulla mappatura dei cubi , forse è quello che stai cercando:

https://jsfiddle.net/v8efxdo7/

Il cubo proietta la sua trama del viso sull'oggetto sottostante e guarda la telecamera.

Nota: le luci cambiano con la rotazione perché la luce e l'oggetto interno sono in posizione fissa, mentre la telecamera e il cubo di proiezione ruotano entrambi attorno al centro della scena.

Se osservi attentamente l'esempio, questa tecnica non è perfetta, ma ciò che stai cercando (applicato a una scatola) è complicato, perché lo scartamento UV della trama di un cubo è a forma di croce, la rotazione dell' UV stesso non lo farà essere efficace e l'uso delle tecniche di proiezione ha anche i suoi svantaggi, perché la forma dell'oggetto del proiettore e la forma del soggetto della proiezione sono importanti.

Solo per una migliore comprensione: nel mondo reale, dove vedi questo effetto nello spazio 3D sulle scatole? L'unico esempio che mi viene in mente è una proiezione 2D su una superficie 3D (come la mappatura della proiezione nella progettazione visiva).


1
Più del primo. Per favore, usi three.js per farlo? Non ho familiarità con GLSL. E sono curioso di sapere cosa succederà se il cubo nella prima animazione che mostri ruota attorno ad ogni asse contemporaneamente? Dopo aver fornito la tua implementazione usando three.js, proverò a vedere se la mia domanda viene risolta. Sembra promettente :)
Ken Kin,

1
Ciao amico, ho aggiunto un codepen con una semplice demo che riproduce lo shader di cui hai bisogno.
Mosè Raguzzini,

Ho bisogno di un po 'di tempo per verificare se funziona per il mio caso.
Ken Kin,

1
Non perde il morphing, se la trama è sempre rivolta verso la telecamera, l'effetto sarà sempre di una trama semplice, se l'ambiente non ha luci o il materiale non proietta ombre. Attributi e uniformi come la posizione sono forniti da Geometry e BufferGeometry, quindi non è necessario recuperarli in altri luoghi. I documenti di Three.js hanno una bella sezione al riguardo: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini,

1
Dai un'occhiata a jsfiddle.net/7k9so2fr di quello rivisto. Direi che questo comportamento dell'associazione delle trame non è quello che sto cercando di ottenere :( ..
Ken Kin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.