¿Cómo hacer que una textura siempre mire a la cámara ...?

10

Actualización 5

Creó otro violín para mostrar lo que se espera que se vea. Se agregan un skydome invisible y una cámara cúbica y se utiliza el mapa del entorno; en mi caso, ninguna de estas técnicas debería usarse por los motivos ya mencionados.


Actualización 4

Importante: Tenga en cuenta que hay un plano reflectante en la parte posterior de la malla objetivo que sirve para observar si la textura se une correctamente a la superficie de la malla, no tiene nada que ver con lo que estoy tratando de resolver.


Actualización 3

Creó un nuevo violín para mostrar cuál NO es el comportamiento esperado

  • Código

Tal vez debería reformular mi pregunta, pero no tengo el conocimiento para describir con precisión lo que estoy tratando de resolver, por favor ayuda ... tal vez .. ?)


Actualización 2

(Se ha desaprobado ya que se aplica el fragmento de código).


Actualizar

OK ... agregué 3 métodos:

  • TransformUvacepta una geometría y un método de transformador que maneja la transformación uv. La devolución de llamada acepta una matriz uvs para cada cara y el correspondiente Face3de geometry.faces[]como sus parámetros.

  • MatcapTransformer es la devolución de llamada del controlador uv-transform para hacer la transformación matcap.

    y

  • fixTextureWhenRotateAroundZAxis funciona como lo llamó.

Hasta ahora, ninguno de los fixTexture..métodos puede funcionar en conjunto, tampoco fixTextureWhenRotateAroundXAxisestá resuelto. El problema sigue sin resolverse, desearía que lo que acaba de agregar pueda ayudarme a ayudarme.


Estoy tratando de hacer que la textura de una malla siempre se enfrente a una cámara de perspectiva activa, sin importar cuáles sean las posiciones relativas.

Para construir un caso real de mi escena y la interacción sería bastante compleja, construí un ejemplo mínimo para demostrar mi intención.

  • Código
    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>

Tenga en cuenta que aunque la malla misma gira en esta demostración, mi verdadera intención es hacer que la cámara se mueva como orbitando alrededor de la malla.

He agregado la estructura metálica para que el movimiento sea más claro. Como puede ver, solía fixTextureWhenRotateAroundYAxishacerlo correctamente, pero es solo para el eje y. El mesh.rotation.yen mi código real se calcula algo así como

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

Sin embargo, me falta el conocimiento de cómo hacerlo fixTextureWhenRotateAroundAllAxiscorrectamente. Existen algunas restricciones para resolver esto:

  • CubeCamera / CubeMap no se puede usar ya que las máquinas cliente pueden tener problemas de rendimiento

  • No haga simplemente la malla de lookAtla cámara, ya que eventualmente tienen cualquier tipo de geometría, no solo las esferas; trucos como lookAty restaurar .quaternionen un marco estaría bien.

Por favor, no me malinterpreten porque estoy preguntando un problema XY ya que no tengo derecho a exponer el código de propiedad o no tendría que pagar el esfuerzo para crear un ejemplo mínimo :)

Ken Kin
fuente
¿Conoces el lenguaje de sombreador GLSL? La única forma de lograr este efecto es escribir un sombreador personalizado que anule el comportamiento predeterminado de las coordenadas UV.
Marquizzo
@Marquizzo No soy un experto en GLSL, sin embargo, he cavado un código fuente de three.js como WebGLRenderTargetCube; Puedo encontrar el GLSL envuelto con ShaderMaterial. Como he dicho, me faltan conocimientos sobre esto y sería demasiado beber en este momento. Creo que three.js envolvió GLSL lo suficientemente bueno y liviano como para pensar que podemos lograr cosas como esta usando la biblioteca sin tener que lidiar con GLSL nosotros mismos.
Ken Kin
2
Lo siento, pero la única forma en que puedo pensar en hacerlo es a través de GLSL, ya que las texturas siempre se dibujan en el sombreador, y estás tratando de cambiar la forma predeterminada en que se calcula la posición de la textura. Es posible que tenga más suerte haciendo este tipo de preguntas "cómo" en discursos.threejs.org
Marquizzo
Puedo confirmar que eso se puede resolver en la tubería de la GPU con un sombreador de píxeles
Mosè Raguzzini

Respuestas:

7

Frente a la cámara se verá así:

ingrese la descripción de la imagen aquí

O, mejor aún, como en esta pregunta , donde se pregunta la solución opuesta:

ingrese la descripción de la imagen aquí

Para lograr eso, debe configurar un sombreador de fragmentos simple (como lo hizo accidentalmente el OP):

Sombreador de vértices

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

Sombreador de fragmentos

uniform vec2 size;
uniform sampler2D texture;

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

Un simulacro de trabajo del sombreador 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>
  

Una alternativa viable: mapeo de cubos

Aquí he modificado un jsfiddle sobre el mapeo de cubos , tal vez es lo que estás buscando:

https://jsfiddle.net/v8efxdo7/

El cubo proyecta su textura facial en el objeto subyacente y está mirando a la cámara.

Nota: las luces cambian con la rotación porque la luz y el objeto interno están en una posición fija, mientras que la cámara y el cubo de proyección giran alrededor del centro de la escena.

Si observa cuidadosamente el ejemplo, esta técnica no es perfecta, pero lo que está buscando (aplicado a una caja) es complicado, porque el desenvolvimiento UV de la textura de un cubo tiene forma de cruz, al girar el propio UV no ser eficaz y usar técnicas de proyección también tiene sus inconvenientes, porque la forma del objeto del proyector y la forma del objeto de proyección son importantes.

Solo para una mejor comprensión: en el mundo real, ¿dónde ves este efecto en el espacio 3d en las cajas? El único ejemplo que viene a mi mente es una proyección 2D en una superficie 3D (como el mapeo de proyección en diseño visual).

Mosè Raguzzini
fuente
1
Más del primero. ¿Podría usar three.js para hacerlo? No estoy familiarizado con GLSL. Y tengo curiosidad por saber qué sucederá si el cubo en la primera animación que muestra gira alrededor de cada eje al mismo tiempo. Después de proporcionar su implementación usando three.js, intentaré ver si mi pregunta se resuelve. Parece prometedor :)
Ken Kin
1
Hola amigo, agregué un codepen con una demostración simple que reproduce el sombreador que necesitas.
Mosè Raguzzini
Necesito algo de tiempo para verificar si funciona para mi caso.
Ken Kin
1
No pierde el morphing, si la textura siempre se enfrenta a la cámara, el efecto será siempre una textura simple, si el entorno no tiene luces o el material no proyecta sombras. Los atributos y uniformes como la posición son proporcionados por Geometry y BufferGeometry para que no tenga que recuperarlos en otros lugares. Los documentos de Three.js tienen una buena sección: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini
1
Por favor, eche un vistazo a jsfiddle.net/7k9so2fr de la versión revisada. Diría que este comportamiento de unión de texturas no es lo que estoy tratando de lograr :( ..
Ken Kin