Como fazer uma textura sempre de frente para a câmera ..?

10

Atualização 5

Criou outro violino para mostrar como seria o esperado. Um skydome invisível e uma câmera de cubo são adicionados e o mapa do ambiente é usado; no meu caso, nenhuma dessas técnicas deve ser usada pelos motivos já mencionados.


Atualização 4

Importante: Observe que existe um plano reflexivo atrás da malha de destino, que é para observar se a textura se liga à superfície da malha corretamente, não tem nada a ver com o que estou tentando resolver.


Atualização 3

Criou um novo violino para mostrar qual NÃO é o comportamento esperado

  • Código

Talvez eu deva reformular minha pergunta, mas não tenho o conhecimento necessário para descrever com precisão o que estou tentando resolver, por favor ajude .. (Transformação panorâmica com textura olhando para a direção travada na câmera talvez .. ?)


Atualização 2

(Descontinuado quando o snippet de código é aplicado.)


Atualizar

OK .. eu adicionei 3 métodos:

  • TransformUvaceita uma geometria e um método de transformador que manipula a transformação uv. O retorno de chamada aceita um array uvs para cada face e o correspondente Face3de geometry.faces[]como seus parâmetros.

  • MatcapTransformer é o retorno de chamada do manipulador de transformação uv para fazer a transformação matcap.

    e

  • fixTextureWhenRotateAroundZAxis funciona como o nome dele.

Até agora, nenhum dos fixTexture..métodos pode funcionar em conjunto, também fixTextureWhenRotateAroundXAxisnão está resolvido. O problema permanece sem solução. Desejo que o que foi adicionado possa ajudar você a me ajudar.


Estou tentando fazer com que a textura de uma malha fique sempre de frente para uma câmera de perspectiva ativa, independentemente das posições relativas.

Para construir um caso real da minha cena e a interação seria bastante complexa, construí um exemplo mínimo para demonstrar minha intenção.

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

Observe que, embora a própria malha gire nesta demonstração, minha verdadeira intenção é fazer a câmera se mover como orbitando em torno da malha.

Eu adicionei a estrutura de arame para tornar o movimento mais claro. Como você pode ver, eu costumo fixTextureWhenRotateAroundYAxisfazer isso corretamente, mas é apenas para o eixo y. O mesh.rotation.yno meu código real é calculado algo como

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

No entanto, não tenho conhecimento de como fazer fixTextureWhenRotateAroundAllAxiscorretamente. Existem algumas restrições para resolver isso:

  • O CubeCamera / CubeMap não pode ser usado, pois as máquinas clientes podem ter problemas de desempenho

  • Não basta fazer da malha lookAta câmera, uma vez que são eventualmente de qualquer tipo de geometria, não apenas as esferas; truques como lookAte restaurar .quaternionem um quadro seria ok.

Por favor, não me entenda mal, estou perguntando um problema XY, pois não tenho o direito de expor código proprietário ou não precisaria pagar um esforço para criar um exemplo mínimo :)

Ken Kin
fonte
Você conhece a linguagem shader GLSL? A única maneira de obter esse efeito é escrever um sombreador personalizado que substitua o comportamento padrão das coordenadas UV.
Marquizzo 15/10/19
@Marquizzo Não sou especialista no GLSL, no entanto, procurei um código-fonte de three.js como WebGLRenderTargetCube; Eu posso encontrar o GLSL envolvido com ShaderMaterial. Como eu disse, estou com falta de conhecimento sobre isso e seria demais beber no momento. Acredito que o three.js envolveu o GLSL bom o suficiente e também leve o suficiente para que eu consiga conseguir coisas como essa usando a biblioteca sem lidar com o GLSL.
Ken Kin
2
Desculpe, mas a única maneira de pensar nisso é através do GLSL, já que as texturas são sempre desenhadas no sombreador e você está tentando alterar a maneira padrão de calcular a posição da textura. Você pode ter melhor sorte pedindo este tipo de "como fazer" perguntas em discourse.threejs.org
Marquizzo
Posso confirmar, isso é solucionável no gasoduto GPU por um pixel shader
Mosè Raguzzini

Respostas:

7

De frente para a câmera, será semelhante a:

insira a descrição da imagem aqui

Ou, melhor ainda, como nesta pergunta , onde a correção oposta é feita:

insira a descrição da imagem aqui

Para conseguir isso, você precisa configurar um shader de fragmento simples (como o OP acidentalmente fez):

Sombreador de vértice

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

Tonalizador de fragmentos

uniform vec2 size;
uniform sampler2D texture;

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

Uma simulação de trabalho do shader com o 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>
  

Uma alternativa viável: mapeamento de cubo

Aqui eu modifiquei um jsfiddle sobre mapeamento de cubo , talvez seja o que você está procurando:

https://jsfiddle.net/v8efxdo7/

O cubo projeta sua textura de rosto no objeto subjacente e está olhando para a câmera.

Nota: as luzes mudam com a rotação porque a luz e o objeto interno estão em posição fixa, enquanto câmera e o cubo de projeção giram ambos ao redor do centro da cena.

Se você observar atentamente o exemplo, essa técnica não é perfeita, mas o que você está procurando (aplicado a uma caixa) é complicado, porque o desembrulhamento de UV da textura de um cubo é em forma de cruz, girar o próprio UV não será ser eficaz e o uso de técnicas de projeção também tem suas desvantagens, porque a forma do objeto do projetor e a forma do objeto da projeção são importantes.

Apenas para uma melhor compreensão: no mundo real, onde você vê esse efeito no espaço 3D nas caixas? O único exemplo que me vem à mente é uma projeção 2D em uma superfície 3D (como o mapeamento de projeção no design visual).

Mosè Raguzzini
fonte
11
Mais do anterior. Você poderia usar o three.js para fazer isso? Eu não estou familiarizado com GLSL. E estou curioso para saber o que acontecerá se o cubo na primeira animação que você mostrar girar em torno de todos os eixos ao mesmo tempo? Depois de fornecer sua implementação usando three.js, tentarei ver se minha pergunta foi resolvida. Parece promissor :)
Ken Kin
11
Oi amigo, eu adicionei um codepen com uma demo simples reproduzindo o shader que você precisa.
Mosè Raguzzini
Preciso de um tempo para verificar se funciona para o meu caso.
Ken Kin
11
Não perde a transformação, se a textura sempre estiver voltada para a câmera, o efeito será sempre de textura simples, se o ambiente não tiver luzes ou o material não projetar sombras. Atributos e uniformes como a posição são fornecidos por Geometry e BufferGeometry, para que você não precise recuperá-los em outros lugares. Three.js docs têm uma seção agradável sobre ele: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini
11
Por favor, dê uma olhada em jsfiddle.net/7k9so2fr da revista. Eu diria que este comportamento de ligação textura é não o que eu estou tentando alcançar :( ..
Ken Kin