Демо

Вывод 3D объектов на странице сайта

Описание, как реализовать

В примере используется внешний файл GLB (или GLBF) - некая 3D модель, которое создается 3D дизайнером. 

ВАЖНО. Если модель лежит на вашем сервере, то надо прописать MIME типы для GLB и GLBF файлов. В appsettings.json: 

  "staticFiles":{
	".glb":"model/gltf-binary" ,
	".gltf": "model/gltf+json"	  
  },

Для вывода можно использовать следующий код. 

Раздел head: 

<style>
        /* Минимальные стили для позиционирования панели поверх canvas */
        body {
            margin: 0;
            overflow: hidden;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
        }
        #controls-panel {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 100;
            width: 380px;
            max-width: calc(100% - 40px);
        }
        .progress {
            height: 8px;
        }
        .status-text {
            font-size: 0.85rem;
            word-break: break-word;
        }
        .info-footer {
            position: absolute;
            bottom: 15px;
            left: 15px;
            z-index: 100;
            background: rgba(0,0,0,0.6);
            backdrop-filter: blur(4px);
            border-radius: 6px;
            padding: 4px 10px;
            font-size: 12px;
            color: #eee;
            pointer-events: none;
        }
        .btn-model {
            font-size: 0.75rem;
            padding: 0.25rem 0.5rem;
            margin-right: 4px;
            margin-bottom: 4px;
        }
        .card-body {
            max-height: calc(100vh - 120px);
            overflow-y: auto;
        }
    </style>

В теле страницы: 

 <div id="controls-panel">
        <div class="card as-card bg-dark text-white shadow-lg">
            <div class="card-header">
                <h5 class="mb-0">📦 Загрузчик GLB моделей</h5>
            </div>
            <div class="card-body">
                <div class="form-group">
                    <label for="modelPath">Путь к файлу (.glb)</label>
                    <input type="text" class="form-control form-control-sm" id="modelPath" 
                           placeholder="например: ./uploads/cave.glb или https://.../model.glb">
                    <small class="form-text text-muted">Поддерживаются относительные пути и URL</small>
                </div>
                
                <div class="mb-3">
                    <label class="small text-muted">📁 Быстрый выбор тестовых моделей:</label>
                    <div id="quickModels" class="d-flex flex-wrap mt-1"></div>
                </div>
                
                <button id="loadBtn" class="btn btn-primary btn-block">🔄 Загрузить модель</button>
                
                <hr class="bg-secondary">
                
                <div class="mt-2">
                    <div class="progress">
                        <div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" 
                             role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
                    </div>
                    <div id="statusMsg" class="status-text mt-2 text-muted">
                        ⚡ Готов к загрузке. Выберите модель или укажите путь.
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <div class="info-footer">
        🖱️ Мышь / 👆 Палец: вращение | ПКМ / двумя пальцами: панорамирование | Колёсико / щипок: зум
    </div>

и JS код:

  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    
    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/three@0.128.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.128.0/examples/jsm/"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        
        $(document).ready(function() {
            // --- Список тестовых моделей ---
            const modelsList = [
                { name: "Damaged Helmet", url: "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/refs/heads/main/2.0/DamagedHelmet/glTF-Binary/DamagedHelmet.glb" },
                { name: "Cesium Man", url: "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/CesiumMan/glTF-Binary/CesiumMan.glb" },
                { name: "Buggy", url: "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/refs/heads/main/2.0/Buggy/glTF-Binary/Buggy.glb" },
             //   { name: "Flight Helmet", url: "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/refs/heads/main/2.0/FlightHelmet/glTF-Binary/FlightHelmet.glb" },
              //  { name: "Simple Skin", url: "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/refs/heads/main/2.0/SimpleSkin/glTF-Binary/SimpleSkin.glb" },
                { name: "Box", url: "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/refs/heads/main/2.0/Box/glTF-Binary/Box.glb" }
            ];
            
            // --- Инициализация Three.js ---
            const scene = new THREE.Scene();
            scene.background = new THREE.Color(0x111122);
            scene.fog = new THREE.FogExp2(0x111122, 0.008);
            
            const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(2, 1.5, 3);
            
            const renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            document.body.appendChild(renderer.domElement);
            
            const controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.rotateSpeed = 1.0;
            controls.zoomSpeed = 1.2;
            controls.panSpeed = 0.8;
            controls.enableZoom = true;
            controls.enablePan = true;
            controls.target.set(0, 0.5, 0);
            
            // --- Освещение ---
            const ambientLight = new THREE.AmbientLight(0x404060);
            scene.add(ambientLight);
            const mainLight = new THREE.DirectionalLight(0xffffff, 1.2);
            mainLight.position.set(1, 2, 1);
            mainLight.castShadow = true;
            scene.add(mainLight);
            const fillLight = new THREE.PointLight(0x556688, 0.5);
            fillLight.position.set(-1, 0.5, -1.5);
            scene.add(fillLight);
            const backLight = new THREE.PointLight(0xffaa66, 0.4);
            backLight.position.set(0, 1.2, -1.8);
            scene.add(backLight);
            const rimLight = new THREE.PointLight(0x88aaff, 0.3);
            rimLight.position.set(0.5, -0.8, 0.8);
            scene.add(rimLight);
            
            // Сетка (остаётся всегда)
            const gridHelper = new THREE.GridHelper(5, 20, 0x88aaff, 0x335588);
            gridHelper.position.y = -0.8;
            scene.add(gridHelper);
            
            // ГРУППА ДЛЯ МОДЕЛЕЙ — сюда будем добавлять каждую новую модель,
            // а перед загрузкой новой — очищать группу.
            const modelGroup = new THREE.Group();
            scene.add(modelGroup);
            
            // DOM элементы
            const $modelPathInput = $('#modelPath');
            const $loadBtn = $('#loadBtn');
            const $progressBar = $('#progressBar');
            const $statusMsg = $('#statusMsg');
            
            // Функция полной очистки группы
            function clearModelGroup() {
                while(modelGroup.children.length > 0) {
                    const child = modelGroup.children[0];
                    if (child.isMesh) {
                        child.geometry.dispose();
                        if (child.material) {
                            if (Array.isArray(child.material)) child.material.forEach(m => m.dispose());
                            else child.material.dispose();
                        }
                    }
                    modelGroup.remove(child);
                }
            }
            
            // Загрузка модели
            function loadModelFromUrl(url) {
                if (!url || url.trim() === '') {
                    $statusMsg.html('❌ Ошибка: путь не может быть пустым').removeClass('text-muted').addClass('text-warning');
                    $progressBar.css('width', '0%').attr('aria-valuenow', 0);
                    return;
                }
                
                // Удаляем старую модель (очищаем группу)
                clearModelGroup();
                
                $progressBar.css('width', '0%').attr('aria-valuenow', 0).removeClass('bg-success');
                $statusMsg.html(`🔄 Начинаем загрузку: ${url}`).removeClass('text-danger text-warning').addClass('text-info');
                
                const loader = new GLTFLoader();
                loader.load(url,
                    (gltf) => {
                        const model = gltf.scene;
                        model.traverse((child) => {
                            if (child.isMesh) {
                                child.castShadow = true;
                                child.receiveShadow = true;
                            }
                        });
                        // Вычисляем bounding box для центровки камеры
                        const box = new THREE.Box3().setFromObject(model);
                        const center = box.getCenter(new THREE.Vector3());
                        controls.target.copy(center);
                        controls.update();
                        
                        // Добавляем модель в группу
                        modelGroup.add(model);
                        
                        $progressBar.css('width', '100%').attr('aria-valuenow', 100).addClass('bg-success');
                        $statusMsg.html(`✅ Модель успешно загружена: ${url}`).removeClass('text-info').addClass('text-success');
                        setTimeout(() => {
                            if ($statusMsg.hasClass('text-success')) {
                                $progressBar.css('width', '0%').attr('aria-valuenow', 0).removeClass('bg-success');
                            }
                        }, 2000);
                    },
                    (xhr) => {
                        if (xhr.lengthComputable) {
                            const percent = (xhr.loaded / xhr.total * 100).toFixed(1);
                            $progressBar.css('width', percent + '%').attr('aria-valuenow', percent);
                            $statusMsg.html(`📥 Загрузка: ${percent}% (${(xhr.loaded / 1048576).toFixed(2)} МБ из ${(xhr.total / 1048576).toFixed(2)} МБ)<br><small class="text-muted">${url}</small>`);
                        } else {
                            const loadedMB = (xhr.loaded / 1048576).toFixed(2);
                            $statusMsg.html(`📥 Загружено: ${loadedMB} МБ (размер неизвестен)<br><small class="text-muted">${url}</small>`);
                        }
                    },
                    (error) => {
                        console.error('Ошибка загрузки:', error);
                        $progressBar.css('width', '0%').attr('aria-valuenow', 0).removeClass('bg-success');
                        $statusMsg.html(`❌ Ошибка: не удалось загрузить модель.<br>Проверьте путь и CORS.<br>${error.message || 'Неизвестная ошибка'}`).removeClass('text-info').addClass('text-danger');
                        const $alert = $(`<div class="alert alert-danger alert-dismissible fade show position-fixed" style="bottom: 20px; right: 20px; z-index: 200; max-width: 300px;" role="alert">
                            <strong>Ошибка загрузки!</strong> ${error.message || 'Проверьте путь и доступность файла'}
                            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                                <span aria-hidden="true">&times;</span>
                            </button>
                        </div>`);
                        $('body').append($alert);
                        setTimeout(() => $alert.alert('close'), 8000);
                    }
                );
            }
            
            // --- Генерация кнопок быстрого выбора ---
            const $quickModelsDiv = $('#quickModels');
            modelsList.forEach(model => {
                const $btn = $(`<button class="btn btn-outline-info btn-model">${model.name}</button>`);
                $btn.on('click', () => {
                    $modelPathInput.val(model.url);
                    loadModelFromUrl(model.url);
                });
                $quickModelsDiv.append($btn);
            });
            
            // Загрузка по кнопке
            $loadBtn.on('click', () => {
                const path = $modelPathInput.val().trim();
                if (!path) {
                    $statusMsg.html('⚠️ Введите путь к .glb файлу').removeClass('text-info').addClass('text-warning');
                    return;
                }
                loadModelFromUrl(path);
            });
            
            // Автоматическая загрузка первой модели из списка при старте
            if (modelsList.length > 0) {
                const defaultModelUrl = modelsList[0].url;
                $modelPathInput.val(defaultModelUrl);
                loadModelFromUrl(defaultModelUrl);
            }
            
            // Анимация
            function animate() {
                requestAnimationFrame(animate);
                controls.update();
                renderer.render(scene, camera);
            }
            animate();
            
            window.addEventListener('resize', onWindowResize);
            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            }
        });
    </script>

 

Насколько полезна эта возможность?

Последние обновления

Платформа Falcon Space

Это снижение стоимости владения

за счет меньшего количества людей для поддержки

Это быстрое внесение изменений

по ходу эксплуатации программы

Это современный интерфейс

полная адаптация под мобильные устройства

Сайт использует Cookie. Правила конфиденциальности OK