HTML для 3D графики

Сложность: Продвинутый

3D графика в HTML

Создание интерактивных 3D сцен с использованием WebGL и библиотеки Three.js.

1. Интерактивная 3D сцена

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D графика - HTML5 & Three.js</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
            background-size: 400% 400%;
            animation: gradientBG 15s ease infinite;
            color: white;
            overflow: hidden;
        }
        
        @keyframes gradientBG {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
        
        .container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }
        
        #scene-container {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        
        .ui-panel {
            position: absolute;
            top: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.7);
            backdrop-filter: blur(10px);
            padding: 20px;
            border-radius: 15px;
            border: 1px solid rgba(255, 255, 255, 0.2);
            z-index: 100;
            max-width: 300px;
        }
        
        .ui-title {
            font-size: 1.5em;
            margin-bottom: 15px;
            background: linear-gradient(135deg, #667eea, #764ba2);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }
        
        .controls {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        
        .control-group {
            margin-bottom: 15px;
        }
        
        .control-label {
            display: block;
            margin-bottom: 5px;
            font-size: 0.9em;
            opacity: 0.9;
        }
        
        .slider-container {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        input[type="range"] {
            flex: 1;
            height: 5px;
            border-radius: 5px;
            background: rgba(255, 255, 255, 0.2);
            outline: none;
        }
        
        .value-display {
            min-width: 40px;
            text-align: center;
            font-size: 0.8em;
            background: rgba(255, 255, 255, 0.1);
            padding: 2px 8px;
            border-radius: 10px;
        }
        
        button {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 14px;
            transition: all 0.3s ease;
            margin: 2px;
        }
        
        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
        }
        
        .button-group {
            display: flex;
            gap: 5px;
            flex-wrap: wrap;
        }
        
        .info-panel {
            position: absolute;
            bottom: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.7);
            backdrop-filter: blur(10px);
            padding: 15px;
            border-radius: 10px;
            border: 1px solid rgba(255, 255, 255, 0.2);
            font-size: 0.8em;
            max-width: 300px;
        }
        
        .stats {
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            backdrop-filter: blur(10px);
            padding: 10px 15px;
            border-radius: 10px;
            border: 1px solid rgba(255, 255, 255, 0.2);
            font-size: 0.8em;
        }
        
        @media (max-width: 768px) {
            .ui-panel {
                max-width: 250px;
                padding: 15px;
            }
            
            .button-group {
                flex-direction: column;
            }
            
            button {
                padding: 8px 12px;
                font-size: 12px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div id="scene-container"></div>
        
        <div class="ui-panel">
            <div class="ui-title">? 3D Контроллер</div>
            
            <div class="controls">
                <div class="control-group">
                    <label class="control-label">Вращение X:</label>
                    <div class="slider-container">
                        <input type="range" id="rotationX" min="0" max="360" value="0">
                        <span class="value-display" id="rotationXValue">0°</span>
                    </div>
                </div>
                
                <div class="control-group">
                    <label class="control-label">Вращение Y:</label>
                    <div class="slider-container">
                        <input type="range" id="rotationY" min="0" max="360" value="0">
                        <span class="value-display" id="rotationYValue">0°</span>
                    </div>
                </div>
                
                <div class="control-group">
                    <label class="control-label">Масштаб:</label>
                    <div class="slider-container">
                        <input type="range" id="scale" min="50" max="200" value="100">
                        <span class="value-display" id="scaleValue">100%</span>
                    </div>
                </div>
                
                <div class="control-group">
                    <label class="control-label">Скорость анимации:</label>
                    <div class="slider-container">
                        <input type="range" id="animationSpeed" min="0" max="200" value="100">
                        <span class="value-display" id="speedValue">100%</span>
                    </div>
                </div>
                
                <div class="control-group">
                    <label class="control-label">Действия:</label>
                    <div class="button-group">
                        <button onclick="toggleAnimation()" id="animBtn">⏸️ Пауза</button>
                        <button onclick="resetScene()">? Сброс</button>
                        <button onclick="changeModel()">? Модель</button>
                        <button onclick="toggleWireframe()" id="wireBtn">? Каркас</button>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="stats" id="stats">
            FPS: 60 | Objects: 5
        </div>
        
        <div class="info-panel">
            <strong>Управление:</strong><br>
            • ЛКМ + движение: Вращение камеры<br>
            • ПКМ + движение: Перемещение камеры<br>
            • Колёсико мыши: Приближение/отдаление<br>
            • Используйте контроллер для точной настройки
        </div>
    </div>

    <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/controls/OrbitControls.js"></script>

    <script>
        // Основные переменные сцены
        let scene, camera, renderer, controls;
        let objects = [];
        let animationId;
        let isAnimating = true;
        let currentModel = 0;
        let isWireframe = false;
        
        // Инициализация сцены
        function init() {
            // Создание сцены
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x000000);
            scene.fog = new THREE.Fog(0x000000, 10, 50);
            
            // Создание камеры
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 5, 15);
            
            // Создание рендерера
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            document.getElementById("scene-container").appendChild(renderer.domElement);
            
            // Настройка контролов
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            
            // Создание освещения
            setupLighting();
            
            // Создание объектов
            createScene();
            
            // Обработка событий
            setupEventListeners();
            
            // Запуск анимации
            animate();
        }
        
        // Настройка освещения
        function setupLighting() {
            // Основной свет
            const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
            scene.add(ambientLight);
            
            // Направленный свет
            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
            directionalLight.position.set(10, 10, 5);
            directionalLight.castShadow = true;
            directionalLight.shadow.mapSize.width = 2048;
            directionalLight.shadow.mapSize.height = 2048;
            scene.add(directionalLight);
            
            // Точечный свет
            const pointLight = new THREE.PointLight(0x667eea, 0.5, 100);
            pointLight.position.set(-5, 5, 5);
            pointLight.castShadow = true;
            scene.add(pointLight);
            
            // Свет снизу
            const bottomLight = new THREE.PointLight(0x764ba2, 0.3, 50);
            bottomLight.position.set(0, -5, 0);
            scene.add(bottomLight);
        }
        
        // Создание объектов сцены
        function createScene() {
            // Очистка предыдущих объектов
            objects.forEach(obj => scene.remove(obj));
            objects = [];
            
            // Создание пола
            const floorGeometry = new THREE.PlaneGeometry(50, 50);
            const floorMaterial = new THREE.MeshStandardMaterial({
                color: 0x333333,
                roughness: 0.8,
                metalness: 0.2
            });
            const floor = new THREE.Mesh(floorGeometry, floorMaterial);
            floor.rotation.x = -Math.PI / 2;
            floor.receiveShadow = true;
            scene.add(floor);
            objects.push(floor);
            
            // Основной объект (сложная геометрия)
            let mainObject;
            
            switch (currentModel) {
                case 0:
                    // Сфера с текстурами
                    const sphereGeometry = new THREE.SphereGeometry(3, 32, 32);
                    const sphereMaterial = new THREE.MeshPhysicalMaterial({
                        color: 0x667eea,
                        roughness: 0.1,
                        metalness: 0.9,
                        clearcoat: 1.0,
                        clearcoatRoughness: 0.1
                    });
                    mainObject = new THREE.Mesh(sphereGeometry, sphereMaterial);
                    break;
                    
                case 1:
                    // Тор (бублик)
                    const torusGeometry = new THREE.TorusGeometry(3, 1, 16, 100);
                    const torusMaterial = new THREE.MeshPhysicalMaterial({
                        color: 0x764ba2,
                        roughness: 0.2,
                        metalness: 0.8,
                        transmission: 0.5,
                        thickness: 1
                    });
                    mainObject = new THREE.Mesh(torusGeometry, torusMaterial);
                    break;
                    
                case 2:
                    // Икосаэдр
                    const icosahedronGeometry = new THREE.IcosahedronGeometry(3, 1);
                    const icosahedronMaterial = new THREE.MeshPhysicalMaterial({
                        color: 0xf093fb,
                        roughness: 0.3,
                        metalness: 0.7,
                        sheen: 0.5
                    });
                    mainObject = new THREE.Mesh(icosahedronGeometry, icosahedronMaterial);
                    break;
                    
                case 3:
                    // Тороидальный многогранник
                    const torusKnotGeometry = new THREE.TorusKnotGeometry(2, 0.5, 100, 16);
                    const torusKnotMaterial = new THREE.MeshPhysicalMaterial({
                        color: 0x4ecdc4,
                        roughness: 0.1,
                        metalness: 0.9,
                        iridescence: 1,
                        iridescenceIOR: 1,
                        iridescenceThicknessRange: [100, 400]
                    });
                    mainObject = new THREE.Mesh(torusKnotGeometry, torusKnotMaterial);
                    break;
            }
            
            mainObject.castShadow = true;
            mainObject.receiveShadow = true;
            mainObject.position.y = 3;
            scene.add(mainObject);
            objects.push(mainObject);
            
            // Добавление частиц вокруг объекта
            createParticles(mainObject.position);
            
            // Добавление декоративных элементов
            createDecorativeElements();
        }
        
        // Создание частиц
        function createParticles(center) {
            const particleCount = 100;
            const particles = new THREE.BufferGeometry();
            const positions = new Float32Array(particleCount * 3);
            const colors = new Float32Array(particleCount * 3);
            
            for (let i = 0; i < particleCount * 3; i += 3) {
                // Случайная позиция в сфере
                const radius = 5 + Math.random() * 3;
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.random() * Math.PI;
                
                positions[i] = center.x + radius * Math.sin(phi) * Math.cos(theta);
                positions[i + 1] = center.y + radius * Math.sin(phi) * Math.sin(theta);
                positions[i + 2] = center.z + radius * Math.cos(phi);
                
                // Случайный цвет
                colors[i] = Math.random();
                colors[i + 1] = Math.random();
                colors[i + 2] = Math.random();
            }
            
            particles.setAttribute("position", new THREE.BufferAttribute(positions, 3));
            particles.setAttribute("color", new THREE.BufferAttribute(colors, 3));
            
            const particleMaterial = new THREE.PointsMaterial({
                size: 0.1,
                vertexColors: true,
                transparent: true,
                opacity: 0.8
            });
            
            const particleSystem = new THREE.Points(particles, particleMaterial);
            scene.add(particleSystem);
            objects.push(particleSystem);
        }
        
        // Создание декоративных элементов
        function createDecorativeElements() {
            // Создание нескольких маленьких объектов вокруг
            const geometries = [
                new THREE.BoxGeometry(1, 1, 1),
                new THREE.ConeGeometry(0.5, 2, 8),
                new THREE.CylinderGeometry(0.5, 0.5, 2, 12),
                new THREE.OctahedronGeometry(1)
            ];
            
            const materials = [
                new THREE.MeshStandardMaterial({ color: 0xff6b6b }),
                new THREE.MeshStandardMaterial({ color: 0x4ecdc4 }),
                new THREE.MeshStandardMaterial({ color: 0x45b7d1 }),
                new THREE.MeshStandardMaterial({ color: 0x96ceb4 })
            ];
            
            for (let i = 0; i < 8; i++) {
                const geometry = geometries[i % geometries.length];
                const material = materials[i % materials.length];
                const mesh = new THREE.Mesh(geometry, material);
                
                // Расположение по кругу
                const angle = (i / 8) * Math.PI * 2;
                const radius = 8;
                mesh.position.set(
                    Math.cos(angle) * radius,
                    1,
                    Math.sin(angle) * radius
                );
                
                mesh.castShadow = true;
                mesh.receiveShadow = true;
                scene.add(mesh);
                objects.push(mesh);
            }
        }
        
        // Настройка обработчиков событий
        function setupEventListeners() {
            // Обработка изменения размеров окна
            window.addEventListener("resize", onWindowResize);
            
            // Обработка слайдеров
            document.getElementById("rotationX").addEventListener("input", updateRotation);
            document.getElementById("rotationY").addEventListener("input", updateRotation);
            document.getElementById("scale").addEventListener("input", updateScale);
            document.getElementById("animationSpeed").addEventListener("input", updateAnimationSpeed);
        }
        
        // Обновление вращения
        function updateRotation() {
            const rotationX = document.getElementById("rotationX").value;
            const rotationY = document.getElementById("rotationY").value;
            
            document.getElementById("rotationXValue").textContent = rotationX + "°";
            document.getElementById("rotationYValue").textContent = rotationY + "°";
            
            // Применяем вращение к основному объекту
            if (objects[1]) { // objects[1] - основной объект
                objects[1].rotation.x = THREE.MathUtils.degToRad(rotationX);
                objects[1].rotation.y = THREE.MathUtils.degToRad(rotationY);
            }
        }
        
        // Обновление масштаба
        function updateScale() {
            const scale = document.getElementById("scale").value / 100;
            document.getElementById("scaleValue").textContent = (scale * 100) + "%";
            
            if (objects[1]) {
                objects[1].scale.set(scale, scale, scale);
            }
        }
        
        // Обновление скорости анимации
        function updateAnimationSpeed() {
            const speed = document.getElementById("animationSpeed").value;
            document.getElementById("speedValue").textContent = speed + "%";
        }
        
        // Переключение анимации
        function toggleAnimation() {
            isAnimating = !isAnimating;
            document.getElementById("animBtn").textContent = isAnimating ? "⏸️ Пауза" : "▶️ Воспр.";
        }
        
        // Переключение каркасного режима
        function toggleWireframe() {
            isWireframe = !isWireframe;
            document.getElementById("wireBtn").textContent = isWireframe ? "? Текстура" : "? Каркас";
            
            objects.forEach(obj => {
                if (obj.isMesh && obj.material) {
                    obj.material.wireframe = isWireframe;
                }
            });
        }
        
        // Смена модели
        function changeModel() {
            currentModel = (currentModel + 1) % 4;
            createScene();
        }
        
        // Сброс сцены
        function resetScene() {
            document.getElementById("rotationX").value = 0;
            document.getElementById("rotationY").value = 0;
            document.getElementById("scale").value = 100;
            document.getElementById("animationSpeed").value = 100;
            
            updateRotation();
            updateScale();
            updateAnimationSpeed();
            
            controls.reset();
            camera.position.set(0, 5, 15);
        }
        
        // Обработка изменения размеров окна
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }
        
        // Анимация
        function animate() {
            animationId = requestAnimationFrame(animate);
            
            if (isAnimating) {
                const speed = document.getElementById("animationSpeed").value / 100;
                const time = Date.now() * 0.001 * speed;
                
                // Анимация объектов
                objects.forEach((obj, index) => {
                    if (index > 0) { // Пропускаем пол
                        // Плавное вращение
                        obj.rotation.y += 0.01 * speed;
                        
                        // Парящая анимация для основного объекта
                        if (index === 1) {
                            obj.position.y = 3 + Math.sin(time) * 0.5;
                        }
                        
                        // Анимация для декоративных объектов
                        if (index > 1 && obj.isMesh) {
                            obj.rotation.x += 0.02 * speed;
                            obj.rotation.z += 0.015 * speed;
                        }
                    }
                });
                
                // Обновление статистики
                updateStats();
            }
            
            controls.update();
            renderer.render(scene, camera);
        }
        
        // Обновление статистики
        function updateStats() {
            const fps = Math.round(1000 / (performance.now() - (updateStats.lastTime || performance.now())));
            updateStats.lastTime = performance.now();
            
            document.getElementById("stats").textContent = 
                `FPS: ${fps} | Objects: ${objects.length}`;
        }
        
        // Инициализация при загрузке
        window.addEventListener("load", init);
        
        // Очистка при разгрузке
        window.addEventListener("beforeunload", () => {
            if (animationId) {
                cancelAnimationFrame(animationId);
            }
        });
    </script>
</body>
</html>