ThreeJS / GLSL投影映射渐变

时间:2016-12-22 19:47:24

标签: opengl-es three.js glsl webgl

我正在尝试使用从CSS派生的渐变纹理将渐变纹理应用于模型。这个想法是用户可以调整渐变的停止/颜色,然后将渐变应用于模型以匹配当前相机视图的旋转。我很难理解如何实现像this tutorial这样的东西。

我创建了一个非常简单的示例,其中包含硬编码的渐变图像和Suzanne the monkey,您可以在此处找到:

https://github.com/abogartz/projection-mapping

(要运行此功能,您可以使用提供的浏览器同步设置或仅在index.html上运行简单服务器)

现在,Suzanne模型根据自己的UV应用纹理。这会产生一个在脸部不是线性的渐变:

enter image description here

我想要的是使用“投影映射”,其中渐变从最左边的顶点开始并在最右边结束,无论相机如何旋转(我将相机矩阵保存在用户动作和以后用它作为制服。)

结果应该更像这样(当然还有照明等)

enter image description here

我当前的着色器看起来像这样:

<script id='fragmentShader' type='x-shader/x-fragment'>
uniform vec2 u_mouse;
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D u_gradient_tex;
varying vec2 vUv;

void main() {
  gl_FragColor = texture2D(u_gradient_tex,vUv);
}

显然,vUv变化不是我想要的,所以我该如何计算投影坐标呢?

2 个答案:

答案 0 :(得分:1)

看看这个,我为你创建了这个,这样你就可以看到如何实现这个的粗略想法:

http://glslsandbox.com/e#37464.0

我用圆圈完成了这项工作,但你也可以用模型做到这一点。 基本上您需要做的就是将gl_FragColor = texture2D(u_gradient_tex,vUv);更改为gl_FragColor = texture2D(u_gradient_tex,gl_FragCoord.xy * some_scaling_factor);

这会将纹理映射更改为依赖于FragCoord而不是模型UV。

答案 1 :(得分:1)

我认为没有“简单”的方法可以做你想做的事。如果希望渐变始终从模型的左边缘延伸到模型的右边缘而不考虑方向,则需要从该透视图/摄像机角度计算最左侧和最右侧顶点位置。否则,渐变将没有锚点(在左侧)并且没有宽度(伸展到适合的距离)

这里稍微描述了典型的投影映射

Programatically generate simple UV Mapping for models

您需要投影仪的位置,然后从投影仪投影到网格上的点以生成UV坐标。在您的情况下,投影机可以始终是相机,因此您可以忽略该部分。你需要使用平面映射,但你需要计算最左边的顶点和最右边的位置,这样你就可以对齐投影,使其与3D模型的轮廓相匹配。

如果您只想要一个带有轮廓的模型,您可以将背景设置为CSS渐变,然后清除为黑色,然后使用模型绘制0,0,0,0来绘制一个洞。

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, 1, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer({alpha: true});
document.body.appendChild( renderer.domElement );

renderer.setClearColor(0x000000);

var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { 
  color: 0x000000, 
  opacity: 0,
} );
var cube = new THREE.Mesh( geometry, material );
scene.add( cube );

camera.position.z = 2;

function resize() {
  var canvas = renderer.domElement;
  var width = canvas.clientWidth;
  var height = canvas.clientHeight;
  if (canvas.width !== width || canvas.height !== height) {
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function render(time) {
  time *= 0.001;  // convert to seconds;
  resize();
  
  cube.position.z = Math.sin(time);
  cube.rotation.x = time * 0.817;
  cube.rotation.y = time * 0.923;
  
  renderer.render( scene, camera );
  requestAnimationFrame( render );
}
requestAnimationFrame( render );
body { margin: 0; }
canvas { 
  width: 100vw; 
  height: 100vh; 
  display: block;
  background: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(255,191,0,1) 23%, rgba(34,255,0,1) 41%, rgba(0,64,255,1) 55%, rgba(170,0,255,1) 75%, rgba(255,0,0,1) 100%);

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.min.js"></script>