Как анимировать 3D-модель с помощью PyWeb3D

Данное руководство научит вас анимировать 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-файл в браузере.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Bruno Odinukweze: Animating 3D Models Using PyWeb3D

Предыдущая статьяРазработка приложения на ChatGPT: пошаговое руководство
Следующая статья3 основных принципа несвязных приложений