文章目录
前言
随着Web技术的飞速发展,3D可视化已不再是游戏和影视领域的专属。如今,通过WebGL技术,我们可以在浏览器中直接渲染复杂的3D模型,为用户带来身临其境的沉浸式体验。Three.js作为最流行的WebGL库,将复杂的3D图形学概念封装成简洁易用的API,让开发者无需深入了解底层细节就能创建惊艳的3D场景。
提示:以下是本篇文章正文内容,下面案例可供参考
一、完整代码
html
<!DOCTYPE html>
<html>
<head>
<title>3D模型展示器</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
#container {
width: 100vw;
height: 100vh;
}
.controls-panel {
position: fixed;
top: 20px;
left: 20px;
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
z-index: 1000;
width: 300px;
}
.controls-panel h2 {
margin-top: 0;
color: #333;
}
.control-group {
margin: 15px 0;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: bold;
}
input[type="range"],
select,
button {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
cursor: pointer;
font-weight: bold;
transition: transform 0.2s;
}
button:hover {
transform: translateY(-2px);
}
.model-info {
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 14px;
position: fixed;
bottom: 20px;
left: 20px;
z-index: 1000;
}
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 20px;
z-index: 1001;
}
</style>
</head>
<body>
<!-- 加载提示 -->
<div class="loading" id="loading">加载3D模型中...</div>
<!-- 3D渲染容器 -->
<div id="container"></div>
<!-- 控制面板 -->
<div class="controls-panel">
<h2>3D模型控制器</h2>
<div class="control-group">
<label>模型选择</label>
<select id="modelSelect">
<option value="duck">示例鸭子</option>
<option value="robot">机器人</option>
<option value="car">汽车</option>
<option value="custom">自定义模型</option>
</select>
</div>
<div class="control-group">
<label>旋转速度</label>
<input type="range" id="rotationSpeed" min="0" max="0.1" step="0.001" value="0.005">
</div>
<div class="control-group">
<label>缩放比例</label>
<input type="range" id="scale" min="0.1" max="5" step="0.1" value="1">
</div>
<div class="control-group">
<label>背景颜色</label>
<input type="color" id="backgroundColor" value="#f0f0f0">
</div>
<div class="control-group">
<label>模型颜色</label>
<input type="color" id="modelColor" value="#ffffff">
</div>
<button id="resetView">重置视图</button>
<button id="toggleRotation">暂停旋转</button>
<button id="toggleAxes">隐藏坐标轴</button>
<button id="toggleGrid">隐藏网格</button>
<button id="screenshot">截图保存</button>
<!-- 自定义模型上传 -->
<div class="control-group" style="margin-top: 20px;">
<label>上传自定义模型 (GLTF/GLB格式)</label>
<input type="file" id="modelUpload" accept=".gltf,.glb">
</div>
</div>
<!-- 模型信息显示 -->
<div class="model-info">
<div>模型信息: <span id="modelName">加载中...</span></div>
<div>多边形数: <span id="polyCount">-</span></div>
<div>内存使用: <span id="memoryUsage">-</span> MB</div>
</div>
<!-- 引入Three.js库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- GLTF加载器 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
<!-- DRACO加载器(用于压缩模型) -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/DRACOLoader.js"></script>
<!-- 轨道控制器 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script>
// 初始化变量
let scene, camera, renderer, controls, model;
let isRotating = true;
let rotationSpeed = 0.005;
let axesVisible = true;
let gridVisible = true;
// 可用模型库
const modelLibrary = {
duck: 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@dev/examples/models/gltf/Duck/glTF/Duck.gltf',
robot: 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@dev/examples/models/gltf/RobotExpressive/glTF/RobotExpressive.gltf',
car: 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@dev/examples/models/gltf/Lamborghini/glTF/Lamborghini.gltf'
};
// 初始化Three.js场景
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(5, 5, 5);
// 创建渲染器
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('container').appendChild(renderer.domElement);
// 添加光源
addLights();
// 添加轨道控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.maxPolarAngle = Math.PI;
controls.minDistance = 1;
controls.maxDistance = 50;
// 加载初始模型
loadModel(modelLibrary.duck, '示例鸭子');
// 添加辅助器
addHelpers();
// 开始动画循环
animate();
// 添加事件监听
setupEventListeners();
// 隐藏加载提示
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 1000);
}
// 添加光源
function addLights() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
// 方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 5);
directionalLight.castShadow = true;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
scene.add(directionalLight);
// 点光源
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.set(-10, 10, -5);
scene.add(pointLight);
}
// 添加辅助器
function addHelpers() {
// 坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
axesHelper.name = 'axesHelper';
scene.add(axesHelper);
// 网格辅助器
const gridHelper = new THREE.GridHelper(20, 20, 0x888888, 0x444444);
gridHelper.name = 'gridHelper';
scene.add(gridHelper);
}
// 加载3D模型
function loadModel(url, modelName) {
const loader = new THREE.GLTFLoader();
// 设置DRACO解码器路径(用于压缩模型)
const dracoLoader = new THREE.DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
loader.setDRACOLoader(dracoLoader);
// 显示加载提示
document.getElementById('loading').style.display = 'block';
// 移除旧模型
if (model) {
scene.remove(model);
// 清理旧模型的材质和几何体
model.traverse(child => {
if (child.isMesh) {
child.geometry.dispose();
child.material.dispose();
}
});
}
loader.load(
url,
// 加载成功回调
function (gltf) {
model = gltf.scene;
// 设置模型属性
model.scale.set(2, 2, 2);
model.position.set(0, 0, 0);
// 启用阴影
model.traverse(child => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add(model);
// 更新模型信息
updateModelInfo(modelName);
// 隐藏加载提示
document.getElementById('loading').style.display = 'none';
console.log(`${modelName} 加载成功!`);
},
// 加载进度回调
function (xhr) {
const percentComplete = (xhr.loaded / xhr.total * 100);
document.getElementById('loading').innerHTML =
`加载3D模型中... ${percentComplete.toFixed(1)}%`;
},
// 加载错误回调
function (error) {
console.error('模型加载失败:', error);
document.getElementById('loading').innerHTML =
'模型加载失败,正在显示备用模型...';
// 显示备用立方体
showFallbackModel();
updateModelInfo('备用模型');
}
);
}
// 显示备用模型
function showFallbackModel() {
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshPhongMaterial({
color: 0x00ff00,
shininess: 100
});
model = new THREE.Mesh(geometry, material);
model.castShadow = true;
scene.add(model);
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 1000);
}
// 更新模型信息
function updateModelInfo(name) {
document.getElementById('modelName').textContent = name;
// 计算多边形数量
let polyCount = 0;
if (model) {
model.traverse(child => {
if (child.isMesh && child.geometry) {
polyCount += child.geometry.attributes.position.count / 3;
}
});
}
document.getElementById('polyCount').textContent = polyCount.toLocaleString();
// 估算内存使用(粗略估算)
const memoryMB = (polyCount * 32) / (1024 * 1024);
document.getElementById('memoryUsage').textContent = memoryMB.toFixed(2);
}
// 设置事件监听
function setupEventListeners() {
// 模型选择
document.getElementById('modelSelect').addEventListener('change', function (e) {
const selectedModel = e.target.value;
if (selectedModel === 'custom') {
document.getElementById('modelUpload').click();
} else if (modelLibrary[selectedModel]) {
loadModel(modelLibrary[selectedModel], selectedModel);
}
});
// 自定义模型上传
document.getElementById('modelUpload').addEventListener('change', function (e) {
const file = e.target.files[0];
if (file) {
const url = URL.createObjectURL(file);
loadModel(url, '自定义模型');
}
});
// 旋转速度控制
document.getElementById('rotationSpeed').addEventListener('input', function (e) {
rotationSpeed = parseFloat(e.target.value);
});
// 缩放控制
document.getElementById('scale').addEventListener('input', function (e) {
if (model) {
const scale = parseFloat(e.target.value);
model.scale.set(scale, scale, scale);
}
});
// 背景颜色控制
document.getElementById('backgroundColor').addEventListener('input', function (e) {
scene.background = new THREE.Color(e.target.value);
});
// 模型颜色控制
document.getElementById('modelColor').addEventListener('input', function (e) {
if (model) {
model.traverse(child => {
if (child.isMesh && child.material) {
child.material.color.set(e.target.value);
}
});
}
});
// 重置视图
document.getElementById('resetView').addEventListener('click', function () {
controls.reset();
camera.position.set(5, 5, 5);
if (model) {
model.rotation.set(0, 0, 0);
}
});
// 切换旋转
document.getElementById('toggleRotation').addEventListener('click', function () {
isRotating = !isRotating;
this.textContent = isRotating ? '暂停旋转' : '恢复旋转';
});
// 切换坐标轴
document.getElementById('toggleAxes').addEventListener('click', function () {
axesVisible = !axesVisible;
const axesHelper = scene.getObjectByName('axesHelper');
if (axesHelper) {
axesHelper.visible = axesVisible;
}
this.textContent = axesVisible ? '隐藏坐标轴' : '显示坐标轴';
});
// 切换网格
document.getElementById('toggleGrid').addEventListener('click', function () {
gridVisible = !gridVisible;
const gridHelper = scene.getObjectByName('gridHelper');
if (gridHelper) {
gridHelper.visible = gridVisible;
}
this.textContent = gridVisible ? '隐藏网格' : '显示网格';
});
// 截图保存
document.getElementById('screenshot').addEventListener('click', function () {
renderer.render(scene, camera);
const dataURL = renderer.domElement.toDataURL('image/png');
const link = document.createElement('a');
link.download = '3d-model-screenshot.png';
link.href = dataURL;
link.click();
alert('截图已保存!');
});
// 窗口大小调整
window.addEventListener('resize', function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 键盘控制
window.addEventListener('keydown', function (event) {
if (!model) return;
switch (event.key) {
case 'ArrowUp':
model.position.y += 0.5;
break;
case 'ArrowDown':
model.position.y -= 0.5;
break;
case 'ArrowLeft':
model.position.x -= 0.5;
break;
case 'ArrowRight':
model.position.x += 0.5;
break;
case ' ':
isRotating = !isRotating;
break;
}
});
// 鼠标点击交互
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
window.addEventListener('click', function (event) {
// 计算鼠标位置
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 更新射线
raycaster.setFromCamera(mouse, camera);
// 检测相交
if (model) {
const intersects = raycaster.intersectObject(model, true);
if (intersects.length > 0) {
// 点击到模型
const intersect = intersects[0];
// 添加点击效果
const originalColor = intersect.object.material.color.getHex();
intersect.object.material.color.setHex(0xff0000);
setTimeout(() => {
intersect.object.material.color.setHex(originalColor);
}, 200);
// 显示点击位置信息
console.log('点击位置:', intersect.point);
}
}
});
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
// 模型旋转
if (model && isRotating) {
model.rotation.y += rotationSpeed;
}
controls.update();
renderer.render(scene, camera);
}
// 初始化应用
init();
</script>
</body>
</html>
总结
由于时间原因,未能一个个知识点列出来进行解析,此篇文章在于全面体验3D的可视化实现。