javascript - 如何使纹理始终面向相机..?

标签 javascript three.js geometry texture-mapping uv-mapping

更新 5

创建了另一个 fiddle 来展示预期的样子。增加了一个隐形天幕和一个立方体摄像机,并使用了环境贴图;就我而言,由于已经提到的原因,这些技术都不应该被使用。

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

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

更新 3

创建了一个新的 fiddle 来展示什么不是预期的行为
  • 代码


  • 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 变换的更改器(mutator)方法。回调接受每个人脸的 uvs 数组和对应的 Face3geometry.faces[]作为它的参数。
  • MatcapTransformer是进行 matcap 变换的 uv-transform 处理程序回调。

  • 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 问题,因为我无权公开专有代码,或者我不必付出努力来构建一个最小的示例 :)

    最佳答案

    面对相机将如下所示:

    enter image description here

    或者,甚至更好,如 question ,要求相反的修复:

    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>
      


    一个可行的替代方案:立方体映射

    在这里,我修改了一个关于立方体映射的 jsfiddle,也许你在找什么:

    https://jsfiddle.net/v8efxdo7/

    立方体将其面部纹理转换到下面的对象上,并且它正在看着相机。

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

    如果你仔细看这个例子,这个技术并不完美,但是你要找什么(应用于一个盒子)是棘手的,因为立方体纹理的 UV 展开是十字形的,旋转 UV 本身不会有效并且使用投影技术也有其缺点,因为投影仪对象形状和投影对象形状很重要。

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

    关于javascript - 如何使纹理始终面向相机..?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58382081/

    相关文章:

    javascript - Threejs对象选择问题

    javascript - 我可以使用 Three.js 合并渲染过程中每一帧中的几何图形吗?

    javascript - Three.js - 相机的良好 z 距离,可以看到盒子的全景

    algorithm - 查找给定集合中的所有共线点

    javascript 正则表达式查找后面以替换字符

    javascript - 循环遍历数组

    javascript - 如何使用 AJAX post 方法从 PHP 的特定函数中检索 JSON

    javascript - 使用颜色选择器使用 jquery 更改颜色?

    css - 对齐半圆形 div 内的文本

    geometry - 如何将点投影到球体上