Ткань имитация 3D-модели ткани.

Я видел много симуляций ткани в three.js . Я обнаружил, что это делается только с плоскими двумерными поверхностями. Но есть ли способ смоделировать трехмерную модель ткани, подобную показанной ниже?

Есть много руководств по моделированию 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

2 ответа

Решение

Код, который вы разместили, не будет касаться одежды, так как нет столкновений. Код в 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). Каждая пружина соединена между двумя массами и пытается удерживать массы от слишком большого растяжения и сближения. Вам нужны тысячи масс и пружин для имитации одежды.

Причина, по которой демо-версии находятся в самолетах, заключается в том, что это самая простая демо-версия. Если вам нужна одежда, вам нужно пройти по многоугольникам вашей одежды, а затем создать массы и пружины. Кроме того, вам необходимо связать массы с соответствующими вершинами в модели одежды, чтобы после запуска моделирования вы могли применить новые положения масс обратно к вершинам одежды.

Вдобавок вам нужны столкновения на теле персонажа, на которого вы собираетесь надеть одежду, с которой будут сталкиваться массы, чтобы они не попадали внутрь тела и просто падали на пол. Большинство физических движков имеют несколько оптимизированных примитивов, таких как коробка, сфера, капсула, цилиндр. Они также могут использовать универсальные полигоны для столкновения, но они медленнее, поэтому вам решать, можете ли вы использовать несколько примитивных форм, прикрепленных к вашей модели, для столкновения, или вам нужна более высокая точность использования полигонов для ваших столкновений..

В любом случае, чем больше масс вы добавляете на область ткани, тем лучше ткань будет выглядеть, но тем медленнее она будет работать, поэтому вам нужно решить, где находится компромисс между хорошим внешним видом и быстрым бегом.

ammo.js AFAICT недокументирован, за исключением того, что это порт Bullet Physics, документация которого находится здесь

Я не вижу демонстраций JavaScript для нестандартной ткани.

Эта демонстрация ammo.js, похоже, не подходит к одежде, потому что, если бы показанные фигуры на самом деле были одеждой, они бы просто свернулись в кучу, а не действовали бы так, как будто они надуты, но тогда, возможно, это поведение является настройкой. Вам нужно будет покопаться в документации и / или в этом образце.

Вам нужно будет отделить геометрию вашей одежды от модели человеческого тела / манекена, превратить одежду в мягкое тело или вручную создать массы и пружины, а затем также сделать твердое тело человека / манекена либо из сетки, либо из примитивов, чтобы оно держалось. одежду.

Если бы я делал это, я бы начал с куба твердого тела внутри сферы мягкого тела и посмотрел, насколько детально мне нужно сделать сферу, чтобы она вела себя как одежда (складывание и складывание).

Popup Dev Я работал с этим, но обнаружил, что использование геометрии плоскости для манипулирования намного проще. У меня также есть вопросы о том, как в исходном примере ткани вычисляется и используется "ветер". Свяжитесь со мной в твиттере, и давайте обсудим.

Другие вопросы по тегам