1. Three.js 自定义模型解析系统

我将创建一个交互式的Three.js自定义模型解析演示,展示如何加载、解析和操作自定义3D模型。这个系统将包含模型加载、几何数据可视化、实时编辑等功能。

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js 自定义模型解析系统</title>
    <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/OBJLoader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/MTLLoader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
            color: #e6e6e6;
            min-height: 100vh;
            overflow-x: hidden;
        }

        .container {
            display: flex;
            flex-direction: column;
            max-width: 1600px;
            margin: 0 auto;
            padding: 20px;
        }

        header {
            text-align: center;
            padding: 30px 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
            margin-bottom: 30px;
        }

        h1 {
            font-size: 2.8rem;
            margin-bottom: 10px;
            background: linear-gradient(90deg, #4cc9f0, #4361ee);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            text-shadow: 0 2px 10px rgba(76, 201, 240, 0.3);
        }

        .subtitle {
            font-size: 1.2rem;
            color: #a0a0c0;
            max-width: 800px;
            margin: 0 auto;
            line-height: 1.6;
        }

        .content {
            display: flex;
            flex-wrap: wrap;
            gap: 30px;
            margin-bottom: 40px;
        }

        .viewer-container {
            flex: 1;
            min-width: 300px;
            min-height: 500px;
            background: rgba(10, 15, 30, 0.7);
            border-radius: 15px;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            position: relative;
            border: 1px solid rgba(76, 201, 240, 0.2);
        }

        #model-viewer {
            width: 100%;
            height: 500px;
            display: block;
        }

        .controls-panel {
            flex: 1;
            min-width: 300px;
            background: rgba(15, 23, 42, 0.8);
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(67, 97, 238, 0.2);
        }

        .panel-title {
            font-size: 1.5rem;
            margin-bottom: 25px;
            color: #4cc9f0;
            padding-bottom: 10px;
            border-bottom: 1px solid rgba(76, 201, 240, 0.3);
        }

        .control-group {
            margin-bottom: 25px;
        }

        .control-label {
            display: block;
            margin-bottom: 10px;
            font-weight: 600;
            color: #b8c1ec;
        }

        .slider-container {
            display: flex;
            align-items: center;
            gap: 15px;
        }

        .slider-value {
            min-width: 40px;
            text-align: center;
            background: rgba(76, 201, 240, 0.1);
            padding: 5px 10px;
            border-radius: 5px;
            font-weight: 600;
        }

        input[type="range"] {
            flex: 1;
            height: 8px;
            -webkit-appearance: none;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 4px;
            outline: none;
        }

        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #4361ee;
            cursor: pointer;
            box-shadow: 0 0 10px rgba(67, 97, 238, 0.7);
        }

        .btn {
            display: inline-block;
            background: linear-gradient(90deg, #4361ee, #3a0ca3);
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 8px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-right: 10px;
            margin-bottom: 10px;
            box-shadow: 0 5px 15px rgba(67, 97, 238, 0.3);
        }

        .btn:hover {
            transform: translateY(-3px);
            box-shadow: 0 8px 20px rgba(67, 97, 238, 0.5);
        }

        .btn:active {
            transform: translateY(1px);
        }

        .btn-outline {
            background: transparent;
            border: 2px solid #4361ee;
            color: #4361ee;
        }

        .model-info {
            background: rgba(10, 15, 30, 0.8);
            border-radius: 15px;
            padding: 25px;
            margin-top: 20px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(76, 201, 240, 0.2);
        }

        .info-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 20px;
            margin-top: 20px;
        }

        .info-item {
            background: rgba(20, 30, 60, 0.5);
            padding: 15px;
            border-radius: 10px;
            border-left: 4px solid #4cc9f0;
        }

        .info-label {
            font-size: 0.9rem;
            color: #a0a0c0;
            margin-bottom: 5px;
        }

        .info-value {
            font-size: 1.2rem;
            font-weight: 600;
            color: #ffffff;
        }

        .toggle-group {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            margin-top: 15px;
        }

        .toggle-btn {
            background: rgba(67, 97, 238, 0.2);
            color: #b8c1ec;
            border: 1px solid rgba(67, 97, 238, 0.5);
            padding: 8px 16px;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.3s;
        }

        .toggle-btn.active {
            background: rgba(67, 97, 238, 0.8);
            color: white;
            border-color: #4361ee;
        }

        .toggle-btn:hover {
            background: rgba(67, 97, 238, 0.4);
        }

        .status-bar {
            background: rgba(10, 15, 30, 0.9);
            padding: 15px;
            border-radius: 10px;
            margin-top: 30px;
            border-left: 4px solid #f72585;
            display: flex;
            align-items: center;
        }

        .status-indicator {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #4cc9f0;
            margin-right: 15px;
            animation: pulse 2s infinite;
        }

        @keyframes pulse {
            0% { opacity: 0.7; }
            50% { opacity: 1; }
            100% { opacity: 0.7; }
        }

        footer {
            text-align: center;
            padding: 30px 0;
            margin-top: 30px;
            border-top: 1px solid rgba(255, 255, 255, 0.1);
            color: #a0a0c0;
            font-size: 0.9rem;
        }

        .highlight {
            color: #4cc9f0;
            font-weight: 600;
        }

        @media (max-width: 768px) {
            .content {
                flex-direction: column;
            }

            h1 {
                font-size: 2.2rem;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>Three.js 自定义模型解析系统</h1>
            <p class="subtitle">本演示展示了Three.js如何解析和操作自定义3D模型。您可以加载预定义模型,查看其几何数据,并实时调整模型属性。</p>
        </header>

        <div class="content">
            <div class="viewer-container">
                <canvas id="model-viewer"></canvas>
            </div>

            <div class="controls-panel">
                <h2 class="panel-title">模型控制</h2>

                <div class="control-group">
                    <label class="control-label">选择模型类型</label>
                    <div class="toggle-group">
                        <div class="toggle-btn active" data-model="sphere">球体</div>
                        <div class="toggle-btn" data-model="cube">立方体</div>
                        <div class="toggle-btn" data-model="torus">圆环</div>
                        <div class="toggle-btn" data-model="custom">自定义网格</div>
                    </div>
                </div>

                <div class="control-group">
                    <label class="control-label">旋转速度</label>
                    <div class="slider-container">
                        <input type="range" id="rotation-speed" min="0" max="5" step="0.1" value="1">
                        <span class="slider-value" id="rotation-value">1.0</span>
                    </div>
                </div>

                <div class="control-group">
                    <label class="control-label">模型尺寸</label>
                    <div class="slider-container">
                        <input type="range" id="model-scale" min="0.5" max="3" step="0.1" value="1">
                        <span class="slider-value" id="scale-value">1.0</span>
                    </div>
                </div>

                <div class="control-group">
                    <label class="control-label">网格线框</label>
                    <div class="slider-container">
                        <input type="range" id="wireframe-opacity" min="0" max="1" step="0.1" value="0.5">
                        <span class="slider-value" id="wireframe-value">0.5</span>
                    </div>
                </div>

                <div class="control-group">
                    <label class="control-label">顶点大小</label>
                    <div class="slider-container">
                        <input type="range" id="vertex-size" min="0" max="0.2" step="0.01" value="0.05">
                        <span class="slider-value" id="vertex-value">0.05</span>
                    </div>
                </div>

                <button id="reset-btn" class="btn">重置设置</button>
                <button id="export-btn" class="btn btn-outline">导出几何数据</button>
                <button id="add-light" class="btn">添加光源</button>
            </div>
        </div>

        <div class="model-info">
            <h2 class="panel-title">模型信息</h2>
            <div class="info-grid">
                <div class="info-item">
                    <div class="info-label">模型类型</div>
                    <div class="info-value" id="info-type">球体</div>
                </div>
                <div class="info-item">
                    <div class="info-label">顶点数量</div>
                    <div class="info-value" id="info-vertices">482</div>
                </div>
                <div class="info-item">
                    <div class="info-label">三角面数量</div>
                    <div class="info-value" id="info-faces">960</div>
                </div>
                <div class="info-item">
                    <div class="info-label">边界框尺寸</div>
                    <div class="info-value" id="info-bounds">2.0 x 2.0 x 2.0</div>
                </div>
            </div>
        </div>

        <div class="status-bar">
            <div class="status-indicator"></div>
            <div id="status-text">系统就绪。选择模型类型开始探索Three.js自定义模型解析功能。</div>
        </div>

        <footer>
            <p>Three.js 自定义模型解析演示 | 使用 <span class="highlight">Three.js r128</span> 构建 | 模型数据实时解析与可视化</p>
        </footer>
    </div>

    <script>
        // 全局变量
        let scene, camera, renderer, controls;
        let currentModel = null;
        let modelGroup = null;
        let wireframe = null;
        let vertices = null;
        let rotationSpeed = 1.0;
        let modelScale = 1.0;
        let wireframeOpacity = 0.5;
        let vertexSize = 0.05;

        // 初始化Three.js场景
        function init() {
            // 创建场景
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a0f1e);

            // 创建相机
            camera = new THREE.PerspectiveCamera(60, 1, 0.1, 1000);
            camera.position.set(5, 5, 5);

            // 创建渲染器
            const canvas = document.getElementById('model-viewer');
            renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
            renderer.setSize(canvas.clientWidth, canvas.clientHeight);
            renderer.setPixelRatio(window.devicePixelRatio);

            // 添加轨道控制
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;

            // 添加环境光
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
            scene.add(ambientLight);

            // 添加平行光
            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
            directionalLight.position.set(10, 20, 15);
            scene.add(directionalLight);

            // 创建模型组
            modelGroup = new THREE.Group();
            scene.add(modelGroup);

            // 初始化加载默认模型
            loadModel('sphere');

            // 窗口大小调整事件
            window.addEventListener('resize', onWindowResize);

            // 更新状态信息
            updateModelInfo();

            // 开始动画循环
            animate();
        }

        // 加载模型函数
        function loadModel(modelType) {
            // 清除之前的模型
            while(modelGroup.children.length > 0) {
                modelGroup.remove(modelGroup.children[0]);
            }

            let geometry, material;

            // 根据类型创建几何体
            switch(modelType) {
                case 'sphere':
                    geometry = new THREE.SphereGeometry(1, 32, 32);
                    material = new THREE.MeshPhongMaterial({ 
                        color: 0x4361ee,
                        shininess: 100,
                        transparent: true,
                        opacity: 0.9
                    });
                    break;

                case 'cube':
                    geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
                    material = new THREE.MeshPhongMaterial({ 
                        color: 0x3a0ca3,
                        shininess: 80,
                        transparent: true,
                        opacity: 0.9
                    });
                    break;

                case 'torus':
                    geometry = new THREE.TorusGeometry(1, 0.4, 16, 100);
                    material = new THREE.MeshPhongMaterial({ 
                        color: 0x7209b7,
                        shininess: 120,
                        transparent: true,
                        opacity: 0.9
                    });
                    break;

                case 'custom':
                    // 创建自定义几何体(复杂形状)
                    geometry = new THREE.IcosahedronGeometry(1, 2);
                    material = new THREE.MeshPhongMaterial({ 
                        color: 0xf72585,
                        shininess: 150,
                        transparent: true,
                        opacity: 0.9
                    });
                    break;
            }

            // 创建模型网格
            currentModel = new THREE.Mesh(geometry, material);
            modelGroup.add(currentModel);

            // 创建线框
            const wireframeGeometry = new THREE.WireframeGeometry(geometry);
            wireframe = new THREE.LineSegments(wireframeGeometry);
            wireframe.material.depthTest = false;
            wireframe.material.opacity = wireframeOpacity;
            wireframe.material.transparent = true;
            wireframe.material.color = new THREE.Color(0x4cc9f0);
            wireframe.visible = true;
            modelGroup.add(wireframe);

            // 创建顶点可视化
            const verticesGeometry = new THREE.BufferGeometry();
            verticesGeometry.setAttribute('position', geometry.attributes.position);

            const verticesMaterial = new THREE.PointsMaterial({
                color: 0xffea00,
                size: vertexSize,
                sizeAttenuation: true
            });

            vertices = new THREE.Points(verticesGeometry, verticesMaterial);
            modelGroup.add(vertices);

            // 更新模型信息
            updateModelInfo();

            // 更新状态
            updateStatus(`已加载${getModelName(modelType)}模型,包含${geometry.attributes.position.count}个顶点`);
        }

        // 获取模型名称
        function getModelName(type) {
            const names = {
                'sphere': '球体',
                'cube': '立方体',
                'torus': '圆环',
                'custom': '自定义二十面体'
            };
            return names[type] || type;
        }

        // 更新模型信息面板
        function updateModelInfo() {
            if (!currentModel) return;

            const geometry = currentModel.geometry;
            const verticesCount = geometry.attributes.position.count;
            const facesCount = geometry.index ? geometry.index.count / 3 : verticesCount / 3;

            // 计算边界框
            geometry.computeBoundingBox();
            const bounds = geometry.boundingBox;
            const boundsSize = bounds.getSize(new THREE.Vector3());

            // 更新显示信息
            document.getElementById('info-type').textContent = getModelName(getCurrentModelType());
            document.getElementById('info-vertices').textContent = verticesCount;
            document.getElementById('info-faces').textContent = facesCount;
            document.getElementById('info-bounds').textContent = 
                `${boundsSize.x.toFixed(2)} x ${boundsSize.y.toFixed(2)} x ${boundsSize.z.toFixed(2)}`;
        }

        // 获取当前模型类型
        function getCurrentModelType() {
            const activeBtn = document.querySelector('.toggle-btn.active');
            return activeBtn ? activeBtn.getAttribute('data-model') : 'sphere';
        }

        // 更新状态信息
        function updateStatus(message) {
            document.getElementById('status-text').textContent = message;
        }

        // 窗口大小调整处理
        function onWindowResize() {
            const canvas = document.getElementById('model-viewer');
            camera.aspect = canvas.clientWidth / canvas.clientHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(canvas.clientWidth, canvas.clientHeight);
        }

        // 动画循环
        function animate() {
            requestAnimationFrame(animate);

            // 旋转模型
            if (modelGroup && rotationSpeed > 0) {
                modelGroup.rotation.y += 0.01 * rotationSpeed;
                modelGroup.rotation.x += 0.005 * rotationSpeed;
            }

            // 更新控制
            controls.update();

            // 渲染场景
            renderer.render(scene, camera);
        }

        // 导出几何数据
        function exportGeometryData() {
            if (!currentModel) return;

            const geometry = currentModel.geometry;
            const vertices = geometry.attributes.position.array;
            const indices = geometry.index ? geometry.index.array : null;

            let data = `Three.js 模型几何数据导出\n`;
            data += `模型类型: ${getModelName(getCurrentModelType())}\n`;
            data += `顶点数量: ${vertices.length / 3}\n`;
            data += `索引数量: ${indices ? indices.length : '无索引'}\n\n`;
            data += `顶点数据 (x, y, z):\n`;

            // 添加前10个顶点作为示例
            for (let i = 0; i < Math.min(vertices.length, 30); i += 3) {
                data += `v${i/3}: ${vertices[i].toFixed(4)}, ${vertices[i+1].toFixed(4)}, ${vertices[i+2].toFixed(4)}\n`;
            }

            if (indices) {
                data += `\n索引数据 (三角面):\n`;
                // 添加前10个索引作为示例
                for (let i = 0; i < Math.min(indices.length, 30); i += 3) {
                    data += `f${i/3}: ${indices[i]}, ${indices[i+1]}, ${indices[i+2]}\n`;
                }
            }

            // 创建下载
            const blob = new Blob([data], {type: 'text/plain'});
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `threejs-model-${getCurrentModelType()}-data.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);

            updateStatus(`已导出${getModelName(getCurrentModelType())}模型的几何数据`);
        }

        // 添加额外光源
        function addLight() {
            const light = new THREE.PointLight(0xff9000, 1, 20);
            light.position.set(
                (Math.random() - 0.5) * 10,
                (Math.random() - 0.5) * 10 + 5,
                (Math.random() - 0.5) * 10
            );
            scene.add(light);

            // 添加光源可视化
            const lightHelper = new THREE.PointLightHelper(light, 0.5, 0xff9000);
            scene.add(lightHelper);

            updateStatus(`已添加点光源到场景中`);
        }

        // 重置所有设置
        function resetSettings() {
            rotationSpeed = 1.0;
            modelScale = 1.0;
            wireframeOpacity = 0.5;
            vertexSize = 0.05;

            // 更新UI元素
            document.getElementById('rotation-speed').value = rotationSpeed;
            document.getElementById('rotation-value').textContent = rotationSpeed.toFixed(1);

            document.getElementById('model-scale').value = modelScale;
            document.getElementById('scale-value').textContent = modelScale.toFixed(1);

            document.getElementById('wireframe-opacity').value = wireframeOpacity;
            document.getElementById('wireframe-value').textContent = wireframeOpacity.toFixed(1);

            document.getElementById('vertex-size').value = vertexSize;
            document.getElementById('vertex-value').textContent = vertexSize.toFixed(2);

            // 重新加载当前模型以应用默认设置
            loadModel(getCurrentModelType());

            updateStatus("已重置所有设置为默认值");
        }

        // 初始化UI事件监听
        function initUIEvents() {
            // 模型类型切换
            document.querySelectorAll('.toggle-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    document.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active'));
                    this.classList.add('active');

                    const modelType = this.getAttribute('data-model');
                    loadModel(modelType);
                });
            });

            // 旋转速度控制
            const rotationSlider = document.getElementById('rotation-speed');
            const rotationValue = document.getElementById('rotation-value');
            rotationSlider.addEventListener('input', function() {
                rotationSpeed = parseFloat(this.value);
                rotationValue.textContent = rotationSpeed.toFixed(1);
            });

            // 模型尺寸控制
            const scaleSlider = document.getElementById('model-scale');
            const scaleValue = document.getElementById('scale-value');
            scaleSlider.addEventListener('input', function() {
                modelScale = parseFloat(this.value);
                scaleValue.textContent = modelScale.toFixed(1);

                if (modelGroup) {
                    modelGroup.scale.setScalar(modelScale);
                }
            });

            // 线框透明度控制
            const wireframeSlider = document.getElementById('wireframe-opacity');
            const wireframeValue = document.getElementById('wireframe-value');
            wireframeSlider.addEventListener('input', function() {
                wireframeOpacity = parseFloat(this.value);
                wireframeValue.textContent = wireframeOpacity.toFixed(1);

                if (wireframe) {
                    wireframe.material.opacity = wireframeOpacity;
                    wireframe.material.needsUpdate = true;
                }
            });

            // 顶点大小控制
            const vertexSlider = document.getElementById('vertex-size');
            const vertexValue = document.getElementById('vertex-value');
            vertexSlider.addEventListener('input', function() {
                vertexSize = parseFloat(this.value);
                vertexValue.textContent = vertexSize.toFixed(2);

                if (vertices) {
                    vertices.material.size = vertexSize;
                    vertices.material.needsUpdate = true;
                }
            });

            // 按钮事件
            document.getElementById('reset-btn').addEventListener('click', resetSettings);
            document.getElementById('export-btn').addEventListener('click', exportGeometryData);
            document.getElementById('add-light').addEventListener('click', addLight);
        }

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            init();
            initUIEvents();
        });
    </script>
</body>
</html>

2. 功能说明

这个Three.js自定义模型解析系统包含以下功能:

2.1. 1. 模型加载与可视化

2.2. 2. 模型解析与信息展示

2.3. 3. 交互控制

2.4. 4. 额外功能

2.5. 5. 技术特性

这个演示展示了Three.js如何解析和处理3D模型数据,让用户可以直观地了解模型的几何结构,并实时调整各种参数来观察模型的变化。