案例
- 案例代码地址:
https://github.com/mrdoob/three.js/blob/master/examples/webgl_geometry_teapot.html#L133 - 案例效果地址:
https://threejs.org/examples/#webgl_geometry_teapot
解读
这段代码是一个基于 three.js 的示例,展示了如何创建和渲染一个经典的“犹他茶壶”模型(Utah Teapot),并使用 lil-gui 提供了一个调试面板,允许用户动态调整茶壶的属性(如细分级别、材质等)。以下是代码的详细解读:
1. 页面结构
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - teapot buffer geometry</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - the Utah Teapot<br />
from <a href="https://www.udacity.com/course/interactive-3d-graphics--cs291" target="_blank" rel="noopener">Udacity Interactive 3D Graphics</a>
</div>
<head>
部分:- 设置了页面标题、字符编码和视口配置。
- 引入了一个外部样式表
main.css
。
<body>
部分:- 包含一个
#info
元素,用于显示项目信息。
- 包含一个
2. three.js 和 lil-gui 的引入
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
</script>
- 使用
importmap
定义模块路径,方便导入 three.js 及其扩展模块。 - 导入的核心模块:
- THREE:three.js 核心库。
- GUI:lil-gui 库,用于创建调试面板。
- OrbitControls:实现相机的旋转、缩放和平移功能。
- TeapotGeometry:用于生成犹他茶壶模型。
3. 全局变量
let camera, scene, renderer;
let cameraControls;
let effectController;
const teapotSize = 300;
let ambientLight, light;
let tess = -1; // force initialization
let bBottom, bLid, bBody, bFitLid, bNonBlinn, shading;
let teapot, textureCube;
const materials = {};
camera
:定义相机对象。scene
:定义场景对象。renderer
:定义渲染器对象。teapotSize
:茶壶的大小。tess
:茶壶的细分级别(Tessellation Level)。materials
:存储不同材质的对象。
4. 初始化函数 (init
)
function init() {
const container = document.createElement('div');
document.body.appendChild(container);
const canvasWidth = window.innerWidth;
const canvasHeight = window.innerHeight;
// CAMERA
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 80000);
camera.position.set(-600, 550, 1300);
// LIGHTS
ambientLight = new THREE.AmbientLight(0x7c7c7c, 2.0);
light = new THREE.DirectionalLight(0xFFFFFF, 2.0);
light.position.set(0.32, 0.39, 0.7);
// RENDERER
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(canvasWidth, canvasHeight);
container.appendChild(renderer.domElement);
// EVENTS
window.addEventListener('resize', onWindowResize);
// CONTROLS
cameraControls = new OrbitControls(camera, renderer.domElement);
cameraControls.addEventListener('change', render);
// TEXTURE MAP
const textureMap = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
textureMap.wrapS = textureMap.wrapT = THREE.RepeatWrapping;
textureMap.anisotropy = 16;
textureMap.colorSpace = THREE.SRGBColorSpace;
// REFLECTION MAP
const path = 'textures/cube/pisa/';
const urls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
textureCube = new THREE.CubeTextureLoader().setPath(path).load(urls);
// MATERIALS
materials['wireframe'] = new THREE.MeshBasicMaterial({ wireframe: true });
materials['flat'] = new THREE.MeshPhongMaterial({ specular: 0x000000, flatShading: true, side: THREE.DoubleSide });
materials['smooth'] = new THREE.MeshLambertMaterial({ side: THREE.DoubleSide });
materials['glossy'] = new THREE.MeshPhongMaterial({ color: 0xc0c0c0, specular: 0x404040, shininess: 300, side: THREE.DoubleSide });
materials['textured'] = new THREE.MeshPhongMaterial({ map: textureMap, side: THREE.DoubleSide });
materials['reflective'] = new THREE.MeshPhongMaterial({ envMap: textureCube, side: THREE.DoubleSide });
// SCENE
scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
scene.add(ambientLight);
scene.add(light);
// GUI
setupGui();
}
- 创建容器:将渲染结果附加到页面上。
- 设置相机:定义透视相机,并设置初始位置。
- 添加光源:包括环境光和方向光。
- 初始化渲染器:设置抗锯齿和分辨率。
- 事件监听:监听页面尺寸变化以调整渲染器大小。
- 加载纹理:
textureMap
:平面纹理。textureCube
:立方体反射贴图。
- 定义材质:为茶壶提供多种材质选项(线框、平滑、反光等)。
- 设置场景:添加背景颜色、光源和 GUI。
5. GUI 设置 (setupGui
)
function setupGui() {
effectController = {
newTess: 15,
bottom: true,
lid: true,
body: true,
fitLid: false,
nonblinn: false,
newShading: 'glossy'
};
const gui = new GUI();
gui.add(effectController, 'newTess', [2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50]).name('Tessellation Level').onChange(render);
gui.add(effectController, 'lid').name('display lid').onChange(render);
gui.add(effectController, 'body').name('display body').onChange(render);
gui.add(effectController, 'bottom').name('display bottom').onChange(render);
gui.add(effectController, 'fitLid').name('snug lid').onChange(render);
gui.add(effectController, 'nonblinn').name('original scale').onChange(render);
gui.add(effectController, 'newShading', ['wireframe', 'flat', 'smooth', 'glossy', 'textured', 'reflective']).name('Shading').onChange(render);
}
- 创建一个
effectController
对象,存储用户可调整的参数。 - 使用
gui.add()
方法为每个参数添加控件:- 滑块:调整细分级别。
- 布尔值开关:控制茶壶各部分的显示(底部、盖子、主体等)。
- 下拉菜单:选择材质类型。
6. 渲染函数 (render
)
function render() {
if (effectController.newTess !== tess ||
effectController.bottom !== bBottom ||
effectController.lid !== bLid ||
effectController.body !== bBody ||
effectController.fitLid !== bFitLid ||
effectController.nonblinn !== bNonBlinn ||
effectController.newShading !== shading) {
tess = effectController.newTess;
bBottom = effectController.bottom;
bLid = effectController.lid;
bBody = effectController.body;
bFitLid = effectController.fitLid;
bNonBlinn = effectController.nonblinn;
shading = effectController.newShading;
createNewTeapot();
}
if (shading === 'reflective') {
scene.background = textureCube;
} else {
scene.background = null;
}
renderer.render(scene, camera);
}
- 检查用户是否修改了参数。如果修改,则调用
createNewTeapot()
更新茶壶模型。 - 根据材质类型调整场景背景(反射材质时使用立方体贴图)。
- 渲染场景。
7. 创建茶壶 (createNewTeapot
)
function createNewTeapot() {
if (teapot !== undefined) {
teapot.geometry.dispose();
scene.remove(teapot);
}
const geometry = new TeapotGeometry(
teapotSize,
tess,
effectController.bottom,
effectController.lid,
effectController.body,
effectController.fitLid,
!effectController.nonblinn
);
teapot = new THREE.Mesh(geometry, materials[shading]);
scene.add(teapot);
}
- 如果已有茶壶模型,则销毁旧几何体并从场景中移除。
- 使用
TeapotGeometry
创建新的茶壶模型,并根据用户选择的材质渲染。
8. 窗口调整事件 (onWindowResize
)
function onWindowResize() {
const canvasWidth = window.innerWidth;
const canvasHeight = window.innerHeight;
renderer.setSize(canvasWidth, canvasHeight);
camera.aspect = canvasWidth / canvasHeight;
camera.updateProjectionMatrix();
render();
}
- 监听窗口尺寸变化,更新渲染器大小和相机投影矩阵。
总结
这个示例展示了如何结合 three.js 和 lil-gui 创建一个交互式 3D 场景:
- 使用
TeapotGeometry
生成犹他茶壶模型。 - 使用
lil-gui
提供一个调试面板,允许用户调整茶壶的细分级别、材质和各部分的显示状态。 - 实现了动态更新和实时渲染的功能。
通过这种方式,用户可以直观地探索不同的参数组合对 3D 模型的影响。
前端工程师、程序员
标签:
threejs
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2018-03-04 vi 使用小结