three.js - 3d obj布料模型的布料模拟
问题描述
我在 three.js 中看到过很多布料模拟。我发现它只用 2d 平面表面完成。但是有没有一种方法可以模拟像下面这样的 3d 布料模型..
平面 2d 模拟有很多教程,例如
下面给出了它们的代码......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<script src="../build/three.js"></script>
<script src="../src/OrbitControls.js"></script>
<script>
var params = {
enableWind: true,
tooglePins: togglePins
};
var DAMPING = 0.03;
var DRAG = 1 - DAMPING;
var MASS = 0.1;
var restDistance = 25;
var xSegs = 10;
var ySegs = 10;
var clothFunction = plane(restDistance * xSegs, restDistance * ySegs);
var cloth = new Cloth(xSegs, ySegs);
var GRAVITY = 981 * 1.4;
var gravity = new THREE.Vector3(0, -GRAVITY, 0).multiplyScalar(MASS);
var TIMESTEP = 18 / 1000;
var TIMESTEP_SQ = TIMESTEP * TIMESTEP;
var pins = [];
var windForce = new THREE.Vector3(0, 0, 0);
var tmpForce = new THREE.Vector3();
var lastTime;
function plane(width, height) {
return function(u, v, target) {
var x = (u - 0.5) * width;
var y = (v + 0.5) * height;
var z = 0;
target.set(x, y, z);
};
}
function Particle(x, y, z, mass) {
this.position = new THREE.Vector3();
this.previous = new THREE.Vector3();
this.original = new THREE.Vector3();
this.a = new THREE.Vector3(0, 0, 0); // acceleration
this.mass = mass;
this.invMass = 1 / mass;
this.tmp = new THREE.Vector3();
this.tmp2 = new THREE.Vector3();
// init
clothFunction(x, y, this.position); // position
clothFunction(x, y, this.previous); // previous
clothFunction(x, y, this.original);
}
// Force -> Acceleration
Particle.prototype.addForce = function(force) {
this.a.add(
this.tmp2.copy(force).multiplyScalar(this.invMass)
);
};
// Performs Verlet integration
Particle.prototype.integrate = function(timesq) {
var newPos = this.tmp.subVectors(this.position, this.previous);
newPos.multiplyScalar(DRAG).add(this.position);
newPos.add(this.a.multiplyScalar(timesq));
this.tmp = this.previous;
this.previous = this.position;
this.position = newPos;
this.a.set(0, 0, 0);
};
var diff = new THREE.Vector3();
function satisfyConstraints(p1, p2, distance) {
diff.subVectors(p2.position, p1.position);
var currentDist = diff.length();
if (currentDist === 0) return; // prevents division by 0
var correction = diff.multiplyScalar(1 - distance / currentDist);
var correctionHalf = correction.multiplyScalar(0.5);
p1.position.add(correctionHalf);
p2.position.sub(correctionHalf);
}
function Cloth(w, h) {
w = w || 10;
h = h || 10;
this.w = w;
this.h = h;
var particles = [];
var constraints = [];
var u, v;
// Create particles
for (v = 0; v <= h; v++) {
for (u = 0; u <= w; u++) {
particles.push(
new Particle(u / w, v / h, 0, MASS)
);
}
}
// Structural
for (v = 0; v < h; v++) {
for (u = 0; u < w; u++) {
constraints.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance
]);
constraints.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance
]);
}
}
for (u = w, v = 0; v < h; v++) {
constraints.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance
]);
}
for (v = h, u = 0; u < w; u++) {
constraints.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance
]);
}
this.particles = particles;
this.constraints = constraints;
function index(u, v) {
return u + v * (w + 1);
}
this.index = index;
}
function simulate(time) {
if (!lastTime) {
lastTime = time;
return;
}
var i, j, il, particles, particle, constraints, constraint;
// Aerodynamics forces
if (params.enableWind) {
var indx;
var normal = new THREE.Vector3();
var indices = clothGeometry.index;
var normals = clothGeometry.attributes.normal;
particles = cloth.particles;
for (i = 0, il = indices.count; i < il; i += 3) {
for (j = 0; j < 3; j++) {
indx = indices.getX(i + j);
normal.fromBufferAttribute(normals, indx);
tmpForce.copy(normal).normalize().multiplyScalar(normal.dot(windForce));
particles[indx].addForce(tmpForce);
}
}
}
for (particles = cloth.particles, i = 0, il = particles.length; i < il; i++) {
particle = particles[i];
particle.addForce(gravity);
particle.integrate(TIMESTEP_SQ);
}
// Start Constraints
constraints = cloth.constraints;
il = constraints.length;
for (i = 0; i < il; i++) {
constraint = constraints[i];
satisfyConstraints(constraint[0], constraint[1], constraint[2]);
}
// Floor Constraints
for (particles = cloth.particles, i = 0, il = particles.length; i < il; i++) {
particle = particles[i];
pos = particle.position;
if (pos.y < -250) {
pos.y = -250;
}
}
// Pin Constraints
for (i = 0, il = pins.length; i < il; i++) {
var xy = pins[i];
var p = particles[xy];
p.position.copy(p.original);
p.previous.copy(p.original);
}
}
/* testing cloth simulation */
var pinsFormation = [];
var pins = [6];
pinsFormation.push(pins);
pins = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
pinsFormation.push(pins);
pins = [0];
pinsFormation.push(pins);
pins = []; // cut the rope ;)
pinsFormation.push(pins);
pins = [0, cloth.w]; // classic 2 pins
pinsFormation.push(pins);
pins = pinsFormation[1];
function togglePins() {
pins = pinsFormation[~~(Math.random() * pinsFormation.length)];
}
var container, stats;
var camera, scene, renderer;
var clothGeometry;
var sphere;
var object;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0xcce0ff, 500, 10000);
// camera
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(1000, 50, 1500);
// lights
scene.add(new THREE.AmbientLight(0x666666));
var light = new THREE.DirectionalLight(0xdfebff, 1);
light.position.set(50, 200, 100);
light.position.multiplyScalar(1.3);
light.castShadow = true;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
var d = 300;
light.shadow.camera.left = -d;
light.shadow.camera.right = d;
light.shadow.camera.top = d;
light.shadow.camera.bottom = -d;
light.shadow.camera.far = 1000;
scene.add(light);
// cloth material
var loader = new THREE.TextureLoader();
var clothTexture = loader.load('textures/water/Water_1_M_Flow.jpg');
clothTexture.anisotropy = 16;
var clothMaterial = new THREE.MeshLambertMaterial({
map: clothTexture,
side: THREE.DoubleSide,
// wireframe: true,
// alphaTest: 0.5
});
// cloth geometry
clothGeometry = new THREE.ParametricBufferGeometry(clothFunction, cloth.w, cloth.h);
// cloth mesh
object = new THREE.Mesh(clothGeometry, clothMaterial);
object.position.set(0, 0, 0);
object.castShadow = true;
scene.add(object);
// object.customDepthMaterial = new THREE.MeshDepthMaterial({
// depthPacking: THREE.RGBADepthPacking,
// map: clothTexture,
// alphaTest: 0.5
// });
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
// controls
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.maxPolarAngle = Math.PI * 0.5;
controls.minDistance = 1000;
controls.maxDistance = 5000;
window.addEventListener('resize', onWindowResize, false);
}
//
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
requestAnimationFrame(animate);
var time = Date.now();
var windStrength = Math.cos(time / 7000) * 20 + 40;
windForce.set(Math.sin(time / 2000), Math.cos(time / 3000), Math.sin(time / 1000));
windForce.normalize();
windForce.multiplyScalar(windStrength);
simulate(time);
render();
}
function render() {
var p = cloth.particles;
for (var i = 0, il = p.length; i < il; i++) {
var v = p[i].position;
clothGeometry.attributes.position.setXYZ(i, v.x, v.y, v.z);
}
clothGeometry.attributes.position.needsUpdate = true;
clothGeometry.computeVertexNormals();
renderer.render(scene, camera);
}
</script>
</body>
</html>
我可以做一个网格就像挂布一样,当风吹来时,它们必须做出相应的反应。无论是使用 three.js 和 ammo.js 还是 cannon.js
解决方案
您发布的代码不会做衣服,因为它没有碰撞。ammo.js 中的代码会,但您需要自己生成衣服。
通常使用质量和弹簧模拟布料
M--s--M--s--M--s--M
|\ /|\ /|\ /|
| \ / | \ / | \ / |
s s s s s s s
| / \ | / \ | / \ |
|/ \|/ \|/ \|
M--s--M--s--M--s--M
|\ /|\ /|\ /|
| \ / | \ / | \ / |
s s s s s s s
| / \ | / \ | / \ |
|/ \|/ \|/ \|
M--s--M--s--M--s--M
上图是质量 (M) 和弹簧 (s) 的示意图。每个弹簧都连接在 2 个质量块之间,并试图防止质量块伸展得太远和太靠近。您需要 1000 多个质量和弹簧来模拟服装。
演示在飞机上的原因是因为它是最容易制作的演示。如果你想要衣服,你需要遍历衣服的多边形,然后生成质量和弹簧。此外,您需要将质量与服装模型中的相应顶点相关联,以便在模拟运行后您可以将质量的新位置应用回服装的顶点。
最重要的是,你需要在你要穿上衣服的角色身上发生碰撞,群众会与之发生碰撞,这样它们就不会进入身体并掉到地板上。大多数物理引擎都有一些经过优化的基元,如盒子、球体、胶囊、圆柱体。他们也可以使用通用多边形进行碰撞,但速度较慢,因此由您决定是否可以使用附加到模型的一些原始形状来进行碰撞,或者是否需要更高保真度使用多边形进行碰撞.
在任何一种情况下,每块布料添加的质量越多,布料看起来越好,但运行速度越慢,因此您必须决定在看起来不错和运行速度之间进行权衡。
ammo.js AFAICT 没有文档,只是说它是 Bullet Physics 的一个端口,文档在这里
我没有看到任何自定义布料的 JavaScript 演示。
这个 ammo.js 演示 似乎不适合衣服,因为如果显示的这些形状实际上是衣服,它们只会折叠成一堆,而不是像充气一样,但可能获得这种行为是一种设置。您需要深入研究文档和/或该示例
您需要将衣服几何体与人体/人体模型分开,将衣服变成柔软的身体或手动生成质量和弹簧,然后从网格或基元中制作人体/人体模型的硬体,以便保持穿上衣服。
如果我这样做的话,我会从一个软体球体内的硬体立方体开始,看看我需要多详细才能使球体表现得像衣服一样(折叠和折痕)
推荐阅读
- android - Android Studio 上对 Android TV 的 Play 商店支持
- sql-server - Gcp appengine 应用未正确连接到 Cloud sql 数据库
- sql - 如何从oracle表的json列中的数据列表类型中获取一些值
- node.js - 在 vue/express 上的 cors 请求失败
- reactjs - 如何在 Material UI Autocomplete 中向清除按钮图标添加/编辑功能?
- ios - 同时带有图像的多个 Apple 推送通知
- android - 将 @SuppressLint("RestrictedApi") 与 AndroidX 库一起使用是否安全?
- c# - 线程安全写入缓存到数据库的最佳模式是什么?
- android - Android Studio 无法找到 ADB,尽管它位于平台工具文件夹中
- c# - 如何实现特定于应用程序的配置?