Wie mache ich eine Textur immer vor der Kamera ..?

10

Update 5

Erstellt eine weitere Geige, um zu zeigen, wie erwartet wird. Ein unsichtbarer Fallschirm und eine Cubecamera werden hinzugefügt und die Umgebungskarte wird verwendet. In meinem Fall sollte keine dieser Techniken aus den bereits genannten Gründen verwendet werden.


Update 4

Wichtig: Bitte beachten Sie, dass sich hinter dem Zielnetz eine reflektierende Ebene befindet, mit der beobachtet werden kann, ob die Textur korrekt an die Netzoberfläche gebunden ist. Dies hat nichts mit dem zu tun, was ich zu lösen versuche.


Update 3

Erstellt eine neue Geige, um zu zeigen, was NICHT das erwartete Verhalten ist

  • Code

Vielleicht sollte ich meine Frage umformulieren, aber mir fehlt das Wissen, um genau zu beschreiben, was ich zu lösen versuche. Bitte helfen Sie mir könnte sein .. ?)


Update 2

(Ist veraltet, da ein Code-Snippet angewendet wird.)


Aktualisieren

OK .. Ich habe 3 Methoden hinzugefügt:

  • TransformUvAkzeptiert eine Geometrie und eine Transformatormethode, die die UV-Transformation handhabt. Die Callback akzeptiert einen UVS - Array für jede Fläche und den entsprechend Face3der geometry.faces[]als Parameter.

  • MatcapTransformer ist der Rückruf des UV-Transformations-Handlers für die Matcap-Transformation.

    und

  • fixTextureWhenRotateAroundZAxis funktioniert wie es heißt.

Bisher kann keine der fixTexture..Methoden insgesamt funktionieren, auch fixTextureWhenRotateAroundXAxisist nicht herausgefunden. Das Problem bleibt ungelöst. Ich wünschte, das, was gerade hinzugefügt wurde, könnte Ihnen helfen, mir zu helfen.


Ich versuche, die Textur eines Netzes immer einer aktiven perspektivischen Kamera zuzuordnen, unabhängig von den relativen Positionen.

Um einen realen Fall meiner Szene zu konstruieren und die Interaktion wäre ziemlich komplex, habe ich ein minimales Beispiel erstellt, um meine Absicht zu demonstrieren.

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

Bitte beachten Sie, dass sich das Netz in dieser Demonstration zwar dreht, meine eigentliche Absicht jedoch darin besteht, die Kamera so zu bewegen, als würde sie um das Netz kreisen.

Ich habe das Drahtmodell hinzugefügt, um die Bewegung klarer zu machen. Wie Sie sehen können fixTextureWhenRotateAroundYAxis, mache ich es richtig, aber es ist nur für die y-Achse. Der mesh.rotation.yin meinem realen Code berechnete so etwas wie

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

Mir fehlt jedoch das Wissen, wie man es fixTextureWhenRotateAroundAllAxisrichtig macht. Es gibt einige Einschränkungen bei der Lösung dieses Problems:

  • CubeCamera / CubeMap kann nicht verwendet werden, da auf den Clientcomputern möglicherweise Leistungsprobleme auftreten

  • Machen Sie das Netz nicht einfach lookAtzur Kamera, da es letztendlich irgendeine Art von Geometrie hat, nicht nur die Kugeln. Tricks wie lookAtund Wiederherstellung .quaternionin einem Frame wäre in Ordnung .

Verstehen Sie mich bitte nicht falsch, dass ich ein XY-Problem stelle, da ich nicht das Recht habe, proprietären Code verfügbar zu machen, oder ich müsste nicht die Mühe bezahlen, um ein minimales Beispiel zu erstellen :)

Ken Kin
quelle
Kennen Sie die GLSL-Shader-Sprache? Die einzige Möglichkeit, diesen Effekt zu erzielen, besteht darin, einen benutzerdefinierten Shader zu schreiben, der das Standardverhalten von UV-Koordinaten überschreibt.
Marquizzo
@Marquizzo Ich bin kein Experte bei GLSL, habe jedoch einen Quellcode von three.js wie WebGLRenderTargetCube ausgegraben. Ich kann die mit ShaderMaterial verpackte GLSL finden. Wie ich schon sagte, fehlt mir das Wissen darüber und es wäre im Moment zu viel zu trinken. Ich glaube, dass three.js GLSL gut genug und auch leicht genug verpackt hat, dass ich dachte, wir können solche Dinge mithilfe der Bibliothek erreichen, ohne uns selbst mit GLSL zu befassen.
Ken Kin
2
Entschuldigung, aber ich kann mir das nur über GLSL vorstellen, da die Texturen immer im Shader gezeichnet werden und Sie versuchen, die Standardmethode für die Berechnung der Texturposition zu ändern. Vielleicht haben Sie mehr Glück haben , diese Art von „how to“ Fragen Vorstellung discourse.threejs.org
Marquizzo
Ich kann bestätigen, dass dies in der GPU-Pipeline durch einen Pixel-Shader lösbar ist
Mosè Raguzzini

Antworten:

7

Der Blick auf die Kamera sieht folgendermaßen aus:

Geben Sie hier die Bildbeschreibung ein

Oder noch besser, wie in dieser Frage , in der das Gegenteil gefragt wird:

Geben Sie hier die Bildbeschreibung ein

Um dies zu erreichen, müssen Sie einen einfachen Fragment-Shader einrichten (wie es das OP versehentlich getan hat):

Vertex-Shader

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

Fragment Shader

uniform vec2 size;
uniform sampler2D texture;

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

Ein funktionierender Mock des Shaders mit 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>
  

Eine praktikable Alternative: Cube Mapping

Hier habe ich eine jsfiddle über Cube-Mapping geändert. Vielleicht ist es das, wonach Sie suchen:

https://jsfiddle.net/v8efxdo7/

Der Würfel projiziert seine Gesichtsstruktur auf das darunter liegende Objekt und schaut in die Kamera.

Hinweis: Das Licht ändert sich mit der Drehung, da sich Licht und inneres Objekt in einer festen Position befinden, während sich Kamera und Projektionswürfel beide um die Mitte der Szene drehen.

Wenn Sie sich das Beispiel genau ansehen, ist diese Technik nicht perfekt, aber was Sie suchen (auf eine Box angewendet), ist schwierig, da das UV-Entpacken der Textur eines Würfels kreuzförmig ist und das Drehen des UV selbst nicht funktioniert Effektiv zu sein und Projektionstechniken zu verwenden, hat auch seine Nachteile, da die Form des Projektorobjekts und die Form des Projektionsobjekts eine Rolle spielen.

Nur zum besseren Verständnis: Wo sehen Sie diesen Effekt in der realen Welt im 3D-Raum auf Boxen? Das einzige Beispiel, das mir in den Sinn kommt, ist eine 2D-Projektion auf eine 3D-Oberfläche (wie Projektionsmapping im visuellen Design).

Mosè Raguzzini
quelle
1
Mehr von dem ersteren. Würden Sie bitte three.js verwenden, um dies zu tun? Ich bin mit GLSL nicht vertraut. Und ich bin gespannt, was passiert, wenn sich der Würfel in der ersten Animation, die Sie zeigen, gleichzeitig um jede Achse dreht? Nachdem Sie Ihre Implementierung mit three.js bereitgestellt haben, werde ich versuchen, zu prüfen, ob meine Frage gelöst wird. Sieht vielversprechend aus :)
Ken Kin
1
Hallo Kumpel, ich habe einen Codepen mit einer einfachen Demo hinzugefügt, die den benötigten Shader reproduziert.
Mosè Raguzzini
Ich brauche etwas Zeit, um zu überprüfen, ob es für meinen Fall funktioniert.
Ken Kin
1
Das Morphing geht nicht verloren. Wenn die Textur immer zur Kamera zeigt, ist der Effekt immer eine einfache Textur, wenn die Umgebung keine Lichter hat oder das Material keine Schatten wirft. Attribute und Uniformen wie die Position werden von Geometry und BufferGeometry bereitgestellt, sodass Sie sie nicht an anderen Stellen abrufen müssen. Three.js docs haben einen schönen Abschnitt darüber: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini
1
Bitte werfen Sie einen Blick auf jsfiddle.net/7k9so2fr des überarbeiteten. Ich würde sagen, dass dieses Verhalten der Texturbindung nicht das ist, was ich erreichen möchte :( ..
Ken Kin