Comment faire une texture toujours face à la caméra ..?

10

Mise à jour 5

Créé un autre violon pour montrer à quoi devrait ressembler. Un skydome invisible et une cubecamera sont ajoutés et une carte d'environnement est utilisée; dans mon cas, aucune de ces techniques ne devrait être utilisée pour les raisons déjà mentionnées.


Mise à jour 4

Important: Veuillez noter qu'il y a un plan réfléchissant à l'arrière du maillage cible qui sert à observer si la texture se lie correctement à la surface du maillage, cela n'a rien à voir avec ce que j'essaie de résoudre.


Mise à jour 3

Création d'un nouveau violon pour montrer ce qui n'est PAS le comportement attendu

  • Code

Je devrais peut-être reformuler ma question, mais je n'ai pas les connaissances nécessaires pour décrire avec précision ce que j'essaie de résoudre, aidez-moi s'il vous plaît. peut être .. ?)


Mise à jour 2

(A été déconseillé car un extrait de code est appliqué.)


Mise à jour

OK .. J'ai ajouté 3 méthodes:

  • TransformUvaccepte une géométrie et une méthode de transformation qui gère la transformation uv. Le rappel accepte un tableau uvs pour chaque face et le correspondant Face3de geometry.faces[]comme paramètres.

  • MatcapTransformer est le rappel du gestionnaire uv-transform pour effectuer la transformation matcap.

    et

  • fixTextureWhenRotateAroundZAxis fonctionne comme il l'a nommé.

Jusqu'à présent, aucune des fixTexture..méthodes ne peut fonctionner ensemble, elle fixTextureWhenRotateAroundXAxisn'est pas non plus comprise . Le problème reste non résolu, je souhaite que ce qui vient d'être ajouté puisse vous aider à m'aider.


J'essaie de faire en sorte que la texture d'un maillage soit toujours face à une caméra en perspective active, quelles que soient les positions relatives.

Pour construire un cas réel de ma scène et l'interaction serait assez complexe, j'ai construit un exemple minimal pour démontrer mon intention.

  • Code
    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>

Veuillez noter que bien que le maillage tourne dans cette démonstration, ma véritable intention est de faire bouger la caméra comme en orbite autour du maillage.

J'ai ajouté le filaire pour rendre le mouvement plus clair. Comme vous pouvez le voir, j'utilise fixTextureWhenRotateAroundYAxispour le faire correctement, mais ce n'est que pour l'axe des y. Le mesh.rotation.ydans mon code réel est calculé quelque chose comme

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

Cependant, je ne sais pas comment faire fixTextureWhenRotateAroundAllAxiscorrectement. Il existe certaines restrictions pour résoudre ce problème:

  • CubeCamera / CubeMap ne peut pas être utilisé car les machines clientes peuvent avoir des problèmes de performances

  • Ne faites pas simplement du maillage lookAtla caméra car ils sont finalement de n'importe quelle sorte de géométrie, pas seulement les sphères; des astuces comme lookAtet restaurer .quaterniondans un cadre serait ok.

S'il vous plaît, ne vous méprenez pas, je pose un problème XY car je n'ai pas le droit d'exposer du code propriétaire ou je n'aurais pas à payer l'effort de construire un exemple minimal :)

Ken Kin
la source
Connaissez-vous le langage de shader GLSL? La seule façon d'obtenir cet effet est d'écrire un shader personnalisé qui remplace le comportement par défaut des coordonnées UV.
Marquizzo
@Marquizzo Je ne suis pas un expert de GLSL, cependant, j'ai creusé du code source de three.js comme WebGLRenderTargetCube; Je peux trouver le GLSL emballé avec ShaderMaterial. Comme je l'ai dit, je manque de connaissances à ce sujet et ce serait trop à boire pour le moment. Je crois que three.js a enveloppé GLSL assez bien et aussi assez léger pour que je pense que nous pouvons réaliser des choses comme ça en utilisant la bibliothèque sans traiter nous-mêmes avec GLSL.
Ken Kin du
2
Désolé, mais la seule façon de penser à cela est via GLSL, car les textures sont toujours dessinées dans le shader et vous essayez de changer la façon par défaut de calculer la position de la texture. Vous pourriez avoir plus de chance de poser ce type de questions "comment faire" sur discourse.threejs.org
Marquizzo
Je peux confirmer que c'est soluble dans le pipeline GPU par un pixel shader
Mosè Raguzzini

Réponses:

7

Faire face à la caméra ressemblera à:

entrez la description de l'image ici

Ou, encore mieux, comme dans cette question , où la solution inverse est demandée:

entrez la description de l'image ici

Pour y parvenir, vous devez configurer un shader de fragment simple (comme l'OP l'a fait accidentellement):

Vertex shader

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

Shader de fragment

uniform vec2 size;
uniform sampler2D texture;

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

Une maquette de travail du shader avec 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>
  

Une alternative viable: la cartographie des cubes

Ici, j'ai modifié un jsfiddle sur le mappage de cube , c'est peut-être ce que vous recherchez:

https://jsfiddle.net/v8efxdo7/

Le cube projette sa texture de visage sur l'objet sous-jacent et regarde la caméra.

Remarque: la lumière change avec la rotation car la lumière et l'objet intérieur sont en position fixe, tandis que la caméra et le cube de projection tournent tous les deux autour du centre de la scène.

Si vous regardez attentivement l'exemple, cette technique n'est pas parfaite, mais ce que vous recherchez (appliqué à une boîte) est délicat, car le dépliage UV de la texture d'un cube est en forme de croix, la rotation de l'UV lui-même ne sera pas être efficace et l'utilisation de techniques de projection a aussi ses inconvénients, car la forme de l'objet projecteur et la forme du sujet de projection sont importantes.

Juste pour une meilleure compréhension: dans le monde réel, où voyez-vous cet effet dans l'espace 3D sur les boîtes? Le seul exemple qui me vient à l'esprit est une projection 2D sur une surface 3D (comme la cartographie de projection dans la conception visuelle).

Mosè Raguzzini
la source
1
Plus de l'ancien. Pourriez-vous utiliser three.js pour le faire? Je ne connais pas GLSL. Et je suis curieux de savoir ce qui se passera si le cube de la première animation que vous montrez tourne autour de chaque axe en même temps? Après avoir fourni votre implémentation à l'aide de three.js, je vais essayer de voir si ma question est résolue. Semble prometteur :)
Ken Kin
1
Salut mec, j'ai ajouté un codepen avec une démo simple reproduisant le shader dont vous avez besoin.
Mosè Raguzzini
J'ai besoin de temps pour vérifier si cela fonctionne pour mon cas.
Ken Kin
1
Il ne perd pas le morphing, si la texture fait toujours face à la caméra, l'effet sera toujours d'une texture unie, si l'env n'a pas de lumière ou si le matériau ne projette pas d'ombres. Les attributs et les uniformes tels que la position sont fournis par Geometry et BufferGeometry afin que vous n'ayez pas à les récupérer ailleurs. Les documents Three.js ont une belle section à ce sujet: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini
1
Veuillez consulter jsfiddle.net/7k9so2fr de la version révisée. Je dirais que ce comportement de liaison de texture n'est pas ce que j'essaie de réaliser :( ..
Ken Kin