Вывод 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">×</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>
Последние обновления
Визуализация 26.05.2026
Страницы 04.04.2026
Форма 26.03.2026
Форма 24.02.2026
05.02.2026
Форма 25.01.2026
Форма 12.12.2025
Интеграции 24.11.2025
Разное 24.11.2025
Форма 15.11.2025
Визуализация 02.11.2025
Таблица 08.10.2025
Форма 26.09.2025
Таблица 23.09.2025
Разное 23.08.2025
Таблица 21.08.2025
Форма 20.08.2025
Таблица 18.08.2025
SQL-инструмент для создания личных кабинетов на сайте
Платформа Falcon Space
Это снижение стоимости владения
за счет меньшего количества людей для поддержки
Это быстрое внесение изменений
по ходу эксплуатации программы
Это современный интерфейс
полная адаптация под мобильные устройства