如何使纹理始终面对相机..?


10

更新5

创建了另一个小提琴以显示预期的外观。添加了不可见的穹顶和魔方相机,并使用了环境贴图;就我而言,由于已经提到的原因,不应使用这些技术。


更新4

重要信息:请注意,目标网格后面有一个反射平面,用于观察纹理是否正确绑定到网格表面,这与我要解决的问题无关。


更新3

创建了一个新的小提琴以显示什么不是预期的行为

也许我应该重新表述我的问题,但是我缺乏准确地描述我要解决的问题的知识,请提供帮助。也许 .. ?)


更新2

(由于应用了代码段,因此已弃用。)


更新资料

好的..我添加了3种方法:

  • TransformUv接受几何图形,以及处理uv变换的变形器方法。回调为每个面接受一个uvs数组,并将对应Face3geometry.faces[]用作其参数。

  • MatcapTransformer 是执行matcap转换的uv变换处理程序回调。

  • 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;我可以找到用ShaderMaterial包装的GLSL。就像我说过的那样,“我对此缺乏知识,现在喝太多了。我相信three.js封装了GLSL足够好,也很轻巧,我认为我们可以使用该库来实现类似这样的事情,而无需自己处理GLSL。
肯坚

2
抱歉,我想到的唯一方法是通过GLSL,因为纹理始终在着色器中绘制,并且您试图更改默认的计算纹理位置的方式。您可能会有更好的运气在discourse.threejs.org上
Marquizzo

我可以证实,通过像素着色器是在GPU管线可解
Mosè地区Raguzzini

Answers:


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>
  

一个可行的选择:多维数据集映射

在这里,我修改了一个关于多维数据集映射的jsfiddle ,也许您正在寻找什么:

https://jsfiddle.net/v8efxdo7/

多维数据集将其脸部纹理投影到基础对象上,并且正在看着相机。

注意:灯光会随着旋转而变化,因为灯光和内部对象处于固定位置,而相机和投影立方体均围绕场景中心旋转。

如果仔细看一下示例,此技术并不完美,但是您要寻找的内容(应用于盒子)很棘手,因为立方体纹理的UV解开是十字形的,因此旋转UV本身不会投影技术的有效性和使用投影技术也有其缺点,因为投影机的物体形状和投影对象的形状都很重要。

只是为了更好地理解:在现实世界中,您在盒子的3d空间中会在哪里看到这种效果?我想到的唯一示例是在3D表面上的2D投影(例如视觉设计中的投影贴图)。


1
前者更多。您能否使用three.js做到这一点?我对GLSL不熟悉。我很好奇,如果显示的第一个动画中的立方体同时绕每个轴旋转,将会发生什么?在使用three.js提供实现之后,我将尝试看看我的问题是否得到解决。看起来很有希望:)
肯健

1
嗨,老兄,我添加了一个带有简单演示的Codepen,可以再现所需的着色器。
Mosè地区Raguzzini

我需要一些时间来检查它是否适合我的情况。
肯坚

1
它不会丢失变形,如果纹理始终面向相机,则当env没有灯光或材质没有投射阴影时,效果将始终为纯纹理。属性和制服(例如位置)GeometryBufferGeometry提供,因此您不必在其他位置检索它们。three.js所文档对此有一个很好的部分:threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Mosè地区Raguzzini

1
请看一看修订后的jsfiddle.net/7k9so2fr。我会说这种纹理绑定的行为不是我想要达到的目的:( ..
Ken Kin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.