使用three.js在3D模型之间转换顶点 [英] Transitioning vertices between 3D models with three.js

查看:160
本文介绍了使用three.js在3D模型之间转换顶点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现类似于以下方面的多边形吹塑和重新组装效果:

I am trying to achieve polygon blowing and reassembling effect similar to:

  • http://na.leagueoflegends.com/en/featured/skins/project-2016
  • https://tkmh.me/

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

In both of these examples, you can see how they are morphing / transitioning the vertices from one 3d model to another, resulting in a pretty cool effect. I have something similar working, but I can't wrap my head around how they are transitioning the vertices with velocity offsets (please refer to the first link and see how the particles don't simply map and ease to the new position, but rather do it with some angular offset):

因此,我将两个模型导入Three.js,选择一个具有较大vert数量的模型,并复制其几何图形,同时将第二个模型数据附加为属性:

So, I import my two models in Three.js, take the one that has bigger vert count and copy its geometry while attaching the second's model data as an attribute:

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

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

Then in my shaders I can simply transition between them like this:

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

这有效,但确实很乏味.顶点可以简单地从一个模型映射到另一个模型,而没有任何偏移,没有重力或您想要添加到混合中的任何东西.

This works, but is really dull and boring. The vertices simply map from one model to another, without any offsets, no gravity or whatever you want to throw into the mix.

此外,由于如果版本号不匹配,我将0附加到一个位置,所以未使用的位置会简单地缩放为vec4(0.0,0.0,0.0,1.0),这又会导致无聊和无聊的效果(在兔子和大象模型之间变形)

Also, since I am attaching 0 to a position if there is a vert number mismatch, the unused positions simply scale to vec4(0.0, 0.0, 0.0, 1.0), which results in, again, a dull and boring effect (here morphing between a bunny and elephant models)

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

(notice how the unused bunny vertices simply scale down to 0)

如何解决这样的问题?

此外,在英雄联盟链接中,该怎么做他们设法

Also, in the League of Legends link, how do they manage to

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

  1. Animate the vertices internally to the model while it is active on the screen

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

Apply different velocity and gravity to the particles when mapping them to the next model (when clicking on the arrows and transitioning)?

是否通过传递布尔属性?他们在改变targetPositions数组吗?任何帮助都超过了赞赏

Is it by passing a boolean attribute? Are they changing the targetPositions array? Any help is more than appreciated

推荐答案

这可行,但确实很乏味和无聊.顶点只是从一个模型映射到另一个模型,而没有任何偏移,没有重力或您想要添加到混合中的任何东西.

因此,您可以应用可以想象和编码的任何效果. 这不是您问题的确切答案,但这是您可以使用着色器进行操作的最简单的示例.剧透:此示例的结尾是工作示例的链接.

So you can apply any effects you can imagine and code. This is not the exact answer to your question, but this is the simplest motivating example of what you can do with shaders. Spoiler: the link to a working example is at the end of this answer.

让我们对此进行改造

进入此

在转换过程中出现有趣的蜂群粒子

with funny swarming particles during transition

我们将使用THREE.BoxBufferGeometry()和一些自定义属性:

We'll use THREE.BoxBufferGeometry() with some custom attributes:

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()函数中.

As you can see, all the magic happens in the vertex shader and its rtp2xyz() function.

最后,是动画功能的代码:

And in the end, the code of the function of animation:

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

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

Oh, and yes, we have a slider control in our page:

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

这是一个片段

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;">

这篇关于使用three.js在3D模型之间转换顶点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆