3d obj 布料模型的布料模拟

Cloth Simulaltion of a 3d obj cloth model

我在three.js看过很多布料模拟。我发现它只适用于 2d 平面。但是有没有一种方法可以模拟像下面这样的 3d 布料模型..

有很多关于平面二维模拟的教程,比如

下面给出了它们的代码...



<!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中的代码可以,但是你需要自己生成衣服。

布料通常使用质量和 springs

来模拟
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)和springs(s)的示意图。每个 spring 都连接在 2 个质量之间,并试图防止质量伸展太远或太近。您需要 1000 秒的质量和 spring 秒来模拟衣服。

演示在平面上的原因是因为它是最容易制作的演示。如果你想要衣服,你需要走衣服的多边形,然后生成质量和 springs。此外,您需要将质量与其在服装模型中的相应顶点相关联,以便在模拟后 运行s 您可以将质量的新位置应用回服装的顶点。

最重要的是,你需要在你要穿上衣服的角色的 body 上发生碰撞,群众会与之发生碰撞,这样他们就不会进入 body然后掉在地板上。大多数物理引擎都有一些优化的原语,如盒子、球体、胶囊、圆柱体。他们也可以使用通用多边形进行碰撞,但它们速度较慢,因此您可以决定是否可以使用附加到模型的一些原始形状来进行碰撞,或者您是否需要使用多边形进行碰撞的更高保真度.

在任何一种情况下,你在每块布料上添加的质量越多,布料看起来就越好,但速度越慢 运行 所以你必须决定在好看和 [=48 之间进行权衡=]宁快。

ammo.js AFAICT 没有记录,只是说它是 Bullet Physics 的一个端口,其文档是 here

我没有看到任何 JavaScript 定制布料的演示。

This ammo.js demo 似乎不适合衣服,因为如果显示的那些形状实际上是衣服,它们就会塌陷成一堆,而不是像充气一样,但也许获得这种行为是一种设置。您需要深入了解 the docs and/or that sample

您需要将衣服的几何形状与人体 body / 人体模型分开,将衣服变成柔软的 body 或手动生成质量和 springs 和然后也用网格或基元使 human/manekin 变硬 body 以便它支撑衣服。

如果我这样做的话,我会先在柔软的 body 球体中放置一个坚硬的 body 立方体,然后看看我需要多精细地制作球体才能使其表现得像衣服一样(折叠和折痕)

Popup Dev 我一直在使用它,但发现使用平面几何来操作要容易得多。我也有关于原始布示例如何计算和使用 'wind' 的问题。在推特上联系我,让我们讨论。