第一课
了解 three 安装和基础应用 做了一个 甜甜圈 案例
提到 Three.js,不得不先提 OpenGL 和 WebGL,OpenGL 是一个跨平台的3D/2D的绘图标准(规范),WebGL(Web Graphics Library)是一种3D绘图协议。
WebGL允许把JavaScript和OpenGL 结合在一起运用,但使用WebGL原生的API来写3D程序非常的复杂,同时需要相对较多的数学知识,对于开发者来说学习成本非常高。
Three.js是基于webGL的封装的一个易于使用且轻量级的3D库,Three.js对WebGL提供的接口进行了非常好的封装,简化了很多细节,大大降低了学习成本,极大地提高了性能,功能也非常强大。
使用 npm 安装threejs
npm i three
然后,引入three
import * as THREE from 'three';
整体代码
<!-- -->
<template>
<button @click="glbPlay">aaa</button>
<div id="three" ref="three">
<div ref="le" ></div>
</div>
</template>
<script setup lang="ts">
import {ref,reactive,onMounted,onUnmounted} from 'vue';
import * as dat from 'dat.gui';
import {
AmbientLight,
AnimationMixer,
AxesHelper,
CameraHelper,
Color,
DirectionalLight,
DirectionalLightHelper,
EquirectangularReflectionMapping,
HemisphereLight,
LoopOnce,
Mesh,
MeshLambertMaterial,
PerspectiveCamera,
PlaneGeometry,
PointLight,
Scene,
SpotLight,
sRGBEncoding,
WebGLRenderer,
} from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
/**
* 配置
*/
// MARK: 配置
const le = ref();
const three = ref();
const mixer = ref();
const donuts = ref();
const action = ref();
const clips = ref();
const scene = new Scene(); // 场景对象Scene
const axes = new AxesHelper(50); // 轴长度
const th = reactive({
ctrl: new dat.GUI(),
renderer:new WebGLRenderer({
antialias: true
}), // 渲染器对象
ambienLight : new AmbientLight(0xcccccc), // 自然光
planeGeometry:new PlaneGeometry(100,100),// 地面
spotLight:new SpotLight(0xFFFFFF),// 聚光灯
pointlight:new PointLight(0xFFFFFF,6,60), // 点光源
directionalLight:new DirectionalLight(0xFFFFFF,4), // 平行光源
hemisphereLight : new HemisphereLight(0xffffff,0x00ff00,1)// 半球光光源
});
scene.background =new Color(0x333333);
scene.add(axes); // 添加轴
new RGBELoader()
.load('./HDR/sky.hdr', function (texture) {
scene.background = texture;
texture.mapping = EquirectangularReflectionMapping;
scene.environment = texture;
th.renderer.outputEncoding = sRGBEncoding;
th.renderer.render(scene, camera);
});
let render=()=>{
th.renderer.render(scene,camera);
}
/**
* 影相机
*/
// MARK: 影相机
// let camera = ref<PerspectiveCamera|OrthographicCamera>(new PerspectiveCamera(75,window.innerWidth/window.innerHeight,5,10000)); //透视相机
let camera = new PerspectiveCamera(75,window.innerWidth/window.innerHeight,10,1000); //透视相机
camera.position.set(-20, 20, 30);
camera.lookAt(scene.position);
/**
* 灯光
*/
// MARK: 灯光
th.spotLight.position.set(0, 30, 60);
th.spotLight.castShadow = true;
th.spotLight.intensity = 0.6; // 光照强度
// th.spotLight.shadow.mapSize = new Vector2(1024, 1024); // shadow 与计算此光照的阴影
// th.spotLight.shadow.camera.far = 130; // far:远面
// th.spotLight.shadow.camera.near = 40; // near:近面
scene.add(th.spotLight);
scene.add(th.ambienLight); // 自然光
// const pointlight = new PointLight(0xFFFFFF,6,60); // 点光源
th.pointlight.position.x = 0;
th.pointlight.position.y = 0;
th.pointlight.position.z = 5;
// scene.add(th.pointlight); // 点光源
new DirectionalLight(0xFFFFFF,4) // 平行光源
th.directionalLight.castShadow=true;
th.directionalLight.shadow.mapSize.width = 2048;
th.directionalLight.shadow.mapSize.height = 2048;
th.directionalLight.position.set(20, 20, 20);
// scene.add(th.directionalLight);
const helper =new DirectionalLightHelper(th.directionalLight,5);
// scene.add(helper);
const dirCameraHelper = new CameraHelper(th.directionalLight.shadow.camera)
// scene.add(dirCameraHelper);
const hemisphereLight = new HemisphereLight(0xffffff,0x00ff00,1);
hemisphereLight.position.set(100,50,100);
// scene.add(hemisphereLight);// 半球光光源
/**
* 渲染 场景
*/
// MARK: 渲染 场景
th.renderer.shadowMap.enabled = true;
th.renderer.setSize(window.innerWidth,window.innerHeight);
/**
* 地面
*/
// MARK: 地面
const lambertMaterial = new MeshLambertMaterial({//材质对象Lambert
color:0xcccccc
});
const plane = new Mesh(th.planeGeometry,lambertMaterial); // 网格模型
plane.rotation.x=-0.5*Math.PI;
plane.position.set(15, 0, 0);
plane.receiveShadow = true;
// scene.add(plane);
/**
* OrbitControls 轨道控制器
*/
// MARK: OrbitControls 轨道控制器
let controls = new OrbitControls(camera,th.renderer.domElement);
controls.update();
/**
* 导入模型
*/
// MARK: 导入模型
new GLTFLoader().setPath('./model/test/').load('test.glb',gltf=>{
console.log(gltf);
scene.add(gltf.scene);
donuts.value = gltf.scene;
// gltf.scene.traverse((child)=>{
// console.log(child.name);
// })
clips.value = gltf.animations; // 播放所有动画
});
let glbPlay=()=>{
mixer.value = new AnimationMixer(donuts.value);
clips.value.forEach( (clip: any)=> {
action.value = mixer.value.clipAction(clip);
action.value.loop = LoopOnce;
action.value.timeScale=1.5;
// 停在最后一帧
action.value.clampWhenFinished = true;
action.value.play();
});
}
//============================================场景搭建end==================================
onMounted(()=>{
le.value.appendChild(th.renderer.domElement);
window.addEventListener('resize',()=>{
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
th.renderer.setSize(window.innerWidth,window.innerHeight);
render();
},false);
});
//============================================构建场景 start==================================
// MARK: 构建场景
//============================================构建场景 end==================================
let renderScene=():void=>{
controls.update();
requestAnimationFrame(renderScene);
render();
if (donuts.value){
donuts.value.rotation.y += 0.01;
}
if (mixer.value) {
mixer.value.update(0.02);
}
}
renderScene()
onUnmounted(()=>{
th.ctrl.destroy() // 销毁dat.GUI
});
</script>
<style scoped lang="less">
@import '~@/common/less/index.less';
</style>
以上代码可以直接运行,运行的效果如下
因为自己会建模,所以没有用到老师的模形
第二课
Blender 的建模基础
主要自己会建模这一课没怎么认真听
第三课
元宇宙开发
角色引入
// 角色引入
new GLTFLoader().setPath("./model/test/").load("player.glb", gltf => {
playerMesh.value = gltf.scene;
gltf.scene.position.set(0, -1.43, 14.5);
gltf.scene.rotateY(Math.PI);
gltf.scene.traverse((child)=>{
child.receiveShadow = true;
child.castShadow = true;
})
camera.position.set(0, 2.5, -4);
camera.lookAt(lookTarget);
const pointLight: PointLight = new PointLight(0xffffff, 1);
pointLight.position.set(0, 1.8, -1);
playerMixer.value = new AnimationMixer(gltf.scene);
const clipWalk = AnimationUtils.subclip(gltf.animations[0], 'walk', 0, 30); // 走路动画
actionWalk.value = playerMixer.value.clipAction(clipWalk);
const clipIdle = AnimationUtils.subclip(gltf.animations[0], 'idle', 31, 281); // 待机动画
actionIdle.value = playerMixer.value.clipAction(clipIdle);
actionIdle.value.play();
gltf.scene.add(pointLight);
gltf.scene.add(camera);
scene.add(gltf.scene);
});
场景搭建
// 场景的整体搭建
// 给场景上贴图 和 贴视频
new GLTFLoader().setPath("./model/test/").load("caab1.glb", (gltf) => {
// console.log(gltf);
scene.add(gltf.scene);
donuts.value = gltf.scene;
gltf.scene.traverse((child) => {
// console.log(child.name);
child.castShadow = true;
child.receiveShadow = true;
console.log(child.name);
if (child.name == "2023") {
happy.value = child;
const video = document.createElement("video");
video.src = "./img/yanhua.mp4";
video.muted = true;
video.autoplay = true;
video.loop = true;
video.play();
// console.log(document.body);
// document.body.append(video);
const videoTexture = new VideoTexture(video);
const videoMaterial = new MeshBasicMaterial({ map: videoTexture });
(child as any).material = videoMaterial;
}
if (child.name === "video1") {
const video = document.createElement("video");
video.src = "./img/video1.mp4";
video.muted = true;
video.autoplay = true;
video.loop = true;
video.play();
const videoTexture = new VideoTexture(video);
const videoMaterial = new MeshBasicMaterial({ map: videoTexture });
(child as any).material = videoMaterial;
}
if (child.name === "video2") {
const video = document.createElement("video");
video.src = "./img/video2.mp4";
video.muted = true;
video.autoplay = true;
video.loop = true;
video.play();
const videoTexture = new VideoTexture(video);
const videoMaterial = new MeshBasicMaterial({ map: videoTexture });
(child as any).material = videoMaterial;
}
if (child.name === "ccc1") {
const video = document.createElement("video");
video.src = "./img/ccc1.mp4";
video.muted = true;
video.autoplay = true;
video.loop = true;
video.play();
const videoTexture = new VideoTexture(video);
const videoMaterial = new MeshBasicMaterial({ map: videoTexture });
(child as any).material = videoMaterial;
}
if (child.name === "bbb1" || child.name === "bbb2" || child.name === "bbb3") {
const video = document.createElement("video");
video.src = "./img/bbb1.mp4";
video.muted = true;
video.autoplay = true;
video.loop = true;
video.play();
const videoTexture = new VideoTexture(video);
const videoMaterial = new MeshBasicMaterial({ map: videoTexture });
(child as any).material = videoMaterial;
}
if (child.name == "image1") {
let imageTexture = new TextureLoader().load('./img/Cam1.jpg');
let imageMaterial = new MeshBasicMaterial({
map:imageTexture,
side:BackSide
});
(child as any).material = imageMaterial;
}
if (child.name == "image2") {
let imageTexture = new TextureLoader().load('./img/Cam2.jpg');
let imageMaterial = new MeshBasicMaterial({
map:imageTexture,
side:BackSide
});
(child as any).material = imageMaterial;
}
if (child.name == "image3") {
let imageTexture = new TextureLoader().load('./img/Came3.jpg');
let imageMaterial = new MeshBasicMaterial({
map:imageTexture,
side:BackSide
});
(child as any).material = imageMaterial;
}
if (child.name == "image4") {
let imageTexture = new TextureLoader().load('./img/Came4.jpg');
let imageMaterial = new MeshBasicMaterial({
map:imageTexture,
side:BackSide
});
(child as any).material = imageMaterial;
}
});
});
场景搭建
// 碰撞检测
window.addEventListener("keydown", (e) => {
if (e.key == "w") {
// if(playerMesh.value){
// playerMesh.value.translateZ(0.1);
// }
if(playerMesh.value){
const curPos = playerMesh.value.position.clone();
playerMesh.value.translateZ(1);
const frontPos = playerMesh.value.position.clone();
playerMesh.value.translateZ(-1);
const frontVector3 = frontPos.sub(curPos).normalize()
const raycasterFront = new Raycaster(playerMesh.value.position.clone().add(playerHalfHeight), frontVector3);
const collisionResultsFrontObjs = raycasterFront.intersectObjects(scene.children);
console.log(collisionResultsFrontObjs);
if ((collisionResultsFrontObjs && collisionResultsFrontObjs[0] && collisionResultsFrontObjs[0].distance > 1) || collisionResultsFrontObjs.length==0) {
console.log('AAAAAAAAAAAAAAAAAAAAAAw');
playerMesh.value.translateZ(0.1);
}
}
if (!isWalk.value) {
if( actionIdle.value && actionWalk.value){
crossPlay(actionIdle.value,actionWalk.value);
}
isWalk.value = true;
}
}
three元宇宙开发 | 大帅老猿threejs特训
结束语
可以加入猿创营 (v:dashuailaoyuan),一起交流学习。