threejs动画

news2024/12/25 14:36:55

个人博客地址: https://cxx001.gitee.io

前面我们所用的模型大都是静态的,没有动画,没有生命。这节我们将赋予它们生命。

动画本质是通过改变物体的旋转、缩放、位置、材质、顶点、面以及其它你所能想到的属性来实现的。这些其实在前面章节示例里或多或少已经使用了。

一、选择对象

在写示例前我们先了解一个重要操作,如何通过鼠标选中场景中的对象。

当我们在屏幕上点击鼠标时,会发生如下事情:

  1. 首先,基于屏幕上点击位置会创建一个THREE.Vecor3向量。

  2. 接着,使用vector.unproject方法将屏幕上的点击位置转换成Threejs场景中的坐标。

  3. 然后,创建THREE.Raycaster。使用它可以向场景中发射光线。

  4. 最后,我们使用raycaster.intersectObjects方法来判断指定的对象中哪些被该光线照射到了。其结果包含了所有被照射到的对象的信息,这些信息包括:

    distance: 50.66666666666   // 从摄像机到被点击对象的距离
    face: THREE.Face3  		   // 那个面被点击了
    faceIndex: 6               // 被点击面的下标
    object: THREE.Mesh         // 被点击的网格对象
    point: THREE.Vector3       // 网格对象上哪个点被点击了
    
<!-- chapter-09-01.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Selecting objects</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <script type="text/javascript" src="../libs/Projector.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        renderer.setSize(window.innerWidth, window.innerHeight);
		
        var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        scene.add(plane);

        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.position.x = -9;
        cube.position.y = 3;
        cube.position.z = 0;
        scene.add(cube);

        var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
        var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        sphere.position.x = 20;
        sphere.position.y = 0;
        sphere.position.z = 2;
        scene.add(sphere);
    
        var cylinderGeometry = new THREE.CylinderGeometry(2, 2, 20);
        var cylinderMaterial = new THREE.MeshLambertMaterial({color: 0x77ff77});
        var cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
        cylinder.position.set(0, 0, 1);
        scene.add(cylinder);

        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        var ambientLight = new THREE.AmbientLight(0x0c0c0c);
        scene.add(ambientLight);

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-40, 60, -10);
        scene.add(spotLight);

        document.getElementById("WebGL-output").appendChild(renderer.domElement);
    	
    	var tube;
        var controls = new function () {
            this.showRay = false;
        };
		
        var gui = new dat.GUI();
        gui.add(controls, 'showRay').onChange(function (e) {
            if (tube) scene.remove(tube)
        });

        function render() {
            stats.update();
            renderer.render(scene, camera);
            requestAnimationFrame(render);
        }
    	render();
    	
    	// 监控鼠标按下和鼠标移动事件
        document.addEventListener('mousedown', onDocumentMouseDown, false);
        document.addEventListener('mousemove', onDocumentMouseMove, false);
		
        function onDocumentMouseDown(event) {
            // 1.鼠标点击位置
            var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
            // 2.把鼠标点击位置转换为threejs场景中的坐标
            vector = vector.unproject(camera);
            // 3.从摄像机位置向点击位置发射一条光线
            var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
            // 4. 获取指定数组里对象哪些被光线照射到了
            var intersects = raycaster.intersectObjects([sphere, cylinder, cube]);
            if (intersects.length > 0) {
                console.log(intersects[0]);
                intersects[0].object.material.transparent = true;
                intersects[0].object.material.opacity = 0.5;
            }
        }
		
        function onDocumentMouseMove(event) {
            // 是否显示HREE.Raycaster射线
            if (controls.showRay) {
                var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
                vector = vector.unproject(camera);
                var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
                var intersects = raycaster.intersectObjects([sphere, cylinder, cube]);
                if (intersects.length > 0) {
                    var points = [];
                    points.push(new THREE.Vector3(-30, 39.8, 30));
                    points.push(intersects[0].point);
                    var mat = new THREE.MeshBasicMaterial({color: 0xff0000, transparent: true, opacity: 0.6});
                    var tubeGeometry = new THREE.TubeGeometry(new THREE.SplineCurve3(points), 60, 0.001);

                    if (tube) scene.remove(tube);
                    tube = new THREE.Mesh(tubeGeometry, mat);
                    scene.add(tube);
                }
            }
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

下面回归正题,我们重新回到动画上来。


二、使用Tween.js实现动画

Tween.js是一个轻量级的javascript库 https://github.com/tweenjs/tween.js,通过这个库可以很容易实现某个属性在两个值之间进行过渡,我们称这种为补间动画。官方提供了很多示例,自行去查看。

<!-- chapter-09-02.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Animation tween </title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/ColladaLoader.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <script type="text/javascript" src="../libs/tween.min.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0xcccccc, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        camera.position.x = 150;
        camera.position.y = 150;
        camera.position.z = 150;
        camera.lookAt(new THREE.Vector3(0, 20, 0));

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(150, 150, 150);
        spotLight.intensity = 2;
        scene.add(spotLight);
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
    
    	var mesh;
        var loader = new THREE.ColladaLoader();
        loader.load("../assets/models/dae/Truck_dae.dae", function (result) {
            mesh = result.scene.children[0].children[0].clone();
            //mesh.scale.set(4, 4, 4);
            scene.add(mesh);
            // 补间动画要在模型加载完后才能开始
            tween.start();  
        });

        // 使用补间动画, 在y轴上下缓动移动
        // http://sole.github.io/tween.js/examples/03_graphs.html
        var posSrc = {pos: 10};
        var tween = new TWEEN.Tween(posSrc).to({pos: 0}, 1000);
        tween.easing(TWEEN.Easing.Sinusoidal.InOut);

        var tweenBack = new TWEEN.Tween(posSrc).to({pos: 10}, 1000);
        tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);
		// 衔接两个动作,循环执行
        tween.chain(tweenBack);
        tweenBack.chain(tween);
		// 执行后坐标更新
        var onUpdate = function () {
            var pos = this.pos;
            mesh.position.y = pos;
        };
        tween.onUpdate(onUpdate);
        tweenBack.onUpdate(onUpdate);
    
    	 function render() {
            stats.update();
            // 更新补间动画
            TWEEN.update();
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }
    	render();

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>


三、使用摄像机实现动画

前面我们已经接触过了,可以通过移动摄像机的位置让整个场景动起来,我们是通过手动改变摄像机位置实现的。Threejs还提供了其它更新摄像机的控件,下面我们依次看看。

最常用的:

其它的本节不会介绍,使用和上面一样,了解下:

1. 轨迹球控制器

下面是使用这个控制器,通过鼠标左键拖动、滚轮、右键拖动分别实现场景的旋转、缩放、平移的示例。

<!-- chapter-09-03.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Trackball controls </title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/OBJLoader.js"></script>
    <script type="text/javascript" src="../libs/MTLLoader.js"></script>
    <script type="text/javascript" src="../libs/OBJMTLLoader.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <script type="text/javascript" src="../libs/chroma.js"></script>
    <script type="text/javascript" src="../libs/TrackballControls.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        camera.position.x = 100;
        camera.position.y = 100;
        camera.position.z = 300;
        camera.lookAt(new THREE.Vector3(0, 0, 0));
    
        var ambientLight = new THREE.AmbientLight(0x383838);
        scene.add(ambientLight);

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(300, 300, 300);
        spotLight.intensity = 1;
        scene.add(spotLight);
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
    	
    	// 创建轨迹球控制器,并绑定摄像机
    	var trackballControls = new THREE.TrackballControls(camera);
    	// 想要全面了解这些属性可以参考rackballControls.js源文件
        trackballControls.rotateSpeed = 1.0;
        trackballControls.zoomSpeed = 1.0;
        trackballControls.panSpeed = 1.0;
		//trackballControls.noZoom=false;
        //trackballControls.noPan=false;
        trackballControls.staticMoving = true;
        //trackballControls.dynamicDampingFactor=0.3;
		
    	// 加载OBJ/MTL模型
        var mesh;
        var loader = new THREE.OBJMTLLoader();
        var texture = THREE.ImageUtils.loadTexture('../assets/textures/Metro01.JPG');
        //texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;
        loader.load('../assets/models/city.obj', '../assets/models/city.mtl', function (object) {
            var scale = chroma.scale(['red', 'green', 'blue']);
            setRandomColors(object, scale);
            mesh = object;
            scene.add(mesh);
        });
        //texture.repeat.set( 1 , 1);
       
        function setRandomColors(object, scale) {
            var children = object.children;
            if (children && children.length > 0) {
                children.forEach(function (e) {
                    setRandomColors(e, scale)
                });
            } else {
                if (object instanceof THREE.Mesh) {
                    object.material.color = new THREE.Color(scale(Math.random()).hex());
                    if (object.material.name.indexOf("building") == 0) {
                        object.material.emissive = new THREE.Color(0x444444);
                        object.material.transparent = true;
                        object.material.opacity = 0.8;
                    }
                }
            }
        }

		var clock = new THREE.Clock();
        function render() {
            stats.update();
            var delta = clock.getDelta();
            trackballControls.update(delta);  // delta距离上次调用时间间隔
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera)
        }
    	render();

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

2. 飞行控制器

可行像飞行视角一样在场景中飞行。

// 创建飞行控制器
var flyControls = new THREE.FlyControls(camera);
flyControls.movementSpeed = 25;
flyControls.domElement = document.querySelector("#WebGL-output");
flyControls.rollSpeed = Math.PI / 24;
flyControls.autoForward = true;
flyControls.dragToLook = false;

// 更新
var clock = new THREE.Clock();
function render() {
    var delta = clock.getDelta();
    flyControls.update(delta);
    
    webGLRenderer.clear();
    requestAnimationFrame(render);
    webGLRenderer.render(scene, camera)
}

3. 翻滚控制器

翻滚场景(Q/E 左/右翻滚)。

// 创建翻滚控制器
var rollControls = new THREE.RollControls(camera);
rollControls.movementSpeed = 25;
rollControls.lookSpeed = 3;

// 更新
var clock = new THREE.Clock();
function render() {
    var delta = clock.getDelta();
    rollControls.update(delta);

    webGLRenderer.clear();
    requestAnimationFrame(render);
    webGLRenderer.render(scene, camera)
}

4. 第一视角控制器

类似第一视角射击游戏那样控制摄像机。鼠标用于控制视角,键盘用于控制角色移动。

// 创建第一视角控制器
var camControls = new THREE.FirstPersonControls(camera);
camControls.lookSpeed = 0.4;
camControls.movementSpeed = 20;
camControls.noFly = true;
camControls.lookVertical = true;
camControls.constrainVertical = true;
camControls.verticalMin = 1.0;
camControls.verticalMax = 2.0;
camControls.lon = -150;
camControls.lat = 120;

// 更新
var clock = new THREE.Clock();
function render() {
    var delta = clock.getDelta();
    rollControls.update(delta);

    webGLRenderer.clear();
    requestAnimationFrame(render);
    webGLRenderer.render(scene, camera)
}

5. 轨道控制器

和轨迹控制器类似,只是轨道控制器强制摄像头 up方向,而轨迹控制器允许相机倒置旋转。可以用于控制场景中的对象围绕场景中心旋转和平移。

<!-- chapter-09-04.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Orbit controls</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <script type="text/javascript" src="../libs/OrbitControls.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;
		
    	// 创建球体网格对象
        var sphere = createMesh(new THREE.SphereGeometry(20, 40, 40));
        scene.add(sphere);

        camera.position.x = -20;
        camera.position.y = 30;
        camera.position.z = 40;
        camera.lookAt(new THREE.Vector3(00, 0, 0));

        var ambiLight = new THREE.AmbientLight(0x111111);
        scene.add(ambiLight);
        var spotLight = new THREE.DirectionalLight(0xffffff);
        spotLight.position.set(-20, 30, 40);
        spotLight.intensity = 1.5;
        scene.add(spotLight);

        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

		// 使用外部纹理材质创建球体网格
        function createMesh(geom) {
            var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/mars_1k_color.jpg");
            var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/mars_1k_normal.jpg");
			// 加载外部图片纹理创建Phong材质
            var planetMaterial = new THREE.MeshPhongMaterial({map: planetTexture, bumpMap: normalTexture});

            var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);
            return mesh;
        }
    	
    	// 创建轨道控制器
    	var orbitControls = new THREE.OrbitControls(camera);
        orbitControls.autoRotate = true;
		
    	var clock = new THREE.Clock();
        function render() {
            stats.update();
            var delta = clock.getDelta();
            orbitControls.update(delta);

            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }
    	render();

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>


四、高级动画

1. 关键帧动画

就是从动画开始到动画结束之间所有的顶点数据都会被存储下来,你需要做的就是定义好关键位置,并重复该过程。这种主要一个缺点就是对于大型网格和大型动画,模型文件会变得非常大。

Threejs库提供的使用关键字动画对象:THREE.MorphAnimMesh

<!-- chapter-09-05.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Working with morph targets</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        camera.position.x = 250;
        camera.position.y = 250;
        camera.position.z = 350;
        camera.lookAt(new THREE.Vector3(100, 50, 0));

        var spotLight = new THREE.DirectionalLight(0xffffff);
        spotLight.position.set(300, 200, 300);
        spotLight.intensity = 1;
        scene.add(spotLight);
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var controls = new function () {
            this.keyframe = 0;  // 关键帧位置
        };

        var gui = new dat.GUI();
        gui.add(controls, "keyframe", 0, 15).step(1).onChange(function (e) {
            showFrame(e);
        });
    
        var meshAnim;
        var frames = [];
        var currentMesh;
        var loader = new THREE.JSONLoader();
    	// 1. 加载帧动画模型
        loader.load('../assets/models/horse.js', function (geometry, mat) {
            // 创建左边静态马网格
            var mat = new THREE.MeshLambertMaterial(
                    {
                        morphTargets: true,  // 设置为true网格才会动
                        vertexColors: THREE.FaceColors
                    });
            var mesh = new THREE.Mesh(geometry, mat);
            mesh.position.x = -100;
            frames.push(mesh);
            currentMesh = mesh;
            morphColorsToFaceColors(geometry);  // 修改各面颜色
			
            // 创建动画所有关键帧网格
            var mat2 = new THREE.MeshLambertMaterial(
                    {color: 0xffffff, vertexColors: THREE.FaceColors});
            // 遍历动画所有帧,并创建为网格对象添加到frames数值中
            mesh.geometry.morphTargets.forEach(function (e) {
                var geom = new THREE.Geometry();
                geom.vertices = e.vertices;
                geom.faces = geometry.faces;
                var morpMesh = new THREE.Mesh(geom, mat2);
                frames.push(morpMesh);
                morpMesh.position.x = -100;
            });
			
            // 确保运动时光照、阴影和颜色的准确性
            geometry.computeVertexNormals();
            geometry.computeFaceNormals();
            geometry.computeMorphNormals();
			
            // 2. THREE.MorphAnimMesh创建帧动画网格,右边运动马
            meshAnim = new THREE.MorphAnimMesh(geometry, mat);
            meshAnim.duration = 1000;
            meshAnim.position.x = 200;
            meshAnim.position.z = 0;
            scene.add(meshAnim);
            
            // 用第一帧创建右边的静态马
            showFrame(0);
        }, '../assets/models');

    	// 手的改变帧播放
        function showFrame(idx) {
            scene.remove(currentMesh);
            scene.add(frames[idx]);
            currentMesh = frames[idx];
            console.log(currentMesh);
        }

        function morphColorsToFaceColors(geometry) {
            if (geometry.morphColors && geometry.morphColors.length) {
                var colorMap = geometry.morphColors[0];
                for (var i = 0; i < colorMap.colors.length; i++) {
                    geometry.faces[i].color = colorMap.colors[i];
                    geometry.faces[i].color.offsetHSL(0, 0.3, 0);
                }
            }
        }

    	var clock = new THREE.Clock();
        function render() {
            stats.update();
            var delta = clock.getDelta();
            webGLRenderer.clear();
            if (meshAnim) {
                // 3. 帧动画更新
                meshAnim.updateAnimation(delta * 1000);
                meshAnim.rotation.y += 0.01;  
            }
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }
    	render();

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

在这里插入图片描述

2. 骨骼动画

就是定义好网格的骨骼,并将顶点绑定到特定的骨骼上。当骨骼运动时,相连骨骼也会随着一起变换,同时骨骼上绑定的顶点也会随之移动。

Threejs库提供的创建骨骼网格的对象:THREE.SkinnedMesh。下面是一个手动变换骨骼对象的示例:

<!-- chapter-09-06.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Load blender model </title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <script type="text/javascript" src="../libs/tween.min.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        camera.position.x = 0;
        camera.position.y = 0;
        camera.position.z = 4;
        camera.lookAt(new THREE.Vector3(0, 0, 0));

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(0, 50, 30);
        spotLight.intensity = 2;
        scene.add(spotLight);
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var mesh;
        var loader = new THREE.JSONLoader();
    	// 1. 加载Blender导出的骨骼模型
        loader.load('../assets/models/hand-1.js', function (geometry, mat) {
            var mat = new THREE.MeshLambertMaterial({color: 0xF0C8C9, skinning: true}); // skinning要设置为true,否则骨骼不会动
            // 2. 创建骨骼模型网格
            mesh = new THREE.SkinnedMesh(geometry, mat);
            mesh.rotation.x = 0.5 * Math.PI;
            mesh.rotation.z = 0.7 * Math.PI;
            scene.add(mesh);
            // 开始运行
            tween.start();
        }, '../assets/models');

    	// 3. 手动改变骨骼变换
        var onUpdate = function () {
            var pos = this.pos;
            console.log(mesh.skeleton);
            mesh.skeleton.bones[5].rotation.set(0, 0, pos);
            mesh.skeleton.bones[6].rotation.set(0, 0, pos);
            mesh.skeleton.bones[10].rotation.set(0, 0, pos);
            mesh.skeleton.bones[11].rotation.set(0, 0, pos);
            mesh.skeleton.bones[15].rotation.set(0, 0, pos);
            mesh.skeleton.bones[16].rotation.set(0, 0, pos);
            mesh.skeleton.bones[20].rotation.set(0, 0, pos);
            mesh.skeleton.bones[21].rotation.set(0, 0, pos);
            mesh.skeleton.bones[1].rotation.set(pos, 0, 0);
        };
        var tween = new TWEEN.Tween({pos: -1})
                .to({pos: 0}, 3000)
                .easing(TWEEN.Easing.Cubic.InOut) // 缓动
                .yoyo(true)   		// 设置为true,使得动画运动完后反着运行
                .repeat(Infinity)  // 重复运行
                .onUpdate(onUpdate);
        

 		var clock = new THREE.Clock();
        function render() {
            stats.update();
            TWEEN.update();
            var delta = clock.getDelta();
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }
    	render();

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>


五、使用外部模型创建动画

1. 使用Blender创建骨骼动画

Blender使用threejs导出器,把.blend动画导出为.json,然后加载并播放动画。

播放外部骨骼动画使用:THREE.Animation

<!-- chapter-09-07.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Animation from blender</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        
    	var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        camera.position.x = 0;
        camera.position.y = 0;
        camera.position.z = 4;
        camera.lookAt(new THREE.Vector3(0, 0, 0));

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(0, 50, 30);
        spotLight.intensity = 2;
        scene.add(spotLight);
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var mesh;
        var helper;
        var controls = new function () {
            this.showHelper = false;  // 是否显示骨骼骨架
        };
        var gui = new dat.GUI();
        gui.add(controls, 'showHelper').onChange(function (state) {
            helper.visible = state;
        });

        var loader = new THREE.JSONLoader();
    	// 加载骨骼动画模型
        loader.load('../assets/models/hand-2.js', function (model, mat) {
            var mat = new THREE.MeshLambertMaterial({color: 0xF0C8C9, skinning: true});
            mesh = new THREE.SkinnedMesh(model, mat);
            mesh.rotation.x = 0.5 * Math.PI;
            mesh.rotation.z = 0.7 * Math.PI;
            scene.add(mesh);
            
            // 创建骨骼动画对象,并开始播放
            var animation = new THREE.Animation(mesh, model.animation);
			animation.play();
            
            // 创建骨骼骨架
            helper = new THREE.SkeletonHelper(mesh);
            helper.material.linewidth = 2;
            helper.visible = false;
            scene.add(helper);
        }, '../assets/models');

		var clock = new THREE.Clock();
        function render() {
            stats.update();
            var delta = clock.getDelta();
            if (mesh) {
                // 更新骨骼动画
                helper.update();
                THREE.AnimationHandler.update(delta);
            }
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }
    	render();

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

2. 从Collada模型加载动画

使用方式和上面一样,只是加载模型文件格式变了。

<!-- chapter-09-08.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Animation from collada</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <script type="text/javascript" src="../libs/ColladaLoader.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        camera.position.x = 400;
        camera.position.y = 50;
        camera.position.z = 150;
        camera.lookAt(new THREE.Vector3(0, 0, 0));

        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(300, 500, 100);
        spotLight.intensity = 3;
        scene.add(spotLight);
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var meshAnim;
    	// 加载.dae模型
        var loader = new THREE.ColladaLoader();
        loader.load('../assets/models/monster.dae', function (collada) {
            // 模型第一帧网格添加进场景
            var child = collada.skins[0];
            scene.add(child);
            child.scale.set(0.15, 0.15, 0.15);
            child.rotation.x = -0.5 * Math.PI;
            child.position.x = -100;
            child.position.y = -60;
            
            // 创建动画并开始播放
            var animation = new THREE.Animation(child, child.geometry.animation);
            animation.play();
        });

		var clock = new THREE.Clock();
        function render() {
            stats.update();
            var delta = clock.getDelta();
            // 动画更新
            THREE.AnimationHandler.update(delta);
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }
    	render();

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

在这里插入图片描述

3. 其它

整的来说动画就两类:帧动画和骨骼动画,使用方法都差不多。其它动画文件格式加载和使用可以查看官方示例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/706415.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git 版本控制从入门到精通

文章目录 1、git安装1.1、Linux安装1.2、Windows安装1.3、MAC安装 2、配置git3、git命令使用4、git远程服务器5、提交到远端服务器6、commit合并7、创建分支8、命令练习记录 1、git安装 1.1、Linux安装 在linux上我们建议你用二进制的方式来安装git&#xff0c;可以使用发行版…

electron报错Error: Object has been destroyed

问题描述 在 Electron 中&#xff0c;当一个窗口被销毁后&#xff0c;与该窗口相关联的 JavaScript 对象也会被销毁&#xff0c;再次访问已被销毁的窗口对象时&#xff0c;会导致 Error: Object has been destroyed 错误。 例如之前在写多窗口pinia状态同步 / 多窗口样式同步的…

Redis【实战篇】---- 分布式锁

Redis【实战篇】---- 分布式锁 1. 基本原理和实现方式对比2. Redis分布式锁的实现核心思路3. 实现分布式锁版本一4. Redis分布式锁误删情况说明5. 解决Redis分布式锁误删问题6. 分布式锁的原子性问题7. Lua脚本解决多条命令原子性问题8. 利用Java代码调试Lua脚本改造分布式锁 1…

Python 利用深度学习识别空间推理验证码(一)

注意:本文会比较长,因为空间推理验证码本身比较复杂,我会详细的讲解,我是如何一步一步拆分空间推理的思想去实现的,另外,这里只介绍第一种思想来解决空间推理验证码,实际上,解决该验证码的方法也比较多,这第一种,我会讲解的比较简单,通俗易懂。 注意:下面数据集使用…

Redis主从/哨兵机制原理介绍

目录 ​编辑 一、主从复制 1.1 什么是主从复制 1.2 主从复制的作用 1.3 主从复制原理 1.3.1 全量复制 1.3.2 增量复制 1.3.3 同步流程 二、哨兵机制 2.1 哨兵机制介绍 2.1.1 集群逻辑图 2.1.2 哨兵机制实现的功能 2.2 哨兵机制原理 2.2.1 监控 2.2.2 下线 2.2.2.1 下线流程 2.…

C# csc构建dll 和 csc构建时指定dll

新建一个mydll.cs&#xff1b; using System; using System.Collections.Generic; using System.Linq; using System.Text;namespace myDLL {public class MyMath{public int add(int x, int y){return x y;}public int sub(int x, int y){return x - y;}} } 用下图命令构建…

MySQL高可用

MySQL高可用 一、高可用 1.什么是MHA MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能…

LeetCode·每日一题·2490. 回环句·模拟

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/circular-sentence/solutions/2325227/mo-ni-zhu-shi-chao-ji-xiang-xi-by-xun-ge-x65e/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获得授权&#xff0…

fusionpbx简介

概述 fusionpbx是以freeswitch作为底层框架开发而成的开源PBX&#xff0c;在freeswitch的基础上&#xff0c;优化了GUI的易用性。 fusionpbx可用作高可用性的单租户或基于域的多租户 PBX、运营商级交换机、呼叫中心服务器、传真服务器、voip服务器、语音邮件服务器、会议服务…

admin配置k8s

系列文章目录 文章目录 系列文章目录一、实验1.实验要求2.3. 所有节点安装docker4.所有节点安装kubeadm&#xff0c;kubelet和kubectl5.部署K8S集群6./所有节点部署网络插件flannel7./在master节点查看节点状态 总结 一、实验 1.实验要求 master&#xff08;2C/4G&#xff0c…

Maven的安装过程

参考地址 https://www.cnblogs.com/hanliukui/p/16842734.html 注意下载包体是bin的包体&#xff1a;Maven – Download Apache Maven 然后ok

echart 绘制一个基础的中国地图

echart&#xff0c;绘制一个基础的中国地图 代码示例 <template><div id"china_map_box"><div id"china_map"></div></div> </template><script> import * as echarts from echarts import chinaJson from ./…

《综合与Design_Compiler》学习笔记——第一章综合综述 第二章verilog语言结构到门级的映射 第三章 使用DC进行综合

文章目录 前言一、综合综述1、综合2、综合的不同层次&#xff08;1&#xff09;逻辑级综合&#xff08;2&#xff09;RTL级综合&#xff08;3&#xff09;行为级综合 二、verilog语言结构到门级的映射三、使用DC进行综合1、定义2、写时序约束3、写环境约束&#xff08;1&#x…

Vite创建vue3+ts+pinia项目流程

Vite 需要 Node.js 版本 14.18&#xff0c;16。然而&#xff0c;有些模板需要依赖更高的 Node 版本才能正常运行&#xff0c;当你的包管理器发出警告时&#xff0c;请注意升级你的 Node 版本(这里建议node版本在16以上)&#xff0c; 推荐使用 pnpm 包管理工具 管理项目 包安装…

Hive 之 beeline 客户端连接

beeline 客户端连接 Hive beeline -u jdbc:hive2://hadoop10:10000 -n hive 我们下期见&#xff0c;拜拜&#xff01;

【Verilog HDL】FPGA-testbench基础知识

&#x1f389;欢迎来到FPGA专栏~testbench基础知识 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大家能…

前端Vue自定义勾选协议组件 可用于登录 注册等场景

前端Vue自定义勾选协议组件&#xff0c; 可用于登录 注册等场景&#xff0c; 下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13299 效果图如下&#xff1a; # cc-protocolBox #### 使用方法 使用方法 <!-- agree&#xff1a;是…

【HTML5】svg 绘制图形

文章目录 一、基本介绍二、用法详解2.1、矩形&#xff08;rect&#xff09;2.2、圆形&#xff08;circle&#xff09;2.3、椭圆&#xff08;ellipse&#xff09;2.4、线条&#xff08;line&#xff09;2.5、折线&#xff08;polyline&#xff09;2.6、多边形&#xff08;polygo…

Redis 发布/订阅介绍

目录 ​编辑 一、发布与订阅 6.1 频道 6.1.1 发布 6.1.2 订阅 6.1.3 数据结构 6.1.3.1 订阅&#xff08;channel&#xff09; 6.1.3.2 发布 6.1.3.3 退订 6.2 模式&#xff08;pattern&#xff09; 6.2.1 发布 6.2.2 订阅 6.2.3 数据结构 6.2.3.1 发布 6.2.3.2 订阅 6.2.3.…

登录远程Linux桌面

远程桌面连接主要使用两种协议&#xff0c;一种是Windows上RDP协议&#xff0c;第二种是VNC协议&#xff0c;从使用效果来看&#xff0c;vnc更优秀。 一、VNC 使用x11vnc 1.安装x11vnc sudo apt install x11vnc 2.启动x11vnc x11vnc -passwd orangepi -display :0 -forever…