Трёхмерная графика и Panda3D
Panda3D — открытый игровой движок с Python-API, который изначально разрабатывался Disney для онлайн-проектов вроде Toontown Online. Он даёт полноценный 3D-рендеринг (OpenGL/DirectX), загрузку моделей, освещение, камеру и игровой цикл — при этом основная логика пишется на Python.
Эта глава — мост между 2D-играми на Pygame и «настоящим» 3D: как устроена трёхмерная графика в экосистеме Python, из чего состоит Panda3D, где он уместен, и как собрать первые сцены из кода.
База Python — первая программа, точка входа main, venv и зависимости. Игровой цикл и события — Pygame. Графики функций и диаграммы — Matplotlib. Общая теория игр — раздел «Разработка игр».
Трёхмерная графика в Python
В Python нет встроенного 3D-рендерера: графика всегда идёт через сторонние библиотеки, которые оборачивают нативный код (C/C++) и OpenGL/Vulkan/DirectX.
| Подход | Примеры | Уровень | Типичное применение |
|---|---|---|---|
| Полноценный движок | Panda3D, Ursina | Высокий | 3D-игры, визуализации, симуляции |
| Тонкая обёртка над OpenGL | PyOpenGL, ModernGL | Низкий | Учебные шейдеры, свой рендер |
| 2D с «псевдо-3D» | Pygame, Arcade | Средний (2D) | Спрайты, тайловые карты |
| Скриптинг в DCC | Blender Python API | Зависит от задачи | Контент, анимация, пайплайн |
| Внешний движок + Python | Godot (GDExtension), Unity (редко) | Высокий | Продакшен-игры |
Трёхмерная сцена в любом движке опирается на одни и те же идеи:
- Вершины — точки в пространстве (координаты
x, y, z). - Примитивы — треугольники, из которых собираются меши.
- Текстуры и материалы — цвет и картинки на поверхности.
- Освещение — ambient, directional, point, spot.
- Камера — точка обзора и проекция (перспектива или ортографика).
- Цикл кадров — обновление логики и отрисовка ~60 раз в секунду.
Python здесь управляет сценой и логикой; тяжёлая математика и вызовы GPU выполняет нативное ядро движка.
Готовая галерея скриптов с карточками, кубом, пирамидой и составными сценами — Примеры фигур Panda3D на Python. Для 2D-фигур в браузере (JavaScript, без Python) — Примеры фигур на Processing/p5.js. Для 2D-игр на том же Python — Pygame — мини-игры и глава Разработка игр на Python.
Panda3D — что это
Panda3D — это не «один pip-модуль», а движок с несколькими Python-пакетами и C++-ядром (libpanda, libpandaexpress и др.). Он включает:
- рендер-пайплайн и шейдеры;
- загрузчик моделей (egg, bam, glTF через плагины, OBJ и др.);
- систему сценового графа (иерархия узлов);
- физику (Bullet, ODE — опционально);
- аудио (OpenAL);
- ввод, окно, таймеры, задачи (
taskMgr).
Движок кроссплатформенный (Windows, Linux, macOS) и распространяется под лицензией BSD. Для учебных 3D-проектов на чистом Python это один из самых зрелых вариантов.
Архитектура
Panda3D строится вокруг сценового графа — дерева узлов (NodePath). Каждый узел может нести геометрию, свет, камеру или пустой «контейнер» для группировки и трансформаций.
ShowBase (приложение)
├── render ← корень 3D-сцены
│ ├── card ← CardMaker / GeomNode
│ ├── cube
│ └── sun_np ← DirectionalLight
├── camera ← камера по умолчанию
├── loader ← текстуры, модели
└── taskMgr ← задачи каждый кадр
ShowBase
Класс ShowBase из direct.showbase.ShowBase — каркас приложения. При создании он:
- открывает окно и контекст рендеринга;
- создаёт
render,camera,loader,taskMgr,win,clock; - запускает главный цикл через
run().
Наследование от ShowBase — типичный паттерн: в __init__ настраиваете сцену, в run() уходит управление движку.
NodePath и трансформации
NodePath — «ручка» к узлу с удобными методами setPos, setHpr (heading-pitch-roll), setScale, lookAt. Дочерние узлы наследуют трансформации родителя — так вращают группы объектов или крепят модель к «руке» персонажа.
Задачи (game loop)
taskMgr.add(callback, "name") регистрирует функцию, вызываемую каждый кадр. Колбэк получает task и возвращает task.cont (продолжить) или task.done. Время между кадрами — globalClock.getDt() — удобно для анимации, не зависящей от FPS.
Рендер и освещение
Объекты вешают на self.render. Свет — отдельные узлы (AmbientLight, DirectionalLight, …), которые подключают через render.setLight(np). Без света объекты с нормалями могут выглядеть чёрными; карточки с текстурой часто переводят в setLightOff().
Состав пакета — основные модули
| Модуль / пакет | Назначение |
|---|---|
panda3d.core | Ядро: геометрия, математика (Vec3, Point3), текстуры, окно, рендер-состояния |
panda3d.bullet | Физика Bullet (опционально) |
panda3d.ode | Физика ODE (legacy) |
direct.showbase.ShowBase | Базовый класс приложения |
direct.task | Задачи и таймеры |
direct.actor.Actor | Скелетная анимация персонажей |
direct.gui | 2D-виджеты поверх 3D (DirectButton, OnscreenText) |
direct.filter | Постобработка (bloom, SSAO — через конфиг) |
Установка с PyPI ставит бинарные колёса с уже собранным C++-ядром — отдельно компилировать Panda3D для учебных примеров обычно не нужно.
Ограничения и сложности
| Тема | Суть |
|---|---|
| Производительность | Горячие пути — на C++; Python годится для логики, AI, UI. Тысячи draw call или тяжёлый CPU-код на Python — узкое место. |
| GIL | Параллельные потоки Python не ускоряют вычисления на CPU; многопоточность в Panda3D чаще для I/O или C++-стороны. |
| Кривая обучения | Нужны базовые 3D-понятия (оси, euler, UV, нормали). API объёмное; документация местами устарела. |
| Экосистема | Меньше ассет-магазинов и туториалов, чем у Unity/Godot. Контент часто готовят в Blender и экспортируют. |
| Редактор | Нет единого «Panda3D Editor» уровня Unity; сцены собирают кодом или через panda3d-gltf / egg-файлы. |
| Размер дистрибутива | Колесо ~100+ MB — для встраиваемых скриптов это много. |
Уместен для учебных 3D-проектов на Python, визуализаций, прототипов и инструментов, где важен код, а не визуальный редактор. Для коммерческого AAA или мобильного хита чаще смотрят на Unity, Unreal или Godot; для 2D — Pygame проще. Для Unity + C# с редактором — курс в редакторе и готовые скрипты в Lab.
Установка и первый запуск
Рекомендуется виртуальное окружение — см. Зависимости Python.
python -m venv panda_env
# Windows
panda_env\Scripts\activate
# Linux / macOS
source panda_env/bin/activate
pip install panda3d
Проверка:
import panda3d
print(panda3d.__version__)
Минимальное приложение — окно с цветным фоном и вращающейся карточкой:
#!/usr/bin/env python3
"""Minimal Panda3D application."""
from direct.showbase.ShowBase import ShowBase
from panda3d.core import AmbientLight, CardMaker, DirectionalLight
class App(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.setBackgroundColor(0.08, 0.1, 0.14, 1)
self.disableMouse()
self.camera.setPos(0, -8, 2)
self.camera.lookAt(0, 0, 0)
ambient = AmbientLight("ambient")
ambient.setColor((0.35, 0.35, 0.4, 1))
self.render.setLight(self.render.attachNewNode(ambient))
sun = DirectionalLight("sun")
sun.setColor((0.9, 0.9, 0.85, 1))
sun_np = self.render.attachNewNode(sun)
sun_np.setHpr(45, -45, 0)
self.render.setLight(sun_np)
cm = CardMaker("card")
cm.setFrame(-2, 2, -2, 2)
self.card = self.render.attachNewNode(cm.generate())
self.card.setColor(0.25, 0.55, 0.95, 1)
self.card.setP(-20)
self.taskMgr.add(self.spin, "spin")
def spin(self, task):
self.card.setH(self.card.getH() + 60 * globalClock.getDt())
return task.cont
if __name__ == "__main__":
app = App()
app.run()
Разбор фрагмента:
ShowBase.__init__поднимает окно и подсистемы.disableMouse()отключает стандартное управление камерой мышью — в учебном примере камера фиксирована.CardMakerсоздаёт плоский прямоугольник в плоскости XY;setP(-20)наклоняет карточку.spinкрутит объект по оси H (heading) с угловой скоростью 60°/с, умноженной наgetDt().
Текстура на карточке
Загрузка PNG через loader.loadTexture. Путь лучше давать относительно проекта — Panda3D ожидает Unix-пути внутри models/ или корня:
#!/usr/bin/env python3
"""Spinning image on a card — like main.py, but with a texture."""
import sys
from pathlib import Path
from direct.showbase.ShowBase import ShowBase
from panda3d.core import AmbientLight, CardMaker, DirectionalLight, Filename, TransparencyAttrib
def default_image_path():
return Path(__file__).resolve().parent / "images" / "sample.png"
def panda_texture_path(path):
resolved = path.resolve()
project_dir = Path(__file__).resolve().parent
try:
return resolved.relative_to(project_dir).as_posix()
except ValueError:
return Filename.fromOsSpecific(str(resolved)).asUnix()
class App(ShowBase):
def __init__(self, image_path):
ShowBase.__init__(self)
self.setBackgroundColor(0.08, 0.1, 0.14, 1)
self.disableMouse()
self.camera.setPos(0, -8, 2)
self.camera.lookAt(0, 0, 0)
ambient = AmbientLight("ambient")
ambient.setColor((0.35, 0.35, 0.4, 1))
self.render.setLight(self.render.attachNewNode(ambient))
sun = DirectionalLight("sun")
sun.setColor((0.9, 0.9, 0.85, 1))
sun_np = self.render.attachNewNode(sun)
sun_np.setHpr(45, -45, 0)
self.render.setLight(sun_np)
texture = self.loader.loadTexture(panda_texture_path(image_path))
if texture is None:
sys.exit(f"Cannot load image: {image_path}")
width = texture.getXSize()
height = texture.getYSize()
aspect = width / height if height else 1.0
half_h = 2.0
half_w = half_h * aspect
cm = CardMaker("card")
cm.setFrame(-half_w, half_w, -half_h, half_h)
self.card = self.render.attachNewNode(cm.generate())
self.card.setTexture(texture)
self.card.setTransparency(TransparencyAttrib.MAlpha)
self.card.setLightOff()
self.card.setP(-20)
self.taskMgr.add(self.spin, "spin")
def spin(self, task):
self.card.setH(self.card.getH() + 60 * globalClock.getDt())
return task.cont
if __name__ == "__main__":
image = Path(sys.argv[1]) if len(sys.argv) > 1 else default_image_path()
if not image.is_file():
sys.exit(f"Image not found: {image}")
app = App(image)
app.run()
Структура каталога:
my_panda_app/
├── main_texture.py
└── images/
└── sample.png
setTransparency(TransparencyAttrib.MAlpha) нужен для PNG с альфа-каналом. setLightOff() — текстура рисуется «как есть», без затенения по нормалям.
Куб из вершин и нормалей
Готового «куба одной строкой» в ядре нет — меш собирают через Geom API: вершины, нормали, цвет, индексы треугольников.
#!/usr/bin/env python3
"""Simple cube example — lit 3D cube with optional spin."""
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
AmbientLight,
DirectionalLight,
Geom,
GeomNode,
GeomTriangles,
GeomVertexData,
GeomVertexFormat,
GeomVertexWriter,
Vec3,
)
def make_cube(size=2.0):
half = size * 0.5
fmt = GeomVertexFormat.getV3n3c4()
vdata = GeomVertexData("cube", fmt, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, "vertex")
normal = GeomVertexWriter(vdata, "normal")
color = GeomVertexWriter(vdata, "color")
rgba = (0.35, 0.55, 0.95, 1)
def quad(n, corners):
for corner in corners:
vertex.addData3(*corner)
normal.addData3(n)
color.addData4(*rgba)
quad(Vec3(0, 0, 1), [(-half, -half, half), (half, -half, half), (half, half, half), (-half, half, half)])
quad(Vec3(0, 0, -1), [(half, -half, -half), (-half, -half, -half), (-half, half, -half), (half, half, -half)])
quad(Vec3(0, 1, 0), [(-half, half, half), (half, half, half), (half, half, -half), (-half, half, -half)])
quad(Vec3(0, -1, 0), [(-half, -half, -half), (half, -half, -half), (half, -half, half), (-half, -half, half)])
quad(Vec3(1, 0, 0), [(half, -half, half), (half, -half, -half), (half, half, -half), (half, half, half)])
quad(Vec3(-1, 0, 0), [(-half, -half, -half), (-half, -half, half), (-half, half, half), (-half, half, -half)])
tris = GeomTriangles(Geom.UHStatic)
for face in range(6):
base = face * 4
tris.addVertices(base, base + 1, base + 2)
tris.addVertices(base, base + 2, base + 3)
tris.closePrimitive()
geom = Geom(vdata)
geom.addPrimitive(tris)
node = GeomNode("cube")
node.addGeom(geom)
return node
class App(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.setBackgroundColor(0.08, 0.1, 0.14, 1)
self.disableMouse()
self.camera.setPos(0, -8, 3)
self.camera.lookAt(0, 0, 0)
ambient = AmbientLight("ambient")
ambient.setColor((0.35, 0.35, 0.4, 1))
self.render.setLight(self.render.attachNewNode(ambient))
sun = DirectionalLight("sun")
sun.setColor((0.9, 0.9, 0.85, 1))
sun_np = self.render.attachNewNode(sun)
sun_np.setHpr(45, -45, 0)
self.render.setLight(sun_np)
self.cube = self.render.attachNewNode(make_cube())
self.cube.setP(15)
self.taskMgr.add(self.spin, "spin")
def spin(self, task):
self.cube.setH(self.cube.getH() + 45 * globalClock.getDt())
return task.cont
if __name__ == "__main__":
app = App()
app.run()
Формат getV3n3c4() — vertex + normal + color на каждую вершину; directional light использует нормали для расчёта яркости граней. В продакшене кубы обычно загружают из .gltf / .bam, а не пишут руками.
Загрузка моделей и ввод
Типичный следующий шаг после примитивов:
model = self.loader.loadModel("models/box.bam")
model.reparentTo(self.render)
model.setScale(0.5)
model.setPos(0, 5, 0)
Обработка клавиш через accept:
self.accept("escape", sys.exit)
self.accept("w", self.move_forward)
Для персонажей с анимацией — direct.actor.Actor и файлы .egg/.bam с joint-анимацией.
Сравнение с Pygame и другими 3D-вариантами
| Pygame | Panda3D | Ursina | |
|---|---|---|---|
| Размерность | 2D | 3D | 3D (над Panda3D) |
| Порог входа | Низкий | Средний | Низкий |
| Контроль | Полный (SDL) | Полный движок | Меньше деталей |
| Документация | Хорошая | Объёмная, местами старая | Компактная |
Pygame остаётся лучшим стартом для игрового цикла и 2D; готовые скрипты с разбором — мини-игры в Lab. Переход на Panda3D логичен, когда нужны перспектива, освещение и модели в 3D.
Что попробовать дальше
- Добавить управление камерой (
enableMouse()или WASD). - Загрузить модель из Blender (glTF → через конвертер или
panda3d-gltf). - Подключить физику
panda3d.bulletдля падения объектов. - Наложить 2D-HUD через
direct.gui.OnscreenText. - Сравнить с мини-играми Pygame и Практикумом игр — те же идеи цикла и состояния, другой рендер.
- Чёрный экран — нет света или камера смотрит мимо объекта.
- Текстура не грузится — путь не относительный или файл вне рабочей директории.
- Анимация «дёргается» — забыли умножить скорость на
globalClock.getDt(). - Окно «зависло» — долгий код в задаче без возврата
task.cont.
Полезные ссылки
- Примеры фигур Panda3D на Python — галерея готовых скриптов от карточки до составных сцен
- Pygame — мини-игры на Python — 2D: змейка, Pong, Breakout с разбором кода
- Unity C# — скрипты для новичков — 3D в редакторе: WASD, прыжок, монетки, UI
- Tkinter — окна и виджеты — десктоп-формы и кнопки без 3D-движка
- Официальный мануал Panda3D
- Примеры в репозитории panda3d
- Blender — подготовка 3D-моделей для экспорта
В подборках
Статья входит в тематические маршруты из меню Подборки и блока "С чего начать?" на главной. Соседние шаги того же маршрута:
Разработка видеоигр — Разработка игр на Python, Pygame — мини-игры на Python, Minecraft — команды и datapack, Unity C# — скрипты для новичков, Практикум разработки игр — о разделе, Разработка игр — о разделе, Языки программирования игр, Разработка игр с использованием C++.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Python как язык общего назначения - философия, ключевые свойства и области применения в современной разработке. Python — это высокоуровневый язык программирования общего назначения, который отличается читаемым синтаксисом и широким спектром применения. Принципы, которые делают код понятным, поддерживаемым и расширяемым. Примеры реализации типовых приложений. Каждый пример сопровождается разбором ключевых концепций языка. Наверняка каждый новичок, решивший перейти в что-то стандартное в Python, открывает себе этот файл. Как устроен Python, что входит в комплект и какие есть реализации. Структурные каркасы для построения приложений, как они устроены. Фреймворки, библиотеки, инструменты сборки, среды выполнения, системы тестирования и специализированные платформы, объединённые общей философией ясности, простоты и гибкости. Что такое модули, как устроены механизмы импорта и загрузки. Управление изолированной средой и зависимостями проекта. История Python - ключевые этапы развития языка, сообщества и экосистемы инструментов. Философия Python не зафиксирована в официальных стандартах, но она глубоко интегрирована в язык, его стандартную библиотеку, документацию и культуру разработчиков.Python - язык общего назначения
Что требуется знать перед началом изучения языка программирования Python
Рекомендации по разработке на Python
Простые приложения на Python
Встроенный модуль builtins и типизация в Python
Архитектура интерпретатора Python
Фреймворки и библиотеки Python
Экосистема Python-приложений
Модули в Python
Виртуальные окружения и управление зависимостями
История языка Python
Философия Python - Zen of Python