借助DeepSeek实现在MkDocs-Material网页上使用Three.js展示3D模型¶
约 5052 个字 • 1227 行代码
之前在查看kinect摄像头github仓库时,发现其中有一个摄像头的3d模型文件,于是就产生了把这个3d模型文件展示在文档首页的想法。
之后搜索了一下如何在html中显示3d模型,发现主流是使用three.js。查看了three.js的说明文档,但是感觉太复杂了(有很多东西需要设置),于是就直接开始询问deepseek,最后一步步将idea实现成了实际的效果。
大致的过程是:
- 询问deepseek如何在(空白的)html中使用three.js加载glb模型文件(一种3d模型的文件格式)
- 参照deepseek的回答创建本地的文件,尝试运行并查看效果
- 继续跟deepseek说要添加或修改调整的效果
- 效果基本满意之后,询问deepseek如何将这个东西移植到mkdocs构建的网页中
- 按照deepseek的回答修改原有的文件,并
mkdocs serve
预览查看效果- 询问deepseek如何修改实现自己想要的效果
- 最终实现
第1段对话记录
You:
我想在html中使用three.js来加载glb 3d模型文件,你能帮我编写一个这样的main.js文件吗?
DeepSeek:
已深度思考(用时 23 秒)
嗯,用户想在HTML里用Three.js加载GLB模型,我得帮他写个main.js。首先,我得回想一下Three.js的基本结构。通常需要初始化场景、相机、渲染器,然后加载模型。
用户可能不太熟悉Three.js的步骤,所以代码要注释清楚。首先,创建场景,设置背景色,比如灰色。然后透视相机,设置好视场、宽高比、近远平面。渲染器要用WebGL,并调整大小到窗口。
接下来是光照,环境光和方向光,这样模型看起来不会太暗。然后GLTF加载器,Three.js自带了吗?可能需要从examples/jsm/loaders里导入,不过如果用户用的是CDN,可能已经包括进去了。不过用户可能没正确引入加载器,所以代码里需要确认使用GLTFLoader。
加载模型的时候,用loader.load,传入路径,成功回调里把模型加入场景。可能需要调整模型的位置或缩放,特别是如果模型太大或太小的话。还要考虑动画循环,如果有动画的话,但用户没说,可能先不加。不过如果有的话,可能需要调用update方法。
然后是窗口调整事件,确保相机和渲染器能自适应。最后动画循环用requestAnimationFrame,渲染场景。
可能的问题点:GLB文件的路径是否正确,服务器是否支持这种MIME类型。另外,可能需要添加一些错误处理的回调,比如加载出错时的处理。还有,相机的位置可能需要调整,不然模型可能在视野外。比如设置相机的位置z为5,或者根据模型大小调整。
用户可能没有提到控制部分,比如轨道控制器,这样用户无法旋转查看模型。所以最好加上OrbitControls,这需要从examples/jsm/controls导入,或者CDN链接。但用户可能没安装,所以代码里要提醒他们引入。
总结步骤:初始化Three.js基础,设置光照,加载模型,处理窗口变化,添加动画循环,可能的话添加控制器。代码要注释清楚,方便用户理解每个部分的作用,并提醒他们注意路径和额外需要的模块。
当然可以!以下是一个使用Three.js在HTML中加载和显示GLB模型文件的main.js
示例代码。我会包含详细的注释说明每个步骤的作用:
// main.js
// 初始化Three.js核心组件
let scene, camera, renderer, controls;
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xeeeeee); // 设置背景颜色
// 创建透视相机
camera = new THREE.PerspectiveCamera(
75, // 视野角度
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近平面
1000 // 远平面
);
camera.position.set(5, 5, 5); // 设置相机位置
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加灯光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 10);
scene.add(directionalLight);
// 添加轨道控制器(需要额外引入OrbitControls.js)
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果
controls.dampingFactor = 0.05;
// 加载GLB模型
const loader = new THREE.GLTFLoader();
loader.load(
'model.glb', // 模型文件路径
(gltf) => {
scene.add(gltf.scene);
console.log('模型加载完成');
// 可选:自动调整相机视角到模型
const box = new THREE.Box3().setFromObject(gltf.scene);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
camera.position.copy(center).add(new THREE.Vector3(0, 0, size.length() * 1.5));
controls.update();
},
(xhr) => {
// 加载进度回调
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
(error) => {
console.error('加载错误:', error);
}
);
// 处理窗口大小变化
window.addEventListener('resize', onWindowResize, false);
}
// 窗口大小变化处理函数
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
controls.update(); // 更新控制器
renderer.render(scene, camera);
}
// 初始化并启动
init();
animate();
需要使用以下HTML结构(需放在同一目录下):
<!DOCTYPE html>
<html>
<head>
<title>Three.js GLB Viewer</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<!-- 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>
<!-- 轨道控制器 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<!-- 主程序 -->
<script src="main.js"></script>
</body>
</html>
使用说明:
- 将模型文件命名为
model.glb
并放在与HTML文件同目录下 - 按需调整以下参数:
- 相机初始位置(
camera.position.set()
) - 背景颜色(
scene.background
) - 灯光参数(颜色和强度)
- 相机初始位置(
- 可选功能:
- 添加加载进度条
- 添加模型点击交互
- 添加自动旋转功能
注意事项:
- 需要从网络服务器运行(本地直接打开可能会有CORS问题)
- 模型尺寸过大时需要调整相机位置
- 不同版本的Three.js可能需要调整加载器路径
如果需要更复杂的功能(如模型交互、动画控制等),可以在加载完成的回调函数中添加相关逻辑。
You:
-
添加加载进度条
-
添加自动旋转功能
这两个功能要如何添加呢?
DeepSeek:
已深度思考(用时 26 秒)
好的,用户之前已经得到了一个使用Three.js加载GLB模型的示例代码,现在他们想进一步添加加载进度条和自动旋转功能。我需要仔细分析这两个需求,并给出具体的实现步骤。
首先,关于加载进度条。原来的代码中已经有一个加载进度的回调函数,但只是将进度打印到控制台。用户想要一个可视化的进度条,这意味着需要在页面上添加HTML元素来显示进度。通常,进度条可以用一个div容器,里面包含一个表示进度的内层div,通过调整内层div的宽度来显示进度百分比。在加载过程中,GLTFLoader的load方法会持续调用进度回调,这里需要更新进度条的宽度。加载完成后,可能需要隐藏进度条或者将其设置为完成状态。
接下来是自动旋转功能。自动旋转通常指的是让模型或相机围绕某个中心点旋转。在Three.js中,可以通过在动画循环中调整模型或相机的旋转属性来实现。另一种方法是使用OrbitControls的autoRotate属性,将其设置为true即可启用控制器的自动旋转,这样用户就不需要手动编写旋转逻辑,而且可以与用户交互(如拖动)结合使用。用户可能希望控制旋转速度,所以需要添加一个参数来调整autoRotateSpeed。
然后,我需要考虑如何将这两个功能集成到现有的代码中。对于进度条,需要在HTML中添加相应的结构,并在CSS中定义样式,然后在JavaScript中更新进度条的宽度。加载完成后,可能需要隐藏进度条。对于自动旋转,只需在初始化OrbitControls时设置autoRotate和autoRotateSpeed属性,并在动画循环中确保controls.update()被调用,因为OrbitControls需要每帧更新。
此外,用户可能没有提到但需要注意的细节:比如进度条的样式是否需要过渡效果,自动旋转的速度是否可调,以及如何确保在窗口大小变化时进度条仍然正确显示。另外,如果模型加载失败,是否需要处理错误情况并更新进度条的状态。
现在需要检查示例代码中是否已经包含OrbitControls,因为自动旋转功能依赖于它。原来的代码确实有引入OrbitControls,并且启用了阻尼效果,因此在设置autoRotate后,应该可以正常工作。同时,需要确认Three.js的版本是否支持这些属性,但一般来说,较新版本的Three.js都支持。
最后,要将这些修改步骤清晰地解释给用户,并提供完整的代码示例,确保用户能够复制并运行。同时,提醒用户注意路径和版本问题,确保所有依赖正确加载。
以下是添加加载进度条和自动旋转功能的完整实现方案,我在之前的代码基础上进行了功能扩展,新增的代码用注释标出:
<!-- 修改后的HTML(新增进度条容器) -->
<!DOCTYPE html>
<html>
<head>
<title>Three.js GLB Viewer</title>
<style>
body { margin: 0; }
canvas { display: block; }
/* 新增进度条样式 */
#loading-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 20px;
background: #555;
border-radius: 10px;
}
#progress-bar {
width: 0%;
height: 100%;
background: #00ff88;
border-radius: 10px;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<!-- 新增加载进度条 -->
<div id="loading-container">
<div id="progress-bar"></div>
</div>
<!-- Three.js 依赖保持不变 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script src="main.js"></script>
</body>
</html>
// 修改后的main.js
let scene, camera, renderer, controls, model;
// 新增变量
let isLoading = true;
const autoRotateSpeed = 1; // 自动旋转速度 (1=正常速度)
function init() {
// ...保持之前的初始化代码不变...
// 修改后的模型加载部分
const loader = new THREE.GLTFLoader();
loader.load(
'model.glb',
(gltf) => {
model = gltf.scene; // 保存模型引用
scene.add(model);
// 隐藏进度条
isLoading = false;
document.getElementById('loading-container').style.display = 'none';
// 自动调整视角
const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
camera.position.copy(center).add(new THREE.Vector3(0, 0, size.length() * 1.5));
controls.target.copy(center); // 设置控制器的焦点
controls.update();
},
(xhr) => {
// 更新进度条
const progress = (xhr.loaded / xhr.total) * 100;
document.getElementById('progress-bar').style.width = `${progress}%`;
},
(error) => {
console.error('加载错误:', error);
document.getElementById('progress-bar').style.background = '#ff0000';
}
);
// 配置自动旋转
controls.autoRotate = true; // 启用自动旋转
controls.autoRotateSpeed = autoRotateSpeed; // 设置旋转速度
// ...保持其他代码不变...
}
function animate() {
requestAnimationFrame(animate);
// 只在加载完成后执行自动旋转
if (!isLoading) {
controls.update();
}
renderer.render(scene, camera);
}
// ...其他保持不变的代码...
功能说明:
-
加载进度条:
- 添加了包含动态进度条的HTML/CSS结构
- 在加载过程中实时更新进度条宽度
- 加载完成后隐藏进度条
- 加载错误时进度条变红提示
-
自动旋转功能:
- 启用了
OrbitControls
的自动旋转(autoRotate
) - 可通过
autoRotateSpeed
调整旋转速度(值越大旋转越快) - 保持原有控制器功能(鼠标交互仍然有效)
- 启用了
高级定制建议:
- 要修改自动旋转速度,调整
autoRotateSpeed
变量的值 - 要改变旋转方向,可以设置负值:
autoRotateSpeed = -1
- 进度条样式可以通过修改CSS自定义:
- 修改
#progress-bar
的背景色改变进度条颜色 - 调整
border-radius
值改变圆角大小 - 修改
transition
参数调整动画速度
- 修改
效果预览:
- 加载时会显示绿色进度条从左到右填充
- 加载完成后模型会自动缓慢旋转
- 仍然可以用鼠标:
- 左键拖动旋转视角
- 右键拖动平移视角
- 滚轮缩放
如果需要强制面向某个方向旋转,可以在animate()
函数中添加手动旋转逻辑(示例):
第2段对话记录
You:
现在我在一个空白的index.html中实现了使用three.js加载glb 3d模型文件(整个页面都是这个模型的展示),
index.html
<!DOCTYPE html>
<html>
<head>
<title>Three.js GLB Viewer</title>
<style>
body { margin: 0; }
canvas { display: block; }
/* 新增进度条样式 */
#loading-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 20px;
background: #555;
border-radius: 10px;
}
#progress-bar {
width: 0%;
height: 100%;
background: #00ff88;
border-radius: 10px;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<!-- 新增加载进度条 -->
<div id="loading-container">
<div id="progress-bar"></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>
<!-- 轨道控制器 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<!-- 主程序 -->
<script src="main.js"></script>
</body>
</html>
main.js如下
main.js
// main.js
// 初始化Three.js核心组件
let scene, camera, renderer, controls;
// 新增变量
let isLoading = true;
const autoRotateSpeed = 1; // 自动旋转速度 (1=正常速度)
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xeeeeee); // 设置背景颜色
// 创建透视相机
camera = new THREE.PerspectiveCamera(
45, // 视野角度
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近平面
1000 // 远平面
);
camera.position.set(5, 5, 5); // 设置相机位置
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加灯光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 10);
scene.add(directionalLight);
// 添加轨道控制器(需要额外引入OrbitControls.js)
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果
controls.dampingFactor = 0.05;
// 修改后的模型加载部分
const loader = new THREE.GLTFLoader();
loader.load(
'model.glb',
(gltf) => {
model = gltf.scene; // 保存模型引用
scene.add(model);
// 隐藏进度条
isLoading = false;
document.getElementById('loading-container').style.display = 'none';
// 自动调整视角
const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
camera.position.copy(center).add(new THREE.Vector3(size.length(), size.length(), size.length() * 1.5));
controls.target.copy(center); // 设置控制器的焦点
controls.update();
},
(xhr) => {
// 更新进度条
const progress = (xhr.loaded / xhr.total) * 100;
document.getElementById('progress-bar').style.width = `${progress}%`;
},
(error) => {
console.error('加载错误:', error);
document.getElementById('progress-bar').style.background = '#ff0000';
}
);
// 配置自动旋转
controls.autoRotate = true; // 启用自动旋转
controls.autoRotateSpeed = autoRotateSpeed; // 设置旋转速度
// 处理窗口大小变化
window.addEventListener('resize', onWindowResize, false);
}
// 窗口大小变化处理函数
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
// 只在加载完成后执行自动旋转
if (!isLoading) {
controls.update(); // 更新控制器
}
renderer.render(scene, camera);
}
// 初始化并启动
init();
animate();
我现在想要在我的另一个网页中,把这个3d模型作为一个部分放进去(类似于插入一张图片),
home.html
<!-- Custom HTML site displayed as the Home chapter -->
{% extends "main.html" %}
{% block tabs %}
{{ super() }}
<style>
.md-main {
flex-grow: 0
}
.md-main__inner {
display: flex;
height: 100%;
}
.tx-container {
padding-top: .0rem;
background: linear-gradient(to bottom, var(--md-primary-fg-color), var(--md-link-color) 99%)
}
.tx-hero {
margin: 32px 2.8rem;
color: var(--md-primary-bg-color);
justify-content: center;
}
.tx-hero h1 {
margin-bottom: 1rem;
color: currentColor;
font-weight: 700
}
.tx-hero__content {
padding-bottom: 1rem;
margin: 0 auto;
}
.tx-hero__image{
width:17rem;
height:17rem;
order:1;
padding-right: 2.5rem;
}
.feature-item h3 svg {
fill: var(--md-default-fg-color);
height: 30px;
float: left;
margin-right: 10px;
transform: translateY(10%);
}
.top-hr {
margin-top: 42px;
}
.feature-item {
font-family: var(--md-text-font);
font-weight: 300;
box-sizing: border-box;
padding: 0 15px;
word-break: break-word
}
@media screen and (max-width:30em) {
.tx-hero h1 {
font-size: 1.4rem
}
}
@media screen and (min-width:60em) {
.md-sidebar--secondary {
display: none
}
.tx-hero {
display: flex;
align-items: center;
justify-content: center;
}
.tx-hero__content {
max-width: 22rem;
margin-top: 3.5rem;
margin-bottom: 3.5rem;
margin-left: 1.0rem;
margin-right: 4.0rem;
align-items: center;
}
}
@media screen and (min-width:76.25em) {
.md-sidebar--primary {
display: none
}
.top-hr {
width: 100%;
display: flex;
max-width: 61rem;
margin-right: auto;
margin-left: auto;
padding: 0 .2rem;
}
.bottom-hr {
margin-top: 10px;
width: 100%;
display: flex;
max-width: 61rem;
margin-right: auto;
margin-left: auto;
padding: 0 .2rem;
}
.feature-item {
flex: 1;
min-width: 0;
}
}
.hr {
border-bottom: 1px solid #eee;
width: 100%;
margin: 20px 0;
}
.text-center {
text-align: center;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
margin-top: 15px;
font-family: 'Lato', sans-serif;
font-size: 23px;
font-weight: 300;
padding-bottom: 10px;
}
.logos {
display: flex;
align-items: center;
justify-content: center;
flex-flow: row wrap;
margin: 0 auto;
}
.logos img {
flex: 1 1 auto;
padding: 25px;
max-height: 130px;
vertical-align: middle;
}
.hr-logos {
margin-top: 0;
margin-bottom: 30px;
}
.md-footer-meta__inner {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 1.0rem;
}
.md-footer-social {
padding-top: 20px;
}
.md-typeset img, .md-typeset video {
box-shadow: initial;
border: initial;
max-width: 110%;
}
</style>
<!-- Main site Entry button descriptions -->
<section class="tx-container">
<div class="md-grid md-typeset">
<div class="tx-hero">
<div class="tx-hero__image">
<img src="assets/azure-kinect-sensor.webp" draggable="false">
</div>
<div class="tx-hero__content">
<h1>
视觉技术文档
<br>
<p align="center">
<span class="twemoji">
{% include ".icons/material/camera-iris.svg" %}
</span>·<span class="twemoji">
{% include ".icons/material/eye.svg" %}
</span>·<span class="twemoji">
{% include ".icons/material/camera.svg" %}
</span>·<span class="twemoji">
{% include ".icons/material/video-image.svg" %}
</span>
</p>
</h1>
<a href="{{ page.next_page.url | url }}" class="md-button md-button--primary">
开始查看文档
</a>
<a href="blog/" class="md-button">
查看记录博客
</a>
</div>
</div>
</div>
</section>
<!-- Main site box descriptions -->
<div class="top-hr">
<div class="feature-item">
<div class="md-grid md-typeset">
<h3>基地网站</h3>
<p>
<a href="https://npu-home.github.io" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/home.svg" %}
</span>
基地官网:NPU-舞蹈机器人基地
</a>
<a href="https://github.com/NPU-Home" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/github.svg" %}
</span>
家政组软件仓库:NPU-Home
</a>
</p>
</div>
</div>
<div class="feature-item">
<div class="md-grid md-typeset">
<h3>文档仓库</h3>
<p>
<a href="https://github.com/NPU-Home/vision-docs" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/book.svg" %}
</span>
文档仓库:vision-docs
<span class="twemoji">
{% include ".icons/octicons/lock-24.svg" %}
</span>
</a>
<a href="https://github.com/NPU-Home/vision-docs-site" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/web.svg" %}
</span>
网站源代码:vision-docs-site
</a>
</p>
</div>
</div>
</div>
{% endblock %}
{% block content %}{% endblock %}
{% block footer %}{% endblock %}
我想要的效果是这样,<img src="assets/azure-kinect-sensor.webp" draggable="false">
这个图片被点击后,图片取消显示,开始加载3d的模型。
我现在应该怎么做才能实现这个效果呢?
DeepSeek:
已深度思考(用时 43 秒)
嗯,用户想要在他们现有的另一个网页中嵌入一个3D模型,当点击原本显示的图片时,图片隐藏并开始加载3D模型。这需要结合现有的Three.js代码和网页中的元素交互。我需要逐步分析如何实现这个效果。
首先,用户当前的网页结构是使用MkDocs的模板,其中有一个图片元素,当点击该图片时需要替换为3D模型。所以,第一步应该是在图片被点击时触发一个事件,隐藏图片并启动Three.js的场景加载。
接下来需要考虑如何将原有的独立index.html中的Three.js代码整合到当前的页面中。原来的index.html包含HTML结构和main.js脚本,但现在需要将这些内容嵌入到现有的页面结构中,特别是作为图片点击后的替换内容。
可能的步骤包括:
事件监听:给图片元素添加点击事件监听器,当点击时执行相应的函数。
动态创建容器:在图片的位置或附近创建一个容器元素,用于承载Three.js的canvas和加载进度条。
加载Three.js相关资源:确保页面已经加载了Three.js、GLTFLoader和OrbitControls等必要的库。
初始化3D场景:在点击事件中初始化Three.js的场景、相机、渲染器等,并开始加载模型。
隐藏图片:点击后隐藏原始图片,显示3D模型容器。
处理加载进度和完成后的操作:显示加载进度条,加载完成后隐藏进度条并启动自动旋转。
需要考虑的问题:
库的引入:现有的模板可能没有引入Three.js及其相关组件,需要确认在页面中正确引入这些脚本。
容器尺寸:3D模型的渲染区域需要合适的尺寸,可能需要设置为与图片相同的大小,或者根据父容器调整。
防止重复加载:用户可能多次点击图片,需要确保Three.js的初始化只执行一次,或者正确处理重复加载的情况。
页面布局:插入的canvas元素可能会影响现有布局,需要使用CSS确保其正确放置,比如绝对定位或覆盖在图片的位置。
性能问题:在已有页面中嵌入3D模型需要注意性能,避免影响其他部分的交互和加载。
可能的实现方式:
修改HTML结构:在图片的父容器中添加一个用于3D模型的div,设置其样式为隐藏,点击图片时显示该div并隐藏图片。
动态加载脚本:如果原页面没有加载Three.js相关脚本,可以使用动态脚本加载的方式,在点击时加载所需库,但需要注意异步加载的问题。
整合原有代码:将main.js中的初始化函数和动画循环整合到点击事件的处理函数中,确保正确的作用域和变量访问。
具体步骤:
在图片的HTML元素中添加onclick事件,例如onclick="load3DModel()"。
在图片附近添加一个容器div,设置其ID(如model-container),初始状态隐藏(display: none)。
在load3DModel函数中,隐藏图片,显示model-container,并执行Three.js的初始化。
确保Three.js所需的脚本已经在页面中存在,如果没有,需要在HTML头部添加相应的script标签。
调整Three.js的渲染器尺寸以适应容器的大小,可能需要根据容器的尺寸设置renderer的大小,并处理窗口resize事件。
确保模型加载后的自动旋转和控制器正确工作,同时处理可能的交互冲突,比如页面滚动或其它事件。
另外,需要考虑用户可能已经存在的样式冲突,比如z-index问题,确保canvas能够正确显示在页面的正确位置。进度条的位置可能需要调整,以适应新的容器大小。
测试过程中需要注意的问题:
点击图片后,是否成功隐藏图片并显示3D容器。
Three.js是否正确初始化,模型是否加载,是否有控制台报错。
进度条是否正常显示和隐藏。
窗口调整大小时,3D场景是否自适应。
多次点击是否导致重复初始化或内存泄漏。
最后,可能需要调整原有Three.js代码中的部分参数,比如相机的初始位置和自动旋转速度,以适应嵌入后的容器尺寸和布局。
要在现有页面中实现点击图片替换为3D模型的效果,可以按照以下步骤实现:
-
首先在现有页面中添加3D模型容器和必要的脚本引用:
<!-- 在.tx-hero__image区块中添加模型容器 --> <div class="tx-hero__image"> <img id="model-preview" src="assets/azure-kinect-sensor.webp" draggable="false" style="cursor: pointer"> <div id="model-container" style="display: none; width: 100%; height: 100%; position: relative;"></div> </div> <!-- 在现有脚本引用之后添加 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
-
添加新的样式到现有样式中:
#model-container canvas { width: 100% !important; height: 100% !important; } .model-loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; height: 3px; background: rgba(255,255,255,0.2); z-index: 100; } .model-progress { height: 100%; background: #00ff88; transition: width 0.3s ease; }
-
在页面底部添加JavaScript代码:
<script> // 初始化变量 let scene, camera, renderer, controls, model; let isModelLoaded = false; function init3DViewer(container) { // 初始化场景 scene = new THREE.Scene(); scene.background = new THREE.Color(0xeeeeee); // 相机设置 camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 0.1, 1000); camera.position.set(5, 5, 5); // 渲染器 renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.offsetWidth, container.offsetHeight); container.appendChild(renderer.domElement); // 灯光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 10); scene.add(directionalLight); // 控制器 controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.autoRotate = true; } function loadModel() { const container = document.getElementById('model-container'); const progressBar = document.createElement('div'); progressBar.className = 'model-loading'; progressBar.innerHTML = '<div class="model-progress"></div>'; container.appendChild(progressBar); const loader = new THREE.GLTFLoader(); loader.load( 'model.glb', (gltf) => { model = gltf.scene; scene.add(model); // 移除进度条 container.removeChild(progressBar); // 调整视角 const box = new THREE.Box3().setFromObject(model); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); camera.position.copy(center).add(new THREE.Vector3(size.length(), size.length(), size.length() * 1.5)); controls.target.copy(center); controls.update(); }, (xhr) => { const progress = (xhr.loaded / xhr.total) * 100; progressBar.querySelector('.model-progress').style.width = `${progress}%`; }, (error) => { console.error('加载失败:', error); progressBar.querySelector('.model-progress').style.background = '#ff0000'; } ); } // 点击图片事件处理 document.getElementById('model-preview').addEventListener('click', function() { if (!isModelLoaded) { const container = document.getElementById('model-container'); this.style.display = 'none'; container.style.display = 'block'; if (!scene) { init3DViewer(container); loadModel(); } isModelLoaded = true; // 启动动画循环 function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); } }); // 窗口大小调整处理 window.addEventListener('resize', () => { if (camera && renderer) { const container = document.getElementById('model-container'); camera.aspect = container.offsetWidth / container.offsetHeight; camera.updateProjectionMatrix(); renderer.setSize(container.offsetWidth, container.offsetHeight); } }); </script>
-
需要修改的部分说明:
- 将原来的独立页面功能封装成按需加载
- 添加点击事件处理来触发3D模型加载
- 动态创建进度条而不是使用固定HTML
- 自动处理容器尺寸变化
- 保持原有页面布局结构不变
这个实现方案有以下特点:
- 保持原有页面结构,仅在点击时初始化3D场景
- 自动适应容器尺寸
- 显示加载进度条
- 支持窗口大小调整
- 保持OrbitControls的交互功能
- 模型加载后自动旋转
注意需要确保:
- 模型文件路径正确
- Three.js相关CDN链接可访问
- 容器尺寸与图片显示区域匹配
- 模型复杂度适合在网页中展示
如果遇到性能问题,可以考虑:
- 降低模型多边形数量
- 添加加载完成前的旋转指示器
- 限制最大渲染分辨率
- 添加模型卸载功能(如果需要)
You:
现在3d模型成功显示在我的预览网页上了。我现在想要把这个3d模型显示窗口中,背景的部分改成透明的(现在背景是灰色的,会遮住网页原有的背景)
DeepSeek:
已深度思考(用时 17 秒)
好的,用户现在成功将3D模型嵌入到他们的网页中,但希望将模型的背景改为透明,这样就不会遮挡网页原有的背景。首先,我需要回顾之前的对话,确认他们是如何设置Three.js场景的。之前他们提到场景的背景颜色是灰色(0xeeeeee),所以第一步应该是修改这个颜色设置,使其透明。
然后,我需要考虑如何正确配置Three.js的渲染器以支持透明背景。Three.js的WebGLRenderer有一个alpha参数,设置为true可以让canvas的背景透明。同时,可能还需要调整渲染器的clearAlpha值,确保渲染器在清除画布时不会覆盖背景颜色。
接下来,用户可能希望场景中的物体本身不要有背景色,因此需要检查是否有其他元素(如灯光或材质)可能影响透明度。例如,环境光和方向光的颜色和强度是否合适,材质是否使用透明属性等。如果模型材质本身不支持透明,可能需要调整材质设置,但用户的问题主要集中在背景透明,所以可能不需要改动材质。
另外,考虑到用户可能在集成到现有网页时遇到层叠问题,需要确认canvas元素的CSS样式是否正确,特别是z-index和position属性,确保模型不会遮挡其他内容,或者被其他元素遮挡。
还有,用户在点击图片后加载模型,可能需要确保在初始化渲染器时已经启用了alpha通道。检查他们的init3DViewer函数中创建WebGLRenderer的参数是否正确设置了alpha:true。
最后,测试这些修改是否有效,确保背景透明后,模型的交互功能(如轨道控制)仍然正常工作,并且网页的其他部分如预期显示。如果有问题,可能需要进一步调整渲染器的设置或CSS样式。
要实现Three.js场景透明背景,需要修改以下四个关键位置:
-
修改渲染器初始化配置(在init3DViewer函数中):
-
移除场景背景色设置(删除或注释掉这行):
-
在CSS中添加模型容器的透明处理:
-
如果模型需要透明材质支持,在加载后添加:
完整修改后的init3DViewer函数应该像这样:
function init3DViewer(container) {
// 初始化场景
scene = new THREE.Scene();
// 移除原有的背景色设置
// 相机设置
camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 0.1, 1000);
camera.position.set(5, 5, 5);
// 渲染器配置(关键修改)
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true // 启用alpha通道
});
renderer.setClearColor(0x000000, 0); // 设置透明背景
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.appendChild(renderer.domElement);
// 保持其他灯光和控制器设置不变...
}
注意几个可能需要额外调整的地方:
- 如果模型有半透明材质,需要设置
material.transparent = true
-
调整灯光强度避免过曝:
-
在CSS中确保父容器没有背景:
修改完成后,模型背景会显示为透明,但可能会观察到以下现象需要进一步优化:
- 模型边缘可能有抗锯齿白边(可添加
renderer.antialias = true
) - 透明区域可能出现深度混合问题(尝试设置
renderer.sortObjects = true
) - 环境反射可能丢失(需要添加场景环境贴图)
如果希望保留场景照明但保持背景透明,可以考虑添加环境贴图:
到这里按照deepseek的回答,已经能够实现把3d模型嵌入到网页中显示,并且背景设置成透明了。但deepseek还把模型的材质给设置成透明了,预览效果之后我感觉还是不透明的好看,所以将这部分的javascript代码注释掉了
// 遍历所有材质设置透明属性
// model.traverse((child) => {
// if (child.material) {
// child.material.transparent = true;
// child.material.depthWrite = false; // 改善透明渲染
// }
// });
然后由于在展示3d模型的那个框中,显示的模型太小了,之前查看官方说明文档的时候,发现
// 相机设置
camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 0.1, 1000);
这里的45
是视野角度,将它调小,模型就会(在框中)显示得更大。但是如果设置的太小,模型的部分会在框外(显示不出来),最后调到了32
刚刚好不会让模型超出框外。
home.html
<!-- Custom HTML site displayed as the Home chapter -->
{% extends "main.html" %}
{% block tabs %}
{{ super() }}
<style>
.md-main {
flex-grow: 0
}
.md-main__inner {
display: flex;
height: 100%;
}
.tx-container {
padding-top: .0rem;
background: linear-gradient(to bottom, var(--md-primary-fg-color), var(--md-link-color) 99%)
}
.tx-hero {
margin: 32px 2.8rem;
color: var(--md-primary-bg-color);
justify-content: center;
}
.tx-hero h1 {
margin-bottom: 1rem;
color: currentColor;
font-weight: 700
}
.tx-hero__content {
padding-bottom: 1rem;
margin: 0 auto;
}
.tx-hero__image{
width:17rem;
height:17rem;
order:1;
padding-right: 2.5rem;
}
.feature-item h3 svg {
fill: var(--md-default-fg-color);
height: 30px;
float: left;
margin-right: 10px;
transform: translateY(10%);
}
.top-hr {
margin-top: 42px;
}
.feature-item {
font-family: var(--md-text-font);
font-weight: 300;
box-sizing: border-box;
padding: 0 15px;
word-break: break-word
}
@media screen and (max-width:30em) {
.tx-hero h1 {
font-size: 1.4rem
}
}
@media screen and (min-width:60em) {
.md-sidebar--secondary {
display: none
}
.tx-hero {
display: flex;
align-items: center;
justify-content: center;
}
.tx-hero__content {
max-width: 22rem;
margin-top: 3.5rem;
margin-bottom: 3.5rem;
margin-left: 1.0rem;
margin-right: 4.0rem;
align-items: center;
}
}
@media screen and (min-width:76.25em) {
.md-sidebar--primary {
display: none
}
.top-hr {
width: 100%;
display: flex;
max-width: 61rem;
margin-right: auto;
margin-left: auto;
padding: 0 .2rem;
}
.bottom-hr {
margin-top: 10px;
width: 100%;
display: flex;
max-width: 61rem;
margin-right: auto;
margin-left: auto;
padding: 0 .2rem;
}
.feature-item {
flex: 1;
min-width: 0;
}
}
.hr {
border-bottom: 1px solid #eee;
width: 100%;
margin: 20px 0;
}
.text-center {
text-align: center;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
margin-top: 15px;
font-family: 'Lato', sans-serif;
font-size: 23px;
font-weight: 300;
padding-bottom: 10px;
}
.logos {
display: flex;
align-items: center;
justify-content: center;
flex-flow: row wrap;
margin: 0 auto;
}
.logos img {
flex: 1 1 auto;
padding: 25px;
max-height: 130px;
vertical-align: middle;
}
.hr-logos {
margin-top: 0;
margin-bottom: 30px;
}
.md-footer-meta__inner {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 1.0rem;
}
.md-footer-social {
padding-top: 20px;
}
.md-typeset img, .md-typeset video {
box-shadow: initial;
border: initial;
max-width: 110%;
}
#model-container {
background: transparent !important;
}
#model-container canvas {
width: 100% !important;
height: 100% !important;
background: transparent !important;
mix-blend-mode: multiply; /* 可选,根据背景调整混合模式 */
}
.model-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 3px;
background: rgba(255,255,255,0.2);
z-index: 100;
}
.model-progress {
height: 100%;
background: #00ff88;
transition: width 0.3s ease;
}
</style>
<!-- Main site Entry button descriptions -->
<section class="tx-container">
<div class="md-grid md-typeset">
<div class="tx-hero">
<div class="tx-hero__image">
<img id="model-preview" src="assets/azure-kinect-sensor.webp" draggable="false" style="cursor: pointer">
<div id="model-container" style="display: none; width: 100%; height: 100%; position: relative;"></div>
</div>
<div class="tx-hero__content">
<h1>
视觉技术文档
<br>
<p align="center">
<span class="twemoji">
{% include ".icons/material/camera-iris.svg" %}
</span>·<span class="twemoji">
{% include ".icons/material/eye.svg" %}
</span>·<span class="twemoji">
{% include ".icons/material/camera.svg" %}
</span>·<span class="twemoji">
{% include ".icons/material/video-image.svg" %}
</span>
</p>
</h1>
<a href="{{ page.next_page.url | url }}" class="md-button md-button--primary">
开始查看文档
</a>
<a href="blog/" class="md-button">
查看记录博客
</a>
</div>
</div>
</div>
</section>
<!-- Main site box descriptions -->
<div class="top-hr">
<div class="feature-item">
<div class="md-grid md-typeset">
<h3>基地网站</h3>
<p>
<a href="https://npu-home.github.io" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/home.svg" %}
</span>
基地官网:NPU-舞蹈机器人基地
</a>
<a href="https://github.com/NPU-Home" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/github.svg" %}
</span>
家政组软件仓库:NPU-Home
</a>
</p>
</div>
</div>
<div class="feature-item">
<div class="md-grid md-typeset">
<h3>文档仓库</h3>
<p>
<a href="https://github.com/NPU-Home/vision-docs" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/book.svg" %}
</span>
文档仓库:vision-docs
<span class="twemoji">
{% include ".icons/octicons/lock-24.svg" %}
</span>
</a>
<a href="https://github.com/NPU-Home/vision-docs-site" class="md-button" id="width-left">
<span class="twemoji">
{% include ".icons/material/web.svg" %}
</span>
网站源代码:vision-docs-site
</a>
</p>
</div>
</div>
</div>
<!-- 在现有脚本引用之后添加 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.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 isModelLoaded = false;
function init3DViewer(container) {
// 初始化场景
scene = new THREE.Scene();
// scene.background = new THREE.Color(0xeeeeee);
// 相机设置
camera = new THREE.PerspectiveCamera(32, container.offsetWidth / container.offsetHeight, 0.1, 1000); // 视角大小
camera.position.set(5, 5, 5);
// 渲染器
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true // 启用alpha通道
});
renderer.setClearAlpha(0x000000, 0); // 设置完全透明
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.appendChild(renderer.domElement);
// 灯光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 10);
scene.add(directionalLight);
// 控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = true;
}
function loadModel() {
const container = document.getElementById('model-container');
const progressBar = document.createElement('div');
progressBar.className = 'model-loading';
progressBar.innerHTML = '<div class="model-progress"></div>';
container.appendChild(progressBar);
const loader = new THREE.GLTFLoader();
loader.load(
'assets/akdk_camera_cad.glb',
(gltf) => {
model = gltf.scene;
// 遍历所有材质设置透明属性
// model.traverse((child) => {
// if (child.material) {
// child.material.transparent = true;
// child.material.depthWrite = false; // 改善透明渲染
// }
// });
scene.add(model);
// 移除进度条
container.removeChild(progressBar);
// 调整视角
const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
camera.position.copy(center).add(new THREE.Vector3(size.length(), size.length(), size.length() * 1.5));
controls.target.copy(center);
controls.update();
},
(xhr) => {
const progress = (xhr.loaded / xhr.total) * 100;
progressBar.querySelector('.model-progress').style.width = `${progress}%`;
},
(error) => {
console.error('加载失败:', error);
progressBar.querySelector('.model-progress').style.background = '#ff0000';
}
);
}
// 点击图片事件处理
document.getElementById('model-preview').addEventListener('click', function() {
if (!isModelLoaded) {
const container = document.getElementById('model-container');
this.style.display = 'none';
container.style.display = 'block';
if (!scene) {
init3DViewer(container);
loadModel();
}
isModelLoaded = true;
// 启动动画循环
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
}
});
// 窗口大小调整处理
window.addEventListener('resize', () => {
if (camera && renderer) {
const container = document.getElementById('model-container');
camera.aspect = container.offsetWidth / container.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.offsetWidth, container.offsetHeight);
}
});
</script>
{% endblock %}
{% block content %}{% endblock %}
{% block footer %}{% endblock %}
创建日期: 2025-04-07