您是否发现了一个非常逼真的3D模型,但不确定是否能将其集成到Three.js应用程序中?不要害怕,本文可以满足你的需求。
如何下载模型
首先,在加载模型之前,我们需要准备好一个项目。如果你已经准备好了,那就太好了!如果没有,也不用担心我有一个模板可用。
启动并运行您的项目:
现在,让我们选择一个模型来使用。就我个人而言,我推荐quaternius.com上的免费模型。对于这个例子,我将使用飞龙。
模型有三种文件扩展名: .obj、 .fbx和 .gltf。虽然它们都可以工作,但建议使用 .gltf 和 .glb 文件。 加载模型需要一个加载器。有多种加载器可用,您需要使用的加载器取决于模型的文件扩展名。
在我们的例子中,我们将使用GLTFLoader,因为文件扩展名是 .gltf。
注意: GLTFLoader用于加载 .gltf和 .glb文件,因为它们基本上是相同的格式。
要使用GLTFLoader,我们必须首先将其导入到项目中。
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader';
下一步是创建一个GLTFLoader的实例,并在其上调用load方法。load方法有两个参数:第一个是文件的路径,第二个是回调函数。
const gltfLoader = new GLTFLoader();
gltfLoader.load('./assets/Dragon_Evolved.gltf', function(gltf) {
// What should be done once the model is loaded.
});
将其集成到场景中:
scene.add(model);
正如你所看到的,模型被引入到场景中。但由于没有光源,它是全黑的。让我们添加一些光。
// Outside the body of the load callback function.
const aLight = new THREE.AmbientLight(0xFFFFFF, 1);
scene.add(aLight);
const dLight = new THREE.DirectionalLight(0xFFFFFF, 10);
scene.add(dLight);
dLight.position.set(4, 10, 3);
完成后,您现在应该可以看到您的模型。
load()的第三个参数是一个在加载过程中调用的函数,它提供有关已加载数据量的信息。
// Ensure that you have a large file available for loading.
// The one I'm loading here is 14.1 MB
gltfLoader.load('./assets/free_datsun_280z.glb', function(glb) {
const model = glb.scene;
scene.add(model);
}, function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
});
xhr参数表示XMLHttpRequest实例,该实例包含表示文件总大小的total和表示已加载字节数的loaded。
load()方法的第四个也是最后一个参数是一个函数,在发生加载错误时调用。它接受一个参数,其中包含默认的错误消息。
gltfLoader.load('./assets/wrong_file_name.glb', function(glb) {
const model = glb.scene;
scene.add(model);
}, function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, function (error) {
console.log('Loading failed');
});
带动画的模型
为模型设置动画
你可能知道,模型通常带有一组动画。通常,下载模型的网站提供了预览这些动画的方法。但是,如果他们不这样做,有一个方法来查看这些动画。
说到这里,转到Three.js编辑器并拖放您的模型。
同样,由于没有光源,您的模型看起来全黑。要添加一个光源,在导航菜单上的添加平行光。
现在,要在我们自己的场景中播放我们刚刚在编辑器中看到的动画,我们需要遵循以下步骤。
首先,我们需要创建一个全局变量。
// Outside the body of the load callback function.
let mixer;
然后,在load回调中,我们将AnimationMixer类的一个实例赋给mixer变量,并将模型作为参数传递给构造函数。
AnimationMixer是场景中的动画播放器。
// Within the body of the load callback function.
mixer = new THREE.AnimationMixer(model);
接下来,我们将AnimationClip调用静态方法findByName()选择要播放的动画。
// Within the body of the load callback function.
// Array of clips
const clips = gltf.animations;
mixer = new THREE.AnimationMixer(model);
const clip =
THREE.AnimationClip.findByName(clips, 'Flying_Idle');
// You can select it directly if you know
// its index in the clips array.
// const clip = clips[2];
下一步是使用mixer中的clipAction()方法将选定的动画转换为可播放的动作,然后调用play()开始播放动画。
动作通常指动画的可播放实例。它表示场景中特定动画片段的控制和播放。当您使用clipAction()方法将剪辑转换为动作时,您实际上是在创建该动画的可播放实例。
// Within the body of the load callback function.
const action = mixer.clipAction(clip);
action.play();
要播放动画,我们将导航到animate()函数,并从mixer变量调用update()方法。
update()方法需要一个参数deltaTime。它使您能够根据时间的推移更新对象位置、速度和其他属性,从而确保即使帧速波动,动画也以一致的速率进行。我们从Clock实用程序获取deltaTime。
const clock = new THREE.Clock();
function animate() {
if(mixer) {
const delta = clock.getDelta();
mixer.update(delta);
}
renderer.render(scene, camera);
}
完成后,您的模型现在应该有动画了。
AnimationAction属性和方法
现在,让我们探索一些操作方法和属性。
我们可以使用paused属性来暂停动画。将paused设置为true将暂停动画,将其设置为false将恢复动画。
// Within the body of the load callback function.
window.addEventListener('keydown', function(e) {
if(e.code === 'Space')
action.paused = !action.paused;
});
我们可以将动画设置为循环一次,每次结束时从头开始重复,或者在向前和向后动画之间交替。
// plays the clip once.
action.loop = THREE.LoopOnce;
// Repeats the animation from the beginning each time it ends.
// This is the default loop mode.
action.loop = THREE.LoopRepeat;
// Repeats the animation forwards and backwards,
// from start to end and then from end to start.
action.loop = THREE.LoopPingPong;
如果要设置特定的重复次数,可以直接为repetitions属性赋值,也可以使用setLoop()方法。
// Option 1
action.loop = THREE.LoopPingPong;
action.repetitions = 3;
// Option 2
action.setLoop(THREE.LoopPingPong, 3);
如果你想在一段时间内逐渐引入动画,而不是立即引入。你可以使用fadeIn()方法。
const action = mixer.clipAction(clip);
action.play();
// The animation will start and gradually increase
// in speed, reaching the normal speed after 5 seconds.
action.fadeIn(5);
我们还有fadeOut(),它的作用与fadeIn()相反。这意味着动画将逐渐降低速度,直到完全停止。
const action = mixer.clipAction(clip);
action.play();
// The animation will start and gradually decrease
// in speed, stopping completely after 5 seconds.
action.fadeOut(5);
此外,我们还有crossFadeTo()和crossFadeFrom()方法,它们可以在不同的动画之间实现无缝过渡。
const clip2 =
THREE.AnimationClip.findByName(clips, 'Fast_Flying');
const action2 = mixer.clipAction(clip2);
window.addEventListener('keydown', function(e) {
if(e.code === 'Space')
action.paused = !action.paused;
if(e.code === 'KeyF') {
action2.play();
action.crossFadeTo(action2, 1);
}
});
现在,按下F键将动画从Idle动作转换为Fast_Flying动作。
window.addEventListener('keydown', function(e) {
if(e.code === 'Space')
action.paused = !action.paused;
if(e.code === 'KeyF') {
action2.play();
action.crossFadeTo(action2, 1);
}
if(e.code === 'KeyS') {
action.play();
action.crossFadeFrom(action2, 1);
}
});
混合器事件和动画链接
有两个事件表示在mixer下订阅的动作的完成。
要确定一个动作是否完成,我们需要使用“finished”事件。
重要的是要注意,您必须为动作设置有限的重复次数。
const clip3 =
THREE.AnimationClip.findByName(clips, 'Yes');
const action3 = mixer.clipAction(clip3);
action3.play();
action3.loop = THREE.LoopOnce;
const clip4 =
THREE.AnimationClip.findByName(clips, 'No');
const action4 = mixer.clipAction(clip4);
action4.play();
action4.repetitions = 3;
mixer.addEventListener('finished', function(e) {
console.log(`Action ${e.action._clip.name} is finished`);
});
回调函数中的代码将执行两次:一次是在action3完成时,另一次是在action4的所有重复完成时。
要确定循环的一次迭代是否完成,我们需要使用“loop”事件。
const clip4 =
THREE.AnimationClip.findByName(clips, 'No');
const action4 = mixer.clipAction(clip4);
action4.play();
action4.repetitions = 3;
mixer.addEventListener('loop', function(e) {
console.log(`One iteration of ${e.action._clip.name} is finished`);
});
现在,有了这些知识,我们可以很容易地创建一个由几个动作组成的简单循环。
gltfLoader.load('./assets/Dragon_Evolved.gltf', function(gltf) {
const model = gltf.scene;
scene.add(model);
const clips = gltf.animations;
mixer = new THREE.AnimationMixer(model);
const clip3 =
THREE.AnimationClip.findByName(clips, 'Yes');
const action3 = mixer.clipAction(clip3);
action3.play();
action3.loop = THREE.LoopOnce;
const clip4 =
THREE.AnimationClip.findByName(clips, 'No');
const action4 = mixer.clipAction(clip4);
action4.loop = THREE.LoopOnce;
mixer.addEventListener('finished', function(e) {
if(e.action._clip.name === 'Yes') {
action4.reset();
action4.play();
} else
if(e.action._clip.name === 'No') {
action3.reset();
action3.play();
}
});
});
真实模型
你遇到了一个令人着迷的超现实模型,比如这辆车,并决定在你的应用程序中使用它。然而,在设置了必要的代码后,你遇到的不是预期的有光泽和反光的汽车,而是这个结果。
模型显示在具有AmbientLight和DirectionalLight实例的场景中,但它看起来不像在Sketchfab查看器中那样逼真。这背后的原因是汽车模型中使用的材料需要特殊类型的照明。
为了实现逼真的外观,照明也需要逼真。这可以通过从 .hdr图像加载灯光并将其设置为环境光源来实现。
如果你不熟悉环境和.hdr图像,这篇文章绝对是必读!
为了在Three.js中加载.hdr镜像,我们使用了一个特殊的加载器,称为RGBELoader。
import {RGBELoader} from 'three/examples/jsm/loaders/RGBELoader';
然后,创建它的一个实例并调用load(),传递两个参数:.hdr图像的路径和一个回调函数。
const rgbeLoader = new RGBELoader();
rgbeLoader.load('./assets/hdrImage.hdr', function(texture) {
});
在回调函数中,指定纹理的贴图模式,并将其设置为场景的环境贴图。
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
最后,您可以选择将THREE.ACESFilmicToneMapping设置为渲染器的色调映射算法,以获得更好的效果。
const gltfLoader = new GLTFLoader();
const rgbeLoader = new RGBELoader();
renderer.toneMapping = THREE.ACESFilmicToneMapping;
rgbeLoader.load('./assets/hdrImage.hdr', function(texture) {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
gltfLoader.load('./assets/car.glb', function(glb) {
const model = glb.scene;
scene.add(model);
});
});
结果如下: