threejs官方demo学习(1):animation

前言

之前的threejs入门视频教学已经学习完了,下面会陆续学习官方demo。官方网址太卡了建议在本地进行搭建,具体见:threejs视频教程学习(1):本地搭建threeJS官网、渲染第一个场景

官方的例子都是html格式,后续以vue3的格式进行学习。

webgl_animation_keyframes

代码

<template>
    <div id="keyframes"> </div>
</template>

<script setup lang="ts">
import {
    
     onMounted, ref } from 'vue';
import * as THREE from 'three';
// 引入轨道控制器
import {
    
     OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// 引入模型加载器
import {
    
     GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 引入模型解压器
import {
    
     DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
// 引入房间环境,之前没有用过,应该是创建一个室内环境
import {
    
     RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment';
// 引入性能监视器,之前也没用过
import Stats from 'three/examples/jsm/libs/stats.module';

const mixer = ref();

onMounted(() => {
    
    
    // 创建一个clock对象,用于跟踪时间
    const clock = new THREE.Clock();
    // 获取dom容器
    const container = document.getElementById('keyframes');
    // 创建一个性能监听器
    const stats = new Stats();
    // 修改一下位置
    stats.dom.style.position = 'relative';
    // 将性能监听器添加到容器中
    container?.appendChild(stats.dom);
    // 创建一个渲染器
    const renderer = new THREE.WebGLRenderer({
    
    
        antialias: true // 设置防锯齿
    });
    // 设置渲染器的像素比例
    renderer.setPixelRatio(window.devicePixelRatio);
    // 设置渲染的尺寸
    renderer.setSize(1000, 800);
    // 设置渲染的输出格式
    renderer.outputEncoding = THREE.sRGBEncoding;
    // 将渲染的内容添加到容器中
    container?.appendChild(renderer.domElement);

    // 创建一个PMREMGenerator,从立方体映射环境纹理生成预过滤的 Mipmap 辐射环境贴图
    const pmremGenerator = new THREE.PMREMGenerator(renderer);
    // 创建一个场景
    const scene = new THREE.Scene();
    // 设置背景色
    scene.background = new THREE.Color(0xbfe3dd);
    // 设置场景的纹理,从提供的场景中生成纹理
    scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;

    // 创建相机并设置位置
    const camera = new THREE.PerspectiveCamera(40, 1.25, 1, 100);
    camera.position.set(5, 2, 8);

    // 设置轨道控制器
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.target.set(0, 0.5, 0);
    controls.update();
    controls.enablePan = false; // 当设置为false时,控制器将不会响应用户的操作。默认值为true。
    controls.enableDamping = true; // 开启阻尼

    // 创建解压器并设置路径,官方文档中有draco文件夹,直接复制到自己项目里就好,另外gltf后面一定要加斜杠
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath('../../../../public/draco/gltf/');

    // 创建模型加载器并加载模型
    const loader = new GLTFLoader();
    loader.setDRACOLoader(dracoLoader);
    loader.load('../../../../public/LittlestTokyo.glb', gltf => {
    
    
        const model = gltf.scene;
        // 设置模型的位置
        model.position.set(1, 1, 0);
        // 设置视角
        model.scale.set(0.01, 0.01, 0.01);
        // 将模型添加到场景中
        scene.add(model);

        // 创建一个动画混合器,动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
        mixer.value = new THREE.AnimationMixer(model);
        // 设置剪辑动画
        mixer.value.clipAction(gltf.animations[0]).play();
        // 执行动画
        animate();
    });

    // 动画执行函数
    const animate = () => {
    
    
        // 调用动画帧执行动画
        requestAnimationFrame(animate);
        // 获取当前秒数
        const delta = clock.getDelta();
        // 更新动画混合器、轨道控制器、性能监听器
        mixer.value.update(delta);
        controls.update();
        stats.update();
        // 重新渲染
        renderer.render(scene, camera);
    };
});
</script>

<style lang="scss" scoped>
#keyframes {
    
    
    background-color: #bfe3dd;
    width: 1000px;
    height: 800px;
}
</style>

效果图
左上角的是性能监听器,点击启动,再次点击关闭
在这里插入图片描述

知识点

最复杂的是模型,不过模型是做好的。主要是如何加载模型,设置动画。

RoomEnvironment

import {
    
     RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment';

应该是室内环境,但是在官方文档里没有找到。

PMREMGenerator

// 创建一个PMREMGenerator,从立方体映射环境纹理生成预过滤的 Mipmap 辐射环境贴图
 const pmremGenerator = new THREE.PMREMGenerator(renderer);
 // 创建一个场景
 const scene = new THREE.Scene();
 // 设置背景色
 scene.background = new THREE.Color(0xbfe3dd);
 // 设置场景的纹理,从提供的场景中生成纹理
 scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;

看官方的demo应该是用来提取场景中的纹理的。

AnimationMixer

官方文档的解释是:动画混合器是用于场景中特定对象的动画的播放器。
当我将相关代码注释后,模型里的动画停止了执行,结合文档的解释,应该就是用于播放模型自身的动画。

Clock
该对象用于跟踪时间,结合上面动画混合器的update 方法,这里单纯用于给动画混合器提供参数。
在这里插入图片描述

webgl_animation_skinning_blending

官方的例子有点复杂,看了好几遍才明白,实际上也是基于上面的动画加载,只是将各个动作给拆解开来了。在官方例子的基础上进行了简化,把一些代码进行了删除,这样更适合我这样的初学者。

代码
这里是有点问题的,没太搞明白,new THREE.AnimationMixer(model)单独定为一个变量使用时会提示某些方法找不到,因此就没有单独定义

<template>
    <div id="keyframes"> </div>
</template>

<script setup lang="ts">
import {
    
     onMounted } from 'vue';
import * as THREE from 'three';
// 引入gui
import * as dat from 'dat.gui';
// 模型加载器
import {
    
     GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 引入轨道控制器
import {
    
     OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

onMounted(() => {
    
    
    // 获取dom容器
    const container = document.getElementById('keyframes');
    // 创建一个场景
    const scene = new THREE.Scene();
    // 设置场景的背景色
    scene.background = new THREE.Color(0xa0a0a0);
    // 给场景中添加雾、雾的颜色、最小距离、最大距离。最小距离和最大距离之间是雾的范围
    scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);

    // 创建相机
    const camera = new THREE.PerspectiveCamera(75, 1.25, 1, 1000);
    // 设置相机的位置
    camera.position.set(2, 2, -10);
    // 将相机添加到场景里
    scene.add(camera);

    // 创建渲染器
    const renderer = new THREE.WebGLRenderer({
    
    
        antialias: true // 设置防锯齿
    });
    // 设置渲染器的像素比例
    renderer.setPixelRatio(window.devicePixelRatio);
    // 设置渲染尺寸
    renderer.setSize(1000, 800);
    // 设置渲染输出的编码
    renderer.outputEncoding = THREE.sRGBEncoding;
    // 开启场景阴影渲染
    renderer.shadowMap.enabled = true;
    // 将渲染对象添加到容器
    container?.appendChild(renderer.domElement);

    // 添加平行光,平行光可以产生投影
    const dirLight = new THREE.DirectionalLight(0xffffff);
    // 设置平行光的位置
    dirLight.position.set(-3, 10, -10);
    // 设置光照产生阴影
    dirLight.castShadow = true;

    // 向场景中添加灯光
    scene.add(dirLight);
    // 添加坐标辅助器
    const axesHelper = new THREE.AxesHelper(10);
    scene.add(axesHelper);
    // 创建轨道控制器
    const controls = new OrbitControls(camera, renderer.domElement);
    // 设置控制器阻尼
    controls.enableDamping = true;

    // 渲染函数
    const render = () => {
    
    
        controls.update();
        renderer.render(scene, camera);
        requestAnimationFrame(render);
    };
    render();
    // 创建一个具有镜面高光的平面
    const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({
    
     color: 0x999999 }));
    // 把平面变成水平的
    mesh.rotation.x = -Math.PI / 2;
    // 设置接收物体投影
    mesh.receiveShadow = true;
    scene.add(mesh);

    // 模型
    let model = null;
    // clock对象
    const clock = new THREE.Clock();
    // 用来模拟骨骼 Skeleton 的辅助对象
    let skeleton = null;
    // 放松动作
    let idleAction = null;
    // 步行
    let walkAction = null;
    // 跑
    let runAction = null;
    // 所有的设置
    let settings = {
    
    };
    // 所有动作
    let actions = [];
    // 当前激活动作
    let active = 2;
    // 所有的动画
    let animations = [];

    // 创建模型加载器,加载模型
    const loader = new GLTFLoader();
    // 应该是模型比较小,这里没有引入模型解压器
    loader.load('../../../../public/Soldier.glb', gltf => {
    
    
        model = gltf.scene;
        scene.add(model);
        // 进行深度遍历
        model.traverse(function(object) {
    
    
            // 如果是物体的话开启投影
            if (object.isMesh) {
    
    
                object.castShadow = true;
            }
        });
        // 获取模型的骨骼对象
        skeleton = new THREE.SkeletonHelper(model);
        // 将骨骼设置为可见
        skeleton.visible = true;
        // 向场景中添加骨骼辅助对象
        scene.add(skeleton);

        // 创建控制面板
        createPanel();
        // 获取模型的动画
        animations = gltf.animations;
        // 这里new THREE.AnimationMixer(model);不统一使用一个变量代替是因为,使用一个变量代替后报错了,不知道因为什么
        // 跑
        runAction = new THREE.AnimationMixer(model);
        runAction.clipAction(animations[1]);
        // 放松动作
        idleAction = new THREE.AnimationMixer(model);
        idleAction.clipAction(animations[2]);
        // 步行
        walkAction = new THREE.AnimationMixer(model);
        walkAction.clipAction(animations[3]).play();

        actions = [runAction, idleAction, walkAction];
        animate();
    });

    /** **************上面的是模型的加载和渲染,下面这些都与动画有关***************** */

    // 执行动画
    const animate = () => {
    
    
        // 调用动画帧执行动画
        requestAnimationFrame(animate);
        // 获取当前秒数
        const delta = clock.getDelta();
        // 更新动画混合器、轨道控制器、性能监听器
        actions[active].update(delta);
        controls.update();
        // 重新渲染
        renderer.render(scene, camera);
    };

    // 创建GUI面板
    const createPanel = () => {
    
    
        const panel = new dat.GUI({
    
     width: 310 });
        settings = {
    
    
            visible: true, // 显示模型
            idle: false // 放松
        };
        panel.add(settings, 'visible').name('是否显示').onChange(showModel);
        panel.add(settings, 'idle').name('放松').onChange(runIdleAction);
    };

    // 显示模型
    const showModel = (visibility: boolean) => {
    
    
        // 隐藏模型
        model.visible = visibility;
        // 隐藏骨骼
        skeleton.visible = visibility;
    };

    // 放松
    const runIdleAction = (value:boolean) => {
    
    
        if (value) {
    
    
            // 将跑、走动画停止,开启放松动画
            actions[0].clipAction(animations[1]).stop();
            actions[2].clipAction(animations[3]).stop();
            actions[1].clipAction(animations[2]).play();
            // 更新激活的动作
            active = 1;
        } else {
    
    
        // 变为走
            actions[0].clipAction(animations[1]).stop();
            actions[2].clipAction(animations[3]).play();
            actions[1].clipAction(animations[2]).stop();
            // 更新激活的动作
            active = 2;
        }
    };
});
</script>

<style lang="scss" scoped>
#keyframes {
    
    
    width: 1000px;
    height: 800px;
    background-color: #bfe3dd;
}
</style>

效果图
在这里插入图片描述

知识点

Fog
在常见中添加雾,具体内容可以看官方文档

 scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);

如何产生投影
这个之前梳理过,具体见:threejs视频教程学习(4):贴图、材质、光线

SkeletonHelper
SkeletonHelper,用来模拟骨骼 Skeleton 的辅助对象.。该辅助对象使用 LineBasicMaterial 材质。
模型里面必须有骨骼辅助对象,才能够获取到

// 获取模型的骨骼对象
skeleton = new THREE.SkeletonHelper(model);
// 将骨骼设置为可见
skeleton.visible = true;
// 向场景中添加骨骼辅助对象
scene.add(skeleton);

gui
gui的基本使用见:threejs视频教程学习(3):应用图形用户界面更改变量 (dat.gui的简单使用)

webgl_animation_skinning_additive_blending

做的比较简陋,好多细节都没有实现。
代码

<template>
    <div id="keyframes"> </div>
</template>

<script setup lang="ts">
import {
    
     onMounted } from 'vue';
import * as THREE from 'three';
// 引入gui
import * as dat from 'dat.gui';
// 模型加载器
import {
    
     GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 引入轨道控制器
import {
    
     OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

onMounted(() => {
    
    
    // 获取dom
    const container = document.getElementById('keyframes');
    // 走
    let walkAction = null;
    let walk = null;
    // 跑
    let runAction = null;
    let run = null;
    const clock = new THREE.Clock();

    // 创建场景
    const scene = new THREE.Scene();
    // 设置背景色
    scene.background = new THREE.Color(0xa0a0a0);
    // 设置雾气
    scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
    // 设置半球光
    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
    hemiLight.position.set(0, 20, 0);
    scene.add(hemiLight);
    // 设置平行光
    const dirLight = new THREE.DirectionalLight(0xffffff);
    dirLight.position.set(3, 10, 10);
    dirLight.castShadow = true; // 开启阴影
    scene.add(dirLight);

    // 创建透视相机
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 100);
    camera.position.set(-1, 2, 3);
    scene.add(camera);

    // 创建渲染器
    const renderer = new THREE.WebGLRenderer({
    
    
        antialias: true // 抗锯齿
    });
    renderer.setPixelRatio(window.devicePixelRatio); // 像素比
    renderer.setSize(window.innerWidth, window.innerHeight); // 渲染尺寸
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.shadowMap.enabled = true; // 开启场景阴影渲染
    container?.appendChild(renderer.domElement);

    // 创建轨道控制器
    const control = new OrbitControls(camera, renderer.domElement);
    control.enableZoom = false; // 关闭缩放
    control.target.set(0, 1, 0);
    control.update(); // 更新控制器

    // 创建一个平面
    const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({
    
     color: 0x999999, depthWrite: false }));
    mesh.rotation.x = -Math.PI / 2;
    mesh.receiveShadow = true; // 设置平面接收阴影
    scene.add(mesh);

    // 加载模型
    const loader = new GLTFLoader();
    loader.load('../../../../public/Xbot.glb', gltf => {
    
    
        // 模型
        const model = gltf.scene;
        scene.add(model);
        // 递归找到物体开启阴影
        model.traverse(object => {
    
    
            if (object.isMesh) object.castShadow = true;
        });
        // 获取模型的动作
        const animations = gltf.animations;
        console.log('动画', animations);

        // 跑
        runAction = new THREE.AnimationMixer(model);
        run = runAction.clipAction(animations[3]);

        // 走
        walkAction = new THREE.AnimationMixer(model);
        walk = walkAction.clipAction(animations[6]);

        animate();
    });

    // 执行动画
    const animate = () => {
    
    
        // 调用动画帧执行动画
        requestAnimationFrame(animate);
        // 获取当前时间,这个不能直接new ,不然动画不会生效
        const delta = clock.getDelta();
        if (settings.run) {
    
    
            run.play();
            runAction.update(delta);
        } else {
    
    
            walk.play();
            walkAction.update(delta);
        }
        control.update();
        // 重新渲染
        renderer.render(scene, camera);
    };

    // 添加GUI
    let settings = {
    
    };
    const panel = new dat.GUI({
    
     width: 310 });
    settings = {
    
    
        run: true,
        speed: 1
    };
    panel.add(settings, 'run').name('跑');
    panel.add(settings, 'speed').min(0).max(5)
        .onChange((value) => {
    
    
            // 通过修改AnimationAction对象的timeScale(时间比例因子)来调节动画的速度
            if (settings.run) {
    
    
                runAction.timeScale = value;
            } else {
    
    
                walkAction.timeScale = value;
            }
        });
});
</script>

<style lang="scss" scoped>
#keyframes {
    
    
    width: 1000px;
    height: 800px;
    background-color: #bfe3dd;
}
</style>

效果图
在这里插入图片描述

知识点

主要是AnimationAction 动画对象的应用。动画的速度是通过修改timeScale 来实现的,动作之间的过渡是通过修改weight来实现的,动作过渡没太弄清楚。

详情内容可以看官方文档。

猜你喜欢

转载自blog.csdn.net/weixin_41897680/article/details/127923155