joken-前端工程师

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: :: :: 管理 ::
  404 随笔 :: 39 文章 :: 8 评论 :: 20万 阅读

案例

解读

这段代码是一个基于 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.jslil-gui 创建一个交互式 3D 场景:

  1. 使用 TeapotGeometry 生成犹他茶壶模型。
  2. 使用 lil-gui 提供一个调试面板,允许用户调整茶壶的细分级别、材质和各部分的显示状态。
  3. 实现了动态更新和实时渲染的功能。

通过这种方式,用户可以直观地探索不同的参数组合对 3D 模型的影响。

posted on   joken1310  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2018-03-04 vi 使用小结
点击右上角即可分享
微信分享提示