Как сделать текстуры всегда лицом к камере?

10

Обновление 5

Создана еще одна скрипка, чтобы показать, как будет выглядеть ожидаемый результат. Добавлены невидимый skydome и cubecamera, и используется карта окружения; в моем случае ни один из этих методов не должен использоваться по причинам, уже упомянутым.


Обновление 4

Важное замечание: Обратите внимание, что в задней части целевой сетки есть отражающая плоскость, которая предназначена для наблюдения, если текстура правильно привязывается к поверхности сетки, это не имеет ничего общего с тем, что я пытаюсь решить.


Обновление 3

Создана новая скрипка, чтобы показать, что НЕ является ожидаемым поведением

  • Код

Может быть, мне следует перефразировать мой вопрос, но мне не хватает знаний, чтобы точно описать, что я пытаюсь решить, пожалуйста, помогите .. (Панорамная трансформация с текстурой, глядя в направлении, заблокирована на камеру может быть .. ?)


Обновление 2

(Устарело, поскольку применяется фрагмент кода.)


Обновить

ОК .. Я добавил 3 метода:

  • TransformUvпринимает геометрию и метод преобразования, который обрабатывает ультрафиолетовое преобразование. Обратный вызов принимает массив uvs для каждого лица и соответствующий Face3из geometry.faces[]его параметров.

  • MatcapTransformer обратный вызов обработчика uv-transform для преобразования matcap

    а также

  • fixTextureWhenRotateAroundZAxis работает как то, что он назвал.

Пока что ни один из fixTexture..методов не может работать вообще, также fixTextureWhenRotateAroundXAxisне разобрался. Проблема остается нерешенной, хотелось бы, чтобы то, что было добавлено, могло бы помочь мне.


Я пытаюсь сделать так, чтобы текстура сетки всегда была направлена ​​на активную перспективную камеру, независимо от того, каковы относительные положения.

Для построения реального случая моей сцены и взаимодействия было бы довольно сложно, я построил минимальный пример, чтобы продемонстрировать свое намерение.

  • Код
    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>

Пожалуйста, обратите внимание, что, хотя сама сетка вращается в этой демонстрации, мое реальное намерение - заставить камеру двигаться как петля вокруг сетки.

Я добавил каркас, чтобы сделать движение более четким. Как вы видите, я использую, fixTextureWhenRotateAroundYAxisчтобы сделать это правильно, но это только для оси Y. В mesh.rotation.yмоем реальном коде рассчитывается что-то вроде

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

Тем не менее, мне не хватает знаний о том, как сделать fixTextureWhenRotateAroundAllAxisправильно. Есть некоторые ограничения для решения этой проблемы:

  • CubeCamera / CubeMap нельзя использовать, поскольку на клиентских машинах могут быть проблемы с производительностью

  • Не просто делайте сетку lookAtкамеры, поскольку они в конечном итоге имеют любую геометрию, не только сферы; трюки, как lookAtи восстановить .quaternionв кадре было бы хорошо.

Пожалуйста, не поймите меня неправильно, что я задаю проблему XY, поскольку у меня нет права выставлять проприетарный код, иначе мне не пришлось бы платить за создание минимального примера :)

Кен Кин
источник
Вы знаете язык шейдеров GLSL? Единственный способ добиться этого эффекта - написать собственный шейдер, который переопределяет поведение UV-координат по умолчанию.
Marquizzo
@Marquizzo Я не эксперт в GLSL, однако я выкопал некоторый исходный код three.js, например, WebGLRenderTargetCube; Я могу найти GLSL, завернутый в ShaderMaterial. Как я уже говорил, мне не хватает знаний об этом, и сейчас было бы слишком много пить. Я считаю, что three.js обернул GLSL достаточно хорошо и достаточно легко, так что я думал, что мы можем достичь таких вещей, используя библиотеку, не разбираясь с GLSL сами.
Кен Кин
2
Извините, но я могу думать только об этом через GLSL, так как текстуры всегда рисуются в шейдере, и вы пытаетесь изменить способ вычисления положения текстуры по умолчанию. Вы может повезти с просьбой такого рода «как» вопросы на discourse.threejs.org
Marquizzo
Я могу подтвердить, что это разрешается в конвейере GPU пиксельным шейдером
Mosè Raguzzini

Ответы:

7

Лицом к камере будет выглядеть так:

введите описание изображения здесь

Или даже лучше, как в этом вопросе , где задается противоположное исправление:

введите описание изображения здесь

Чтобы достичь этого, вам нужно установить простой фрагментный шейдер (как случайно сделал OP):

Вершинный шейдер

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

Фрагмент шейдера

uniform vec2 size;
uniform sampler2D texture;

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

Рабочий макет шейдера с 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>
  

Жизнеспособная альтернатива: Cube Mapping

Здесь я изменил jsfiddle для отображения куба , возможно, это то, что вы ищете:

https://jsfiddle.net/v8efxdo7/

Куб проецирует свою текстуру лица на нижележащий объект и смотрит на камеру.

Примечание: освещение меняется с вращением, потому что свет и внутренний объект находятся в фиксированном положении, а камера и проекционный куб вращаются вокруг центра сцены.

Если вы внимательно посмотрите на пример, эта техника не идеальна, но то, что вы ищете (применено к коробке), сложно, потому что разворачивание текстуры куба в ультрафиолетовой форме имеет крестообразную форму, вращение самого УФ не будет быть эффективным, и использование методов проецирования также имеет свои недостатки, поскольку имеет значение форма объекта проектора и форма объекта проецирования.

Просто для лучшего понимания: в реальном мире, где вы видите этот эффект в трехмерном пространстве на коробках? Единственный пример, который приходит мне в голову, - это двухмерная проекция на трехмерную поверхность (например, отображение проекций в визуальном дизайне).

Mosè Raguzzini
источник
1
Больше бывшего. Не могли бы вы использовать Three.js, чтобы сделать это? Я не знаком с GLSL. И мне интересно, что произойдет, если куб в первой анимации, которую вы показываете, вращается вокруг каждой оси в одно и то же время? После того, как вы предоставите свою реализацию с использованием three.js, я попробую посмотреть, будет ли решен мой вопрос. Выглядит многообещающе :)
Кен Кин
1
Привет, приятель, я добавил кодовую ручку с простой демонстрацией, воспроизводящей нужный шейдер.
Mosè Raguzzini
Мне нужно время, чтобы проверить, работает ли оно в моем случае.
Кен Кин
1
Он не теряет морфинг, если текстура всегда обращена к камере, эффект всегда будет простой текстуры, если в env нет источников света или материал не отбрасывает тени. Атрибуты и униформы, такие как положение , предоставляются Geometry и BufferGeometry, поэтому вам не нужно извлекать их в других местах. В документации Three.js есть хороший раздел об этом: threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè Raguzzini
1
Пожалуйста, посмотрите на jsfiddle.net/7k9so2fr исправленного. Я бы сказал, что такое поведение привязки текстур не то, чего я пытаюсь достичь :( ..
Кен Кин