【问题标题】:Transitioning vertices between 3D models with three.js使用 three.js 在 3D 模型之间转换顶点
【发布时间】:2017-11-10 14:46:06
【问题描述】:

我正在尝试实现类似于以下的多边形吹散和重组效果:

在这两个示例中,您可以看到它们如何将顶点从一个 3d 模型变形/过渡到另一个,从而产生非常酷的效果。我有类似的工作,但我无法理解它们如何通过速度偏移来转换顶点(请参阅第一个链接,看看粒子如何不简单地映射和缓和到新位置,而是做一些角度偏移):

所以,我在 Three.js 中导入我的两个模型,取一个具有较大顶点数的模型并复制其几何图形,同时将第二个模型数据作为属性附加:

class CustomGeometry extends THREE.BufferGeometry {
  constructor (geometry1, geometry2) {
    super()

    let { count } = geometry1.attributes.position

    // this will hold
    let targetArr = new Float32Array(count * 3)
    let morphFactorArr = new Float32Array(count)

    for (let i = 0; i < count; i += 3) {
      targetArr[i + 0] = geometry2.attributes.position.array[i + 0] || 0
      targetArr[i + 1] = geometry2.attributes.position.array[i + 1] || 0
      targetArr[i + 2] = geometry2.attributes.position.array[i + 2] || 0

      let rand = Math.random()
      morphFactorArr[i + 0] = rand
      morphFactorArr[i + 1] = rand
      morphFactorArr[i + 2] = rand
    }
    this.addAttribute('a_target',      new THREE.BufferAttribute(targetArr, 3))
    this.addAttribute('a_morphFactor', new THREE.BufferAttribute(morphFactorArr, 1))
    this.addAttribute('position', geometry1.attributes.position)
  }
}

然后在我的着色器中,我可以像这样简单地在它们之间转换:

vec3 new_position = mix(position, a_targetPosition, a_morphFactor);

这行得通,但真的很枯燥乏味。顶点只是简单地从一个模型映射到另一个模型,没有任何偏移、没有重力或任何你想加入的东西。

此外,由于如果顶点数不匹配,我会将 0 附加到某个位置,因此未使用的位置会简单地缩放到 vec4(0.0, 0.0, 0.0, 1.0),这会再次导致沉闷和无聊的效果 (这里是兔子和大象模型之间的变形)

(注意未使用的兔子顶点如何简单地缩小到 0)

如何解决这样的问题?

还有,在League of Legends link,他们是怎么做到的

  1. 当模型在屏幕上处于活动状态时,在模型内部对顶点进行动画处理

  2. 在将粒子映射到下一个模型时(单击箭头和过渡时)对粒子应用不同的速度和重力?

是通过传递布尔属性吗?他们是否在更改 targetPositions 数组?任何帮助都非常感谢

【问题讨论】:

    标签: three.js glsl shader buffer-geometry


    【解决方案1】:

    这行得通,但真的很无聊。顶点只是简单地从一个模型映射到另一个模型,没有任何偏移、没有重力或任何你想加入的东西。

    因此您可以应用您可以想象和编码的任何效果。 这不是您问题的确切答案,但这是您可以使用着色器做什么的最简单的激励示例。剧透:一个工作示例的链接在这个答案的末尾。

    让我们改变它

    进入这个

    在过渡期间带有有趣的蜂拥粒子

    我们将使用带有一些自定义属性的THREE.BoxBufferGeometry()

    var sideLenght = 10;
    var sideDivision = 50;
    var cubeGeom = new THREE.BoxBufferGeometry(sideLenght, sideLenght, sideLenght, sideDivision, sideDivision, sideDivision);
    var attrPhi = new Float32Array( cubeGeom.attributes.position.count );
    var attrTheta = new Float32Array( cubeGeom.attributes.position.count );
    var attrSpeed = new Float32Array( cubeGeom.attributes.position.count );
    var attrAmplitude = new Float32Array( cubeGeom.attributes.position.count );
    var attrFrequency = new Float32Array( cubeGeom.attributes.position.count );
    for (var attr = 0; attr < cubeGeom.attributes.position.count; attr++){
        attrPhi[attr] = Math.random() * Math.PI * 2;
      attrTheta[attr] = Math.random() * Math.PI * 2;
      attrSpeed[attr] = THREE.Math.randFloatSpread(6);  
      attrAmplitude[attr] = Math.random() * 5;
      attrFrequency[attr] = Math.random() * 5;
    }
    cubeGeom.addAttribute( 'phi', new THREE.BufferAttribute( attrPhi, 1 ) );
    cubeGeom.addAttribute( 'theta', new THREE.BufferAttribute( attrTheta, 1 ) );
    cubeGeom.addAttribute( 'speed', new THREE.BufferAttribute( attrSpeed, 1 ) );
    cubeGeom.addAttribute( 'amplitude', new THREE.BufferAttribute( attrAmplitude, 1 ) );
    cubeGeom.addAttribute( 'frequency', new THREE.BufferAttribute( attrFrequency, 1 ) );
    

    THREE.ShaderMaterial():

    var vertexShader = [
    "uniform float interpolation;",
    "uniform float radius;",
    "uniform float time;",
    "attribute float phi;",
    "attribute float theta;",
    "attribute float speed;",
    "attribute float amplitude;",
    "attribute float frequency;",
    
    "vec3 rtp2xyz(){ // the magic is here",
    " float tmpTheta = theta + time * speed;",
    " float tmpPhi = phi + time * speed;",
    " float r = sin(time * frequency) * amplitude * sin(interpolation * 3.1415926);",
    " float x = sin(tmpTheta) * cos(tmpPhi) * r;",
    " float y = sin(tmpTheta) * sin(tmpPhi) * r;",
    " float z = cos(tmpPhi) * r;",
    " return vec3(x, y, z);",
    "}",
    
    "void main(){",
    " vec3 newPosition = mix(position, normalize(position) * radius, interpolation);",
    " newPosition += rtp2xyz();",
    "   vec4 mvPosition = modelViewMatrix * vec4( newPosition, 1.0 );",
    "   gl_PointSize = 1. * ( 1. / length( mvPosition.xyz ) );",
    "   gl_Position = projectionMatrix * mvPosition;",
    "}"
    ].join("\n");
    
    var fragmentShader = [
    "uniform vec3 color;",
    "void main(){",
    "   gl_FragColor = vec4( color, 1.0 );",
    "}"
    ].join("\n");
    
    var uniforms = {
        interpolation: { value: slider.value},
      radius: { value: 7.5},
      color: { value: new THREE.Color(0x00ff00)},
      time: { value: 0 }
    }
    
    var shaderMat = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      //wireframe: true //just in case, if you want to use THREE.Mesh() instead of THREE.Points()
    });
    

    如您所见,所有魔法都发生在顶点着色器及其rtp2xyz() 函数中。

    最后是动画功能的代码:

    var clock = new THREE.Clock();
    var timeVal = 0;
    
    render();
    function render(){
        timeVal += clock.getDelta();
        requestAnimationFrame(render);
      uniforms.time.value = timeVal;
      uniforms.interpolation.value = slider.value;
      renderer.render(scene, camera);
    }
    

    哦,是的,我们的页面中有一个滑块控件:

    <input id="slider" type="range" min="0" max="1" step="0.01" value="0.5" style="position:absolute;width:300px;">
    

    这是一个sn-p

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.set(10, 10, 20);
    var renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
    
    var controls = new THREE.OrbitControls(camera, renderer.domElement);
    
    var vertexShader = [
    "uniform float interpolation;",
    "uniform float radius;",
    "uniform float time;",
    "attribute float phi;",
    "attribute float theta;",
    "attribute float speed;",
    "attribute float amplitude;",
    "attribute float frequency;",
    
    "vec3 rtp2xyz(){ // the magic is here",
    " float tmpTheta = theta + time * speed;",
    " float tmpPhi = phi + time * speed;",
    " float r = sin(time * frequency) * amplitude * sin(interpolation * 3.1415926);",
    " float x = sin(tmpTheta) * cos(tmpPhi) * r;",
    " float y = sin(tmpTheta) * sin(tmpPhi) * r;",
    " float z = cos(tmpPhi) * r;",
    " return vec3(x, y, z);",
    "}",
    
    "void main(){",
    " vec3 newPosition = mix(position, normalize(position) * radius, interpolation);",
    " newPosition += rtp2xyz();",
    "	vec4 mvPosition = modelViewMatrix * vec4( newPosition, 1.0 );",
    "	gl_PointSize = 1. * ( 1. / length( mvPosition.xyz ) );",
    "	gl_Position = projectionMatrix * mvPosition;",
    "}"
    ].join("\n");
    
    var fragmentShader = [
    "uniform vec3 color;",
    "void main(){",
    "	gl_FragColor = vec4( color, 1.0 );",
    "}"
    ].join("\n");
    
    var uniforms = {
    	interpolation: { value: slider.value},
      radius: { value: 7.5},
      color: { value: new THREE.Color(0x00ff00)},
      time: { value: 0 }
    }
    
    var sideLenght = 10;
    var sideDivision = 50;
    var cubeGeom = new THREE.BoxBufferGeometry(sideLenght, sideLenght, sideLenght, sideDivision, sideDivision, sideDivision);
    var attrPhi = new Float32Array( cubeGeom.attributes.position.count );
    var attrTheta = new Float32Array( cubeGeom.attributes.position.count );
    var attrSpeed = new Float32Array( cubeGeom.attributes.position.count );
    var attrAmplitude = new Float32Array( cubeGeom.attributes.position.count );
    var attrFrequency = new Float32Array( cubeGeom.attributes.position.count );
    for (var attr = 0; attr < cubeGeom.attributes.position.count; attr++){
    	attrPhi[attr] = Math.random() * Math.PI * 2;
      attrTheta[attr] = Math.random() * Math.PI * 2;
      attrSpeed[attr] = THREE.Math.randFloatSpread(6);	
      attrAmplitude[attr] = Math.random() * 5;
      attrFrequency[attr] = Math.random() * 5;
    }
    cubeGeom.addAttribute( 'phi', new THREE.BufferAttribute( attrPhi, 1 ) );
    cubeGeom.addAttribute( 'theta', new THREE.BufferAttribute( attrTheta, 1 ) );
    cubeGeom.addAttribute( 'speed', new THREE.BufferAttribute( attrSpeed, 1 ) );
    cubeGeom.addAttribute( 'amplitude', new THREE.BufferAttribute( attrAmplitude, 1 ) );
    cubeGeom.addAttribute( 'frequency', new THREE.BufferAttribute( attrFrequency, 1 ) );
    
    var shaderMat = new THREE.ShaderMaterial({
    	uniforms: uniforms,
    	vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      //wireframe: true
    });
    var points = new THREE.Points(cubeGeom, shaderMat);
    scene.add(points);
    
    var clock = new THREE.Clock();
    var timeVal = 0;
    
    render();
    function render(){
    	timeVal += clock.getDelta();
    	requestAnimationFrame(render);
      uniforms.time.value = timeVal;
      uniforms.interpolation.value = slider.value;
      renderer.render(scene, camera);
    }
    body{
      margin: 0;
    }
    <script src="https://threejs.org/build/three.min.js"></script>
    <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
    <input id="slider" type="range" min="0" max="1" step="0.01" value="0.5" style="position:absolute;width:300px;">

    【讨论】:

    • 仅供参考:multiline template literals 适用于所有支持 WebGL 的浏览器
    • 嘿,非常感谢您的详细回答!我的大脑正在融化我该如何做new_position = vec3(something),然后在下一行向它添加另一个值......因为我在上面的行中使用= 符号设置它。然后,我看到您实际上使用了rtp2xyz() 中的插值以及,它开始变得有意义....基本上我可以对位置做任何事情,添加柏林噪声、向量场等等不,只要我使用interpolation 值作为偏移量.. 对吗?
    • 另外,您能否详细说明:sin(a_morphFactor * 3.1415926) * 2.0?为什么要乘以 PI?
    • 因为sin(0)sin(PI) 等于0(当a_morphFactor 是从0 到1 时),当a_morphFactor 是0.5 时,那么我们将在sin(PI * 0.5)
    • 感谢您的帮助,我想我现在真的明白了!已经有东西在运行
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-03
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    • 2019-05-19
    • 1970-01-01
    相关资源
    最近更新 更多