ThreeJS的渲染基础知识这里就不再说了,不太熟悉的可以去www.webgl3d.cn/学习
这里渲染人物组合模型主要涉及:身体渲染、头部模型渲染、头发模型渲染、场景渲染等 接下来我们一步一步拆解教学
初始化canvas等基础数据
所有的渲染都是在canvas上进行的,我们需要设置要canvas的大小,动态设置一下canvas的大小,保证canvas大小和浏览器窗口大小一样
/** 初始化canvas */
initCanvas () {
window.onresize = this.updateCanvas; // 监听窗口大小变化事件,动态设置canvas大小
this.updateCanvas();
},
/** 更新canvas的宽高 */
updateCanvas () {
const threejsCreater = this.$refs.threejsCreater;
const width = threejsCreater.clientWidth;
const height = threejsCreater.clientHeight;
this.canvasStyle = {
width: width + 'px',
height: height + 'px'
};
},
初始化好场景、相机、光源、控制器等具体代码会放到最后
创建身体
组合模型其实就是在同一三维空间坐标中,通过调整模型的坐标位置让其看上去组合在一起,接下来我们以身体为根基进行拼装组合
createBody () {
const loader = new GLTFLoader(); // 创建加载器,不同格式的3D模型使用不同的加载器,例fbx使用FBXLoader
loader.load(`${process.env.BASE_URL}resources/base/body.glb`, (gltf) => {
const model = gltf.scene; // GLTFLoader加载出来之后需要取.scene使用,不同的加载器加载出来的数据结构有一点点差异
this.modelGroup.add(model); // 添加到组中
model.position.y = this.position_y; // 通过变量position_y去统一模型位置
model.traverse((o) => {
// 设置皮肤颜色
if (o.isMesh && o.name === 'Wolf3D_Body') {
o.material.color = new THREE.Color(new THREE.Color('#996144'));
}
// 开启阴影投射
if (o.isMesh) {
o.castShadow = true;
// o.receiveShadow = true; // 是否接收阴影
}
});
});
},
创建头部
头部通过调整位置与身体重合就能组成一个人物模型
createHead () {
const loader = new GLTFLoader();
// 创建脸的material
const texture_hair_base = new THREE.TextureLoader().load(
`${process.env.BASE_URL}resources/base/hair_base.jfif`
);
texture_hair_base.flipY = false; // 纹理将沿垂直轴翻转,默认值是 true
const texture_face_ao = new THREE.TextureLoader().load(
`${process.env.BASE_URL}resources/base/face_ao.jpg`
);
const texture_face_roughness = new THREE.TextureLoader().load(
`${process.env.BASE_URL}resources/base/roughness.jpg`
);
const material_face = new THREE.MeshStandardMaterial({
map: texture_hair_base,
aoMap: texture_face_ao,
roughness: 0.5,
roughnessMap: texture_face_roughness
});
// 创建眼睛的material
const texture_eye = new THREE.TextureLoader().load(`${process.env.BASE_URL}resources/base/eye-01-mask.jpg`);
const material_eye = new THREE.MeshPhysicalMaterial({
map: texture_eye
});
loader.load(`${process.env.BASE_URL}resources/base/head.glb`, (gltf) => {
const model = gltf.scene;
this.modelGroup.add(model);
model.position.y = this.position_y + 1.55;
model.position.z = 0.04;
model.traverse((o) => {
if (o.isMesh && o.name === 'Wolf3D_Head_1') {
// 启用投射和接收阴影的能力
o.castShadow = true;
o.material = material_face;
} else if (o.isMesh && o.name === 'Wolf3D_Head_2') {
// 启用投射和接收阴影的能力
o.castShadow = true;
o.material = material_eye;
}
});
});
},
创建头发
头发和头的原理是一样的,不过是将头发模型盖在头上
createHair () {
const loader = new GLTFLoader();
loader.load(`${process.env.BASE_URL}resources/hair/hair1/hair.glb`, (gltf) => {
const model = gltf.scene;
this.hair_model = model;
this.modelGroup.add(model);
model.position.y = this.position_y + 1.55;
model.position.z = 0.04;
model.traverse((o) => {
if (o.isMesh) {
o.material.color = new THREE.Color('#050505'); // 设置头发颜色
}
});
});
},
创建场景
为了让模型看上去更真实,我们可以创建一个场景,来接收阴影,烘托渲染效果
createScene () {
const loader = new GLTFLoader();
loader.load(`${process.env.BASE_URL}resources/scene/scene1/scene.glb`, (gltf) => {
const model = gltf.scene;
this.scene.add(model);
model.position.y = -16.36;
model.position.z = 1.5;
model.rotateY(-1.5707963267948966);
model.traverse((o) => {
if (o.isMesh) {
o.receiveShadow = true; // 接收阴影
}
});
});
}
模型资源来自readyplayerme,仅供大家学习。
完整代码放在下方
<template>
<div ref="threejsCreater" class="threejs-creater">
<canvas
id="canvas"
ref="canvas"
class="canvas"
:style="canvasStyle"
></canvas>
</div>
</template>
<script>
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
export default {
data () {
return {
scene: null,
camera: null,
renderer: null,
modelGroup: null,
canvasStyle: null,
position_y: -1, // 模型y轴位置
hairColor: '#050505' // 头发颜色
};
},
mounted () {
this.initCanvas();
this.init();
},
methods: {
/** 初始化canvas */
initCanvas () {
window.onresize = this.updateCanvas;
this.updateCanvas();
},
/** 更新canvas的宽高 */
updateCanvas () {
const threejsCreater = this.$refs.threejsCreater;
const width = threejsCreater.clientWidth;
const height = threejsCreater.clientHeight;
this.canvasStyle = {
width: width + 'px',
height: height + 'px'
};
},
/** 创建场景 */
initScene () {
const scene = new THREE.Scene();
this.scene = scene;
// 将modelGroup作为容器存放组合的模型
this.modelGroup = new THREE.Object3D();
this.scene.add(this.modelGroup);
},
/** 创建相机 */
initCamera () {
const camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
0.1,
1000
);
// 设置相机位置
camera.position.z = 3;
camera.position.x = 0;
camera.position.y = 0;
this.camera = camera;
},
/** 创建光源 */
initLight () {
const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.5);
hemiLight.position.set(0, 50, 0);
this.scene.add(hemiLight);
const dirLight1 = new THREE.DirectionalLight(0xffffff, 1);
dirLight1.position.set(5, 5, 5);
dirLight1.castShadow = true;
this.scene.add(dirLight1);
},
/** 创建渲染器 */
initRenderer () {
const canvas = document.querySelector('#canvas');
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true // 抗齿距
});
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true; // 投射阴影
renderer.shadowMap.type = THREE.PCFShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
this.$refs.threejsCreater.appendChild(renderer.domElement);
this.renderer = renderer;
// Threejs控制器,可以帮助我们实现以目标为焦点的旋转缩放等
const controls = new OrbitControls(this.camera, renderer.domElement);
controls.maxPolarAngle = Math.PI / 2;
controls.minPolarAngle = Math.PI / 3;
controls.enableDamping = true;
// controls.enablePan = false;
controls.dampingFactor = 0.1;
controls.autoRotate = false; // Toggle this if you'd like the chair to automatically rotate
controls.autoRotateSpeed = 0.2; // 30
controls.minDistance = 1;
controls.maxDistance = 20;
},
/** 初始化 */
init () {
this.initScene();
this.initCamera();
this.initLight();
this.initRenderer();
this.createBody();
this.createHead();
this.createHair();
this.createScene();
this.clock = new THREE.Clock();
this.update();
},
update () {
if (this.resizeRendererToDisplaySize(this.renderer)) {
const canvas = this.renderer.domElement;
this.camera.aspect = canvas.clientWidth / canvas.clientHeight;
this.camera.updateProjectionMatrix();
}
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.update);
},
resizeRendererToDisplaySize (renderer) {
const canvas = renderer.domElement;
const width = window.innerWidth;
const height = window.innerHeight;
const canvasPixelWidth = canvas.width / window.devicePixelRatio;
const canvasPixelHeight = canvas.height / window.devicePixelRatio;
const needResize =
canvasPixelWidth !== width || canvasPixelHeight !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
},
createBody () {
const loader = new GLTFLoader();
loader.load(`${process.env.BASE_URL}resources/base/body.glb`, (gltf) => {
const model = gltf.scene;
this.modelGroup.add(model); // 添加到组中
model.position.y = this.position_y; // 通过变量position_y去统一模型位置
model.traverse((o) => {
// 设置皮肤颜色
if (o.isMesh && o.name === 'Wolf3D_Body') {
o.material.color = new THREE.Color(new THREE.Color('#996144'));
}
// 开启阴影投射
if (o.isMesh) {
o.castShadow = true;
// o.receiveShadow = true; // 是否接收阴影
}
});
});
},
createHead () {
const loader = new GLTFLoader();
// 创建脸的material
const texture_hair_base = new THREE.TextureLoader().load(
`${process.env.BASE_URL}resources/base/hair_base.jfif`
);
texture_hair_base.flipY = false; // 纹理将沿垂直轴翻转,默认值是 true
const texture_face_ao = new THREE.TextureLoader().load(
`${process.env.BASE_URL}resources/base/face_ao.jpg`
);
const texture_face_roughness = new THREE.TextureLoader().load(
`${process.env.BASE_URL}resources/base/roughness.jpg`
);
const material_face = new THREE.MeshStandardMaterial({
map: texture_hair_base,
aoMap: texture_face_ao,
roughness: 0.5,
roughnessMap: texture_face_roughness
});
// 创建眼睛的material
const texture_eye = new THREE.TextureLoader().load(`${process.env.BASE_URL}resources/base/eye-01-mask.jpg`);
const material_eye = new THREE.MeshPhysicalMaterial({
map: texture_eye
});
loader.load(`${process.env.BASE_URL}resources/base/head.glb`, (gltf) => {
const model = gltf.scene;
this.modelGroup.add(model);
model.position.y = this.position_y + 1.55;
model.position.z = 0.04;
model.traverse((o) => {
if (o.isMesh && o.name === 'Wolf3D_Head_1') {
// 启用投射和接收阴影的能力
o.castShadow = true;
o.material = material_face;
} else if (o.isMesh && o.name === 'Wolf3D_Head_2') {
// 启用投射和接收阴影的能力
o.castShadow = true;
o.material = material_eye;
}
});
});
},
createHair () {
const loader = new GLTFLoader();
loader.load(`${process.env.BASE_URL}resources/hair/hair1/hair.glb`, (gltf) => {
const model = gltf.scene;
this.hair_model = model;
this.modelGroup.add(model);
model.position.y = this.position_y + 1.55;
model.position.z = 0.04;
model.traverse((o) => {
if (o.isMesh) {
o.material.color = new THREE.Color('#050505'); // 设置头发颜色
}
});
});
},
createScene () {
const loader = new GLTFLoader();
loader.load(`${process.env.BASE_URL}resources/scene/scene1/scene.glb`, (gltf) => {
const model = gltf.scene;
this.scene.add(model);
model.position.y = -16.36;
model.position.z = 1.5;
model.rotateY(-1.5707963267948966);
model.traverse((o) => {
if (o.isMesh) {
o.receiveShadow = true; // 接收阴影
}
});
});
}
}
};
</script>
<style lang="scss" scoped>
.threejs-creater {
position: relative;
width: 100%;
height: 100%;
.canvas {
display: block;
touch-action: none;
}
}
</style>