首页 > 解决方案 > 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

标签: three.jswebglcannon.jsammo.jscloth-simulation

解决方案


您发布的代码不会做衣服,因为它没有碰撞。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 演示 似乎不适合衣服,因为如果显示的这些形状实际上是衣服,它们只会折叠成一堆,而不是像充气一样,但可能获得这种行为是一种设置。您需要深入研究文档和/或该示例

您需要将衣服几何体与人体/人体模型分开,将衣服变成柔软的身体或手动生成质量和弹簧,然后从网格或基元中制作人体/人体模型的硬体,以便保持穿上衣服。

如果我这样做的话,我会从一个软体球体内的硬体立方体开始,看看我需要多详细才能使球体表现得像衣服一样(折叠和折痕)


推荐阅读