Bagaimana membuat tekstur selalu menghadap kamera ..?

10

Perbarui 5

Dibuat biola lain untuk menunjukkan seperti apa yang diharapkan. Skydome yang tidak terlihat dan cubecamera ditambahkan dan peta lingkungan digunakan; dalam kasus saya, tidak satu pun dari teknik ini harus digunakan karena alasan yang telah disebutkan.


Perbarui 4

Penting: Harap perhatikan ada bidang reflektif di belakang mesh target yang untuk mengamati apakah teksturnya mengikat permukaan mesh dengan benar, itu tidak ada hubungannya dengan apa yang saya coba pecahkan.


Perbarui 3

Dibuat biola baru untuk menunjukkan apa yang BUKAN perilaku yang diharapkan

  • Kode

Mungkin saya harus mengulangi pertanyaan saya, tetapi saya tidak memiliki pengetahuan untuk menjelaskan secara akurat tentang apa yang saya coba selesaikan, tolong bantu .. (Panoramic-Transform-With-Texture-Looking-At-Direction-Locked-Onto-The-Camera mungkin .. ?)


Perbarui 2

(Sudah usang karena cuplikan kode diterapkan.)


Memperbarui

Oke .. Saya sudah menambahkan 3 metode:

  • TransformUvmenerima geometri, dan metode transformator yang menangani uv-transform. Callback menerima sebuah array Uvs untuk setiap wajah dan yang sesuai Face3dari geometry.faces[]sebagai parameternya.

  • MatcapTransformer adalah panggilan balik handler uv-transform untuk melakukan transformasi matcap.

    dan

  • fixTextureWhenRotateAroundZAxis bekerja seperti namanya.

Sejauh ini tidak ada fixTexture..metode yang dapat bekerja sama sekali, juga, fixTextureWhenRotateAroundXAxistidak diketahui. Masalahnya tetap belum terpecahkan, saya berharap apa yang baru saja ditambahkan dapat membantu Anda membantu saya.


Saya mencoba membuat tekstur jaring selalu menghadap kamera perspektif aktif, apa pun posisi relatifnya.

Untuk membangun kasus nyata adegan saya dan interaksinya akan cukup kompleks, saya membangun contoh minimal untuk menunjukkan niat saya.

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

Harap dicatat bahwa meskipun mesh itu sendiri berputar dalam demonstrasi ini, maksud saya sebenarnya adalah membuat kamera bergerak seperti mengorbit di sekitar mesh.

Saya telah menambahkan bingkai gambar untuk membuat gerakan lebih jelas. Seperti yang Anda lihat saya gunakan fixTextureWhenRotateAroundYAxisuntuk melakukannya dengan benar, tetapi itu hanya untuk sumbu y. Di mesh.rotation.ykode asli saya dihitung sesuatu seperti

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

Namun, saya tidak memiliki pengetahuan bagaimana melakukannya fixTextureWhenRotateAroundAllAxisdengan benar. Ada beberapa batasan untuk menyelesaikan ini:

  • CubeCamera / CubeMap tidak dapat digunakan karena mesin klien mungkin memiliki masalah kinerja

  • Jangan hanya membuat mesh lookAtkamera karena mereka akhirnya dari segala jenis geometri, tidak hanya bola; Trik seperti lookAtdan mengembalikan .quaterniondalam bingkai akan ok.

Tolong jangan salah paham bahwa saya meminta masalah XY karena saya tidak punya hak untuk mengekspos kode kepemilikan atau saya tidak perlu membayar upaya untuk membangun contoh minimal :)

Ken Kin
sumber
Apakah Anda tahu bahasa shader GLSL? Satu-satunya cara untuk mencapai efek ini adalah dengan menulis kustom shader yang mengesampingkan perilaku default koordinat UV.
Marquizzo
@Marquizzo Saya bukan ahli di GLSL, namun, saya telah menggali beberapa kode sumber three.js seperti WebGLRenderTargetCube; Saya dapat menemukan GLSL yang dibungkus dengan ShaderMaterial. Seperti yang saya katakan, saya kurang pengetahuan tentang hal ini dan akan terlalu banyak minum saat ini. Saya percaya three.js membungkus GLSL cukup baik dan juga cukup ringan yang saya pikir kita dapat mencapai hal-hal seperti ini menggunakan perpustakaan tanpa berurusan dengan GLSL sendiri.
Ken Kin
2
Maaf, tetapi satu-satunya cara saya bisa memikirkan untuk melakukan ini adalah melalui GLSL, karena tekstur selalu digambar dalam shader, dan Anda mencoba mengubah cara default posisi tekstur dihitung. Anda mungkin lebih beruntung mengajukan pertanyaan "bagaimana" ini di discourse.threejs.org
Marquizzo
Saya dapat mengonfirmasi, itu dapat dipecahkan dalam pipa GPU oleh pixel shader
Mosè Raguzzini

Jawaban:

7

Menghadap kamera akan terlihat seperti:

masukkan deskripsi gambar di sini

Atau, bahkan lebih baik, seperti dalam pertanyaan ini , di mana perbaikan yang berlawanan ditanyakan:

masukkan deskripsi gambar di sini

Untuk mencapai itu, Anda harus menyiapkan shader fragmen sederhana (seperti yang OP lakukan secara tidak sengaja):

Vertex shader

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

Shader fragmen

uniform vec2 size;
uniform sampler2D texture;

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

Sebuah tiruan bekerja dari shader dengan 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>
  

Alternatif yang layak: Pemetaan Kubus

Di sini saya telah memodifikasi jsfiddle tentang pemetaan kubus , mungkin yang Anda cari:

https://jsfiddle.net/v8efxdo7/

Kubus memproyeksikan tekstur wajahnya pada objek yang mendasarinya dan melihat ke kamera.

Catatan: lampu berubah dengan rotasi karena cahaya dan objek dalam berada di posisi tetap, sementara kamera dan kubus proyeksi berputar di sekitar tengah adegan.

Jika Anda perhatikan dengan teliti contohnya, teknik ini tidak sempurna, tetapi apa yang Anda cari (diterapkan pada kotak) itu rumit, karena UV yang membuka tekstur kubus berbentuk silang, memutar UV itu sendiri tidak akan menjadi efektif dan menggunakan teknik proyeksi juga memiliki kelemahan, karena bentuk objek proyektor dan bentuk subjek proyeksi penting.

Hanya untuk pemahaman yang lebih baik: di dunia nyata, di mana Anda melihat efek ini dalam ruang 3d pada kotak? Satu-satunya contoh yang muncul di pikiran saya adalah proyeksi 2D pada permukaan 3D (seperti pemetaan proyeksi dalam desain visual).

Mosè Raguzzini
sumber
1
Lebih dari yang sebelumnya. Bisakah Anda menggunakan three.js untuk melakukannya? Saya tidak terbiasa dengan GLSL. Dan saya ingin tahu apa yang akan terjadi jika kubus dalam animasi pertama yang Anda tampilkan berputar di sekitar setiap sumbu pada saat yang sama? Setelah Anda memberikan implementasi menggunakan three.js, saya akan mencoba dan melihat apakah pertanyaan saya terselesaikan. Tampak menjanjikan :)
Ken Kin
1
Halo, saya telah menambahkan codepen dengan demo sederhana mereproduksi shader yang Anda butuhkan.
Mosè Raguzzini
Saya perlu waktu untuk memeriksa apakah itu berfungsi untuk kasus saya.
Ken Kin
1
Itu tidak kehilangan morphing, jika tekstur selalu menghadap kamera, efeknya akan selalu tekstur polos, jika env tidak memiliki lampu atau bahan tidak membayangi bayangan. Atribut dan seragam seperti posisi disediakan oleh Geometry dan BufferGeometry sehingga Anda tidak harus mengambilnya di tempat lain. Dokumen Three.js memiliki bagian yang bagus tentang hal itu: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini
1
Silakan lihat jsfiddle.net/7k9so2fr dari yang direvisi. Saya akan mengatakan bahwa perilaku pengikatan tekstur ini bukan yang ingin saya capai :( ..
Ken Kin