Как сделать текстуры всегда лицом к камере? - PullRequest
10 голосов
/ 14 октября 2019

Обновление 5

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

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 cubeCam = new THREE.CubeCamera(.1, 200, 4096);
  cubeCam.renderTarget.texture.wrapS = THREE.RepeatWrapping;
  cubeCam.renderTarget.texture.wrapT = THREE.RepeatWrapping;
  cubeCam.renderTarget.texture.center.set(1 / 2, 1 / 2);

  var geoSky = new THREE.SphereGeometry(2, 16, 16);
  var matSky = new THREE.MeshBasicMaterial({
    'map': texture,
    'side': THREE.BackSide
  });
  var meshSky = new THREE.Mesh(geoSky, matSky);
  meshSky.visible = false;

  var geometry = new THREE.IcosahedronGeometry(1, 1);
  var material = new THREE.MeshBasicMaterial({
    'envMap': cubeCam.renderTarget.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);
  scene.add(meshSky);

  {
    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 controls = new THREE.TrackballControls(camera, container);

  var fixTextureWhenRotateAroundAllAxis = function() {
    mesh.rotation.y += 0.01;
    mesh.rotation.x += 0.01;
    mesh.rotation.z += 0.01;

    cubeCam.update(renderer, scene);
  };

  renderer.setAnimationLoop(function() {
    // controls.update();

    plane.visible = false;

    {
      meshSky.visible = true;
      mesh.visible = false;

      fixTextureWhenRotateAroundAllAxis();

      mesh.visible = true;

      meshSky.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>

Обновление 4

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


Обновление 3

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

  • Код

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);
		}
	};
};

	var getVertexShader=function() {
		return `
void main() {
	gl_Position=projectionMatrix*modelViewMatrix*vec4(position, 1.0);
}
`;
	};

	var getFragmentShader=function(size) {
		return `
uniform sampler2D texture1;
const vec2 size=vec2(`+size.x+`, `+size.y+`);

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


(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 geometry=new THREE.BoxGeometry(2, 2, 2);

	// var material=new THREE.MeshBasicMaterial({ 'map': texture });
	var material=new THREE.ShaderMaterial({
		'uniforms': { 'texture1': { 'type': 't', 'value': texture } }
		, 'vertexShader': getVertexShader()
		, 'fragmentShader': getFragmentShader({ 'x': 512, 'y': 256 })
	});

	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);
	};

	var fixTextureWhenRotateAroundAllAxis=function() {
		mesh.rotation.y+=0.01;
		mesh.rotation.x+=0.01;
		mesh.rotation.z+=0.01;
	};
  
	var controls=new THREE.TrackballControls(camera, container);

	renderer.setAnimationLoop(function() {
			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>

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


Обновление 2

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


Обновление

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

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

  • MatcapTransformer - это обратный вызов обработчика uv-преобразования для преобразования 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, поскольку у меня нет права выставлять закрытый код, иначе мне не пришлось бы платить запостроить минимальный пример:)

1 Ответ

7 голосов
/ 17 октября 2019

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

enter image description here

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

enter image description here

Чтобы добиться этого, вам нужно установить простой фрагментный шейдер (как случайно сделал 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/

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

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

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

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

...