Данное руководство научит вас анимировать 3D-модели с помощью pyWeb3D. Перед изучением предложенной темы вы можете предварительно ознакомиться с тем, как загружать 3D-модели, используя pyWeb3D.
По итогам работы получим следующий анимационный клип:
Предварительные требования
Прежде всего, необходимо загрузить выбранную 3D-модель, оснащенную виртуальным “скелетом”, иначе ригом (англ. rig), и подготовленную к последующей анимации. Подобные модели можно скачать с сайта sketchfab.com. 3D-художники могут сами создать персонажа, используя предпочитаемое ПО, и экспортировать его в формате .GLTF
или .GLB
.
Ниже представлены ресурсы, задействованные в проекте:
Шаблонный код HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PyWeb3D webgl - Animationr</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/brython.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/brython_stdlib.js"></script>
<script src="https://unpkg.com/[email protected]/build/three.js"></script>
<script src="https://www.pyweb3d.org/pyweb3d/v1.0.0/pyweb3d.brython.js"></script>
<style>
body{
margin: 0;
}
</style>
</head>
<body onload="brython(1)">
<script src="jsm/loaders/GLTFLoader.js"></script>
<script src="jsm/controls/OrbitControls.js"></script>
<script type="text/python">
# Your Python Code will live here
</script>
</body>
</html>
Весь нижеследующий код размещается в строчном теге <script type=”text/python”>
в теле HTML-файла.
Импорт требуемых модулей и функций
from browser import document, window
from pyweb3d.pyweb3d import *
from javascript import UNDEFINED as undefined, Math
Настройка переменных
scene, renderer, camera = None, None, None
model, mixer, clock = None, None, None
mesh = None
GLTFLoader = window.THREE.GLTFLoader.new
OrbitControls = window.THREE.OrbitControls.new
Здесь мы объявляем глобальные переменные для последующего использования в коде.
Создание сцены и анимирование модели
def init():
global scene
global renderer
global camera
global model
global clock
global mesh
#Камера
camera = PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 )
camera.position.set( 0, 1.5, 5 )
camera.lookAt( 0, 1, 0 )
#Часы
clock = Clock()
#Сцена
scene = Scene()
scene.background = Color( "skyblue" )
scene.fog = Fog( 0xa0a0a0, 10, 50 )
#Источники света
hemiLight = HemisphereLight( 0xffffff, 0x444444 )
hemiLight.position.set( 0, 20, 0 )
scene.add( hemiLight )
dirLight = DirectionalLight( 0xffffff )
dirLight.position.set(5, 10, 10 )
dirLight.castShadow = True
dirLight.shadow.camera.top = 2
dirLight.shadow.camera.bottom = - 2
dirLight.shadow.camera.left = - 2
dirLight.shadow.camera.right = 2
dirLight.shadow.camera.near = 0.1
dirLight.shadow.camera.far = 40
scene.add( dirLight )
# Поверхность
textureLoader = TextureLoader()
texture = textureLoader.load('textures/texture1.jpg')
texture.wrapS = RepeatWrapping
texture.wrapT = RepeatWrapping
texture.repeat.set( 50, 50 )
material = MeshPhongMaterial( { 'map': texture, 'color': 0x999999, 'depthWrite': False } )
plain1 = PlaneGeometry( 100, 100 )
mesh = Mesh(plain1, material)
mesh.rotation.x = - Math.PI / 2
mesh.receiveShadow = True
scene.add( mesh )
# Загрузчик
loader = GLTFLoader()
def myGLTF(gltf):
global mixer
model = gltf.scene
model.scale.setScalar(0.5)
scene.add( model )
def checkMesh(object):
try:
object.castShadow = True
except:
...
model.traverse(checkMesh)
# Анимация
animations = gltf.animations
mixer = AnimationMixer( model )
action = mixer.clipAction( animations[ 0 ] )
action.play()
animate(0)
loader.load( 'models/passive_marker_man_walking.glb', myGLTF)
# Визуализатор
renderer = WebGLRenderer( { 'antialias': True } )
renderer.setPixelRatio( window.devicePixelRatio )
renderer.setSize( window.innerWidth, window.innerHeight )
renderer.outputEncoding = sRGBEncoding
renderer.toneMapping = ACESFilmicToneMapping
renderer.toneMappingExposure = 0.85
renderer.shadowMap.enabled = True
document.body.appendChild( renderer.domElement )
#Элементы управления
controls = OrbitControls(camera, renderer.domElement)
#Изменение размера окна
window.addEventListener( 'resize', onWindowResize )
Приступаем к построчному разбору представленного фрагмента кода.
- Строки
2–7
. Делаем изменяемыми переменные, обозначенные в строках1–3
раздела “Настройки переменных”. Для начинающих Python-программистов отметим, что ключевое словоglobal
необходимо для изменения глобальной копии переменных, объявленных в строках1–3
. - Строки
10 — 12
. Создаем камеру, устанавливаем в заданное положение и задаем ей место обзора. - Строка
15
. Создаем часы для последующего использования в коде на этапе анимирования сцены. - Строки
17 — 19
. Создаем сцену, придаем ей небесно-голубой фоновый цвет и добавляем эффект тумана. - Строки
22 — 35
. Добавляем к сцене 2 источника света: полусферический и направленный. - Строки
38 — 42
. Загружаем текстуру изображения для поверхности и регулируем настройки по умолчанию. - Строки
44 — 49
. СоздаемMesh()
. Она принимает в качестве аргумента геометрическую форму, в данном случае плоскость, и материал. Материал дляMesh
играет ту же роль, что и CSS для HTML. Затем уточняем настройки по умолчанию и добавляем к сцене. - Строка
52
. Инстанцируем загрузчик модели.GLB
. - Строки
54 — 75
. Создаем функцию обратного вызова для загрузки и анимирования модели.
Остановимся на этой функции более подробно.
- Строки
56 — 58
. Инстанцируем модель, немного уменьшаем ее масштаб и добавляем в пространство сцены. - Строки
60 — 65
. Добавляем тень к модели при условии, что она является меш-объектом. - Строка
68
. Получаем все анимационные клипы модели. - Строка
70
. Создаем проигрыватель для анимации. - Строка
72
. Выбираем определенный анимационный клип с помощьюanimation[0]
. Отбор клипа из числа имеющихся происходит путем изменения индекса. - Строка
73
. Воспроизводим анимационный клип с помощью метода.play()
. - Строка
75
. Вызываем функциюanimate
(которую мы еще не определили). - Строка
77
. Загружаем модель. - Строки
79 — 86
. Создаем визуализаторwebgl
, уточняем настройки и добавляем в тело HTML-файла.renderer.domElement
является элементомcanvas
. - Строка
88
. Инициализируем элементы управления. - Строка
90
. Добавляем слушателя события изменения размера окна.
Обработка события изменения размера окна
def onWindowResize():
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize( window.innerWidth, window.innerHeight )
В данном фрагменте кода определяем обработчик события изменения размера окна.
Анимирование сцены
def animate(time):
window.requestAnimationFrame( animate )
mixerUpdateDelta = clock.getDelta()
N_time = - window.performance.now() / 1000
# Обеспечиваем движение заднего плана назад, создавая иллюзию ходьбы
mesh.position.z = + ( N_time ) % 2
mixer.update( mixerUpdateDelta )
renderer.render( scene, camera )
init()
На завершающем этапе создаем функцию animate
.
- Строка
2
. Сообщаем браузеру о намерении выполнить анимацию. - Строка
3
. Получаем количество секунд, прошедших с начала отсчета времени. - Строки
4 — 6
. Перемещаем поверхность назад. - Строка
7
. Обновляем проигрыватель анимации. - Строка
8
. Отрисовываем сцену и камеру.
Запуск кода
Открываем HTML-файл в браузере.
Читайте также:
- Как загрузить 3D-модель с помощью PyWeb3D
- Создание базовых 3D-сцен с помощью Three.js
- Сравниваем WebGL-фреймворки Three.js и Babylon.js
Читайте нас в Telegram, VK и Дзен
Перевод статьи Bruno Odinukweze: Animating 3D Models Using PyWeb3D