使用 Three.js 在 3D 模型之间转换顶点

问题描述 投票:0回答:1

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

在这两个示例中,您可以看到它们如何将顶点从一个 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)

如何解决这样的问题?

此外,在英雄联盟链接中,他们是如何设法

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

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

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

three.js glsl shader buffer-geometry
1个回答
5
投票

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

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

让我们改变这个

进入这个

在过渡期间有有趣的蜂群粒子

我们将使用

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

这是一个片段

body{
  margin: 0;
}
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/[email protected]/build/three.module.js",
      "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
    }
  }
</script>
<input id="slider" type="range" min="0" max="1" step="0.01" value="0.5" style="position:absolute;width:300px;">
<script type="module">
import * as THREE from "three";
import {OrbitControls} from "three/addons/controls/OrbitControls.js";

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 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.BoxGeometry(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.MathUtils.randFloatSpread(6); 
  attrAmplitude[attr] = Math.random() * 5;
  attrFrequency[attr] = Math.random() * 5;
}
cubeGeom.setAttribute( 'phi', new THREE.BufferAttribute( attrPhi, 1 ) );
cubeGeom.setAttribute( 'theta', new THREE.BufferAttribute( attrTheta, 1 ) );
cubeGeom.setAttribute( 'speed', new THREE.BufferAttribute( attrSpeed, 1 ) );
cubeGeom.setAttribute( 'amplitude', new THREE.BufferAttribute( attrAmplitude, 1 ) );
cubeGeom.setAttribute( '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);
}
</script>

© www.soinside.com 2019 - 2024. All rights reserved.