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>