Jak sprawić, by tekstura zawsze była skierowana w stronę aparatu…?

10

Aktualizacja 5

Utworzono kolejne skrzypce, aby pokazać, jak powinno wyglądać. Dodano niewidoczną skocznię i kamerę typu cubecamera oraz użyto mapy środowiska; w moim przypadku żadna z tych technik nie powinna być stosowana z wyżej wymienionych powodów.


Aktualizacja 4

Ważne: pamiętaj, że z tyłu siatki docelowej znajduje się płaszczyzna odblaskowa, która służy do obserwacji, czy tekstura prawidłowo wiąże się z powierzchnią siatki, nie ma to nic wspólnego z tym, co próbuję rozwiązać.


Aktualizacja 3

Utworzono nowe skrzypce, aby pokazać, jakie NIE jest oczekiwane zachowanie

  • Kod

Może powinienem przeformułować moje pytanie, ale brakuje mi wiedzy, aby dokładnie opisać to, co próbuję rozwiązać, proszę o pomoc .. (Panoramiczna transformacja-z-teksturą-patrząc-w-kierunku-zablokowany-na-aparat może .. ?)


Aktualizacja 2

(Przestarzałe, ponieważ zastosowano fragment kodu).


Aktualizacja

OK .. Dodałem 3 metody:

  • TransformUvakceptuje geometrię i metodę transformatora, która obsługuje transformację UV. Wywołania zwrotnego akceptuje tablicę promieniowaniu UV o każdą powierzchnię i odpowiednią Face3z geometry.faces[]a parametrami.

  • MatcapTransformer jest wywołaniem zwrotnym programu obsługi transformacji UV w celu wykonania transformacji matcap.

    i

  • fixTextureWhenRotateAroundZAxis działa jak to się nazywa.

Jak dotąd żadna z fixTexture..metod nie może działać w całości, również fixTextureWhenRotateAroundXAxisnie jest to rozgryzione. Problem pozostaje nierozwiązany. Chciałbym, aby to, co właśnie dodałem, pomogło mi pomóc.


Staram się, aby tekstura siatki zawsze była skierowana w stronę aktywnej kamery perspektywicznej, bez względu na względne pozycje.

Aby zbudować prawdziwy przypadek mojej sceny, a interakcja byłaby dość złożona, stworzyłem minimalny przykład, aby zademonstrować moją intencję.

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

Należy pamiętać, że chociaż sama siatka obraca się w tej demonstracji, moim prawdziwym zamiarem jest, aby kamera poruszała się jak orbitująca wokół siatki.

Dodałem model szkieletowy, aby ruch był bardziej wyraźny. Jak widać, fixTextureWhenRotateAroundYAxisrobię to poprawnie, ale dotyczy to tylko osi Y. W mesh.rotation.ymoim prawdziwym kodzie obliczono coś podobnego

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

Brakuje mi jednak wiedzy, jak to zrobić fixTextureWhenRotateAroundAllAxispoprawnie. Istnieją pewne ograniczenia związane z rozwiązaniem tego:

  • CubeCamera / CubeMap nie może być używany, ponieważ komputery klienckie mogą mieć problemy z wydajnością

  • Nie rób po prostu siatki lookAtjako kamery, ponieważ ostatecznie mają jakąkolwiek geometrię, nie tylko kule; sztuczki takie jak lookAti przywracanie .quaternionw ramce byłyby w porządku.

Nie zrozumcie mnie źle, że zadaję problem XY, ponieważ nie mam prawa ujawniać zastrzeżonego kodu, inaczej nie będę musiał robić wysiłku, aby zbudować minimalny przykład :)

Ken Kin
źródło
Czy znasz język shadera GLSL? Jedynym sposobem na osiągnięcie tego efektu jest napisanie niestandardowego modułu cieniującego, który przesłania domyślne zachowanie współrzędnych UV.
Marquizzo
@Marquizzo Nie jestem ekspertem w GLSL, jednak wykopałem trochę kodu źródłowego pliku Three.js, takiego jak WebGLRenderTargetCube; Mogę znaleźć GLSL owinięty ShaderMaterial. Jak powiedziałem, brakuje mi wiedzy na ten temat i w tej chwili byłoby za dużo do picia. Uważam, że GLSL w pakiecie Three.js jest wystarczająco dobry, a także wystarczająco lekki, że myślałem, że możemy osiągnąć takie rzeczy, korzystając z biblioteki bez konieczności radzenia sobie z GLSL.
Ken Kin
2
Przykro mi, ale jedynym sposobem, w jaki mogę to zrobić, jest GLSL, ponieważ tekstury są zawsze rysowane w module cieniującym, a ty próbujesz zmienić domyślny sposób obliczania położenia tekstury. Możesz mieć więcej szczęścia, zadając tego typu pytania „jak” na discourse.threejs.org
Marquizzo
Mogę potwierdzić, że jest to możliwe do rozwiązania w potoku GPU przez moduł cieniujący piksele
Mosè Raguzzini,

Odpowiedzi:

7

W stronę kamery będzie wyglądać:

wprowadź opis zdjęcia tutaj

Lub nawet lepiej, jak w tym pytaniu , w którym zadaje się przeciwną poprawkę:

wprowadź opis zdjęcia tutaj

Aby to osiągnąć, musisz ustawić prosty moduł cieniujący fragmenty (tak jak przypadkowo zrobił to PO):

Moduł cieniujący wierzchołek

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

Moduł cieniujący fragmenty

uniform vec2 size;
uniform sampler2D texture;

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

Działająca makieta modułu cieniującego z 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>
  

Realna alternatywa: mapowanie kostki

Tutaj zmodyfikowałem jsfiddle na temat mapowania kostki , być może właśnie tego szukasz:

https://jsfiddle.net/v8efxdo7/

Sześcian rzutuje teksturę twarzy na leżący pod nim obiekt i patrzy w kamerę.

Uwaga: światła zmieniają się wraz z obrotem, ponieważ światło i wewnętrzny obiekt znajdują się w stałej pozycji, podczas gdy kamera i sześcian projekcyjny obracają się zarówno wokół środka sceny.

Jeśli dokładnie przyjrzysz się przykładowi, ta technika nie jest doskonała, ale to, czego szukasz (zastosowane do pudełka) jest trudne, ponieważ odwijanie UV tekstury kostki ma kształt krzyża, obracanie samego UV nie będzie być skutecznym, a stosowanie technik projekcji ma również swoje wady, ponieważ kształt obiektu projektora i kształt obiektu projekcji mają znaczenie.

Dla lepszego zrozumienia: w prawdziwym świecie, gdzie widzisz ten efekt w przestrzeni 3D na pudełkach? Jedyny przykład, jaki przychodzi mi do głowy, to projekcja 2D na powierzchni 3D (jak odwzorowanie projekcji w projekcie wizualnym).

Mosè Raguzzini
źródło
1
Więcej poprzedniego. Czy użyłbyś do tego trzech.js? Nie znam GLSL. I jestem ciekawy, co się stanie, jeśli sześcian w pierwszej pokazanej animacji obraca się wokół każdej osi w tym samym czasie? Po podaniu implementacji za pomocą pliku Three.js postaram się sprawdzić, czy moje pytanie zostanie rozwiązane. Wygląda obiecująco :)
Ken Kin,
1
Cześć kolego, dodałem codepen z prostym demo odtwarzającym shader, którego potrzebujesz.
Mosè Raguzzini
Potrzebuję trochę czasu, aby sprawdzić, czy to działa w mojej sprawie.
Ken Kin
1
Nie traci morfingu, jeśli tekstura zawsze będzie skierowana w stronę aparatu, efekt będzie zawsze miał zwykłą teksturę, jeśli w środowisku nie ma światła lub materiał nie rzuca cieni. Atrybuty i mundury, takie jak pozycja, są dostarczane przez Geometry i BufferGeometry, więc nie trzeba ich wyszukiwać w innych miejscach. Dokumenty Three.js zawierają niezłą sekcję na ten temat: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini
1
Proszę spojrzeć na jsfiddle.net/7k9so2fr poprawionego. Powiedziałbym, że to zachowanie wiązania tekstur nie jest tym, co próbuję osiągnąć :( ..
Ken Kin