Нащо воно взагалі треба? Якщо ми хочемо просунутись до програмування трьохвимірних ігор, нам обов'язково треба включити в свій арсенал хоч якийсь редактор. Бо навіть простенька модель кубика, якщо її малювати вручну, з коду буде виглядати так:
glBegin(GL_POLYGON); glVertex3f(1.000000,1.000000,-1.000000); glVertex3f(1.000000,-1.000000,-1.000000); glVertex3f(-1.000000,-1.000000,-1.000000); glVertex3f(-1.000000,1.000000,-1.000000); glEnd();
glBegin(GL_POLYGON); glVertex3f(1.000000,0.999999,1.000000); glVertex3f(-1.000000,1.000000,1.000000); glVertex3f(-1.000000,-1.000000,1.000000); glVertex3f(0.999999,-1.000001,1.000000); glEnd();
glBegin(GL_POLYGON); glVertex3f(1.000000,1.000000,-1.000000); glVertex3f(1.000000,0.999999,1.000000); glVertex3f(0.999999,-1.000001,1.000000); glVertex3f(1.000000,-1.000000,-1.000000); glEnd();
glBegin(GL_POLYGON); glVertex3f(1.000000,-1.000000,-1.000000); glVertex3f(0.999999,-1.000001,1.000000); glVertex3f(-1.000000,-1.000000,1.000000); glVertex3f(-1.000000,-1.000000,-1.000000); glEnd();
glBegin(GL_POLYGON); glVertex3f(-1.000000,-1.000000,-1.000000); glVertex3f(-1.000000,-1.000000,1.000000); glVertex3f(-1.000000,1.000000,1.000000); glVertex3f(-1.000000,1.000000,-1.000000); glEnd();
glBegin(GL_POLYGON); glVertex3f(1.000000,0.999999,1.000000); glVertex3f(1.000000,1.000000,-1.000000); glVertex3f(-1.000000,1.000000,-1.000000); glVertex3f(-1.000000,1.000000,1.000000); glEnd();
А в ньому всього то шість граней. І то я полінувався вводити все вручну, і згенерував цей кубик скриптом з блендера. Тепер порахуйте скільки граней у цієї лисої пані:
А якби вона була не лиса? От тому без 3d редактора ми нікуди не просунемось. Обрати Blender варто вже тому, що він безкоштовний, функціональний, має вбудований ігровий рушій, і скриптується нашим улюбленцем Пайтоном.
Щоб трохи розібратись з інтерфейсом Блендера який спочатку шокує, майже як штурвал літака, варто сісти на кілька годин за підручник, і потицяти кнопки. Про Блендер можна прочитати:
Blender 3D: Noob to Pro - англійський вікіпідручник, один з кращих які можна знайти в мережі. Blender: первые шаги в Python Blender - вікіпідручник, в зародковій стадії, його треба не читати, а скоріше писати.
Привіт, світе!
Скрипти можна писати як у вбудованому редакторі Блендера, так і в зовнішньому. Вбудований може бути незручним для вас, якщо ви звикли до певних комбінацій клавіш. Наприклад, щоб копіювати та вставити текст з буфера обміну, доведеться користуватись комбінаціями CTR+SHIFT-C та CTR+SHIFT+V відповідно.
Скрипти зберігаються в каталозі ~/.blender/scripts у лінуксі, та там де ви встановили блендер\.blender\scripts у windows. Тепер створимо там маленький скрипт. Якось його називаємо. Наприклад hello.py.
Щоб мати поняття що ми хочемо зробити можна подивитись коротке трихвилинне відео.
#!BPY
""" Name: 'Hello' Blender: 244 Group: 'Export' Tooltip: 'Our test script' """ import Blender import bpy
def write(filename): out = file(filename, "w") sce= bpy.data.scenes.active for ob in sce.objects: out.write(ob.type + ": " + ob.name + "\n") out.close()
Blender.Window.FileSelector(write, "Export")
Тепер повертаємось до вікна скриптів, і в його меню Scripts вибираємо пункт Update Menus. Після чого шукаємо hello в Scripts -> Export. Натискаємо. У вікні скриптів з'являється вікно вибору файлу. Вводимо якусь назву, і натиснувши Export отримаємо там текстовий файл з приблизно таким вмістом:
Mesh: Cube Lamp: Lamp Camera: Camera
Якщо звісно ми нічого не додавали на нашу сцену.
Як воно працює?
Розглянемо наш скрипт рядок за рядком: #!BPY Перший рядок каже блендеру, що це скрипт блендера, і що його треба додати в меню скануючи папки зі скриптами. Далі йде багаторядкова документація. Вона складається з чотирьох рядків Name: 'Hello' Blender: 244 Group: 'Export' Tooltip: 'Our test script' які допомагають блендеру правильно розмістити скрипт в меню. Name - так буде називатись пункт меню. Group - він буде поміщений в таке меню. Tooltip - підказка що буде з'являтись при затримці курсора над пунктом меню. Ну, а Blender - номер версії для якої написаний скрипт.
Далі завантажуються два модулі:
import Blender import bpy Перший модуль є основним для написання скриптів, другий загалом повторює його функціональність, та поки що є експериментальним. Він мав би надавати зручніший доступ до даних та функцій блендера, та поки що його вміст часто змінюють. Модуль Blender поки що задокументований краще.
Далі ми створюємо функцію, яка пише щось у файл: def write(filename): out = file(filename, "w")
Це просто. Далі цікавіше:
sce= bpy.data.scenes.active for ob in sce.objects: out.write(ob.type + ": " + ob.name + "\n")
Наступні три рядки беруть поточну сцену, і для всіх об'єктів сцени виводять у файл тип об'єкта, та через дві крапки його ім'я. Потім файл закривається і функція закінчується.
А остання функція: Blender.Window.FileSelector(write, "Export")
Відображає вікно вибору файла, з кнопкою "Export". Після того як її натиснуть викликає функцію write передавши їй ім'я файлу.
Взагалі то, цей скрипт майже нічого не робить, зате демонструє нам загальний принцип роботи розширень Блендера.
Експорт мешу Щоб розібратись з форматом трикутного мешу у Блендері, найкраще було б написати скрипт, яка виписує дані мешу у зовнішній файл. Наприклад можна написати програму, що генерує код OpenGL який малює наш об'єкт. Для цього треба замінити три основні рядки в попередньому скрипті.
Спочатку знайти вибраний об'єкт: sce = bpy.data.scenes.active ob = sce.objects.active
Потім отримати його меш: mesh = ob.getData(mesh=1)
Хоча ми могли і отримати не меш. Якщо наприклад вибраним об'єктом було джерело світла. А воно не має ніякої трикутної сітки. Тому, в коді що піде далі варто ловити виключення. Якось так: try: for face in mesh.faces: out.write("glBegin(GL_POLYGON);\n") for vert in face.v: c=mesh.verts[vert.index].co out.write("glVertex3f(%f,%f,%f);\n" % (c.x,c.y,c.z) ) out.write("glEnd();\n\n") except: print "Selected something without mesh. Nothing exported." Тобто від пробує виконати код в секції try, а якщо не виходить, то жаліється в консоль. Варто зауважити, що при тестуванні скриптів до блендера, бажано щоб він був запущений з консолі, тоді в ній будуть з'являтись повідомлення про помилки. Він вам прямо так і скаже: , але не скаже де ту консоль шукати, тому вам варто знати самим.
Тепер по коду. mesh.faces як вже можна було здогадатись - список граней нашої моделі. Кожна грань має список своїх вершин - атрибут v. Кожна вершина списку має атрибут .index, в якому зберігається індекс цієї вершини - її номер в загальному списку вершин. (Очевидно що одна вершина зазвичай входить в більш ніж одну грань, тому зберігати її координати по кілька раз - неефективно.
Ну, а mesh.verts - список вершин. Основні атрибути в вершини це co - координати, та no - нормаль. Що цікаво, нормалі також є і у кожної грані. Які з них впливають на затінення - визначається кнопками set smooth та set solid на панелі кнопок у вкладці Editing -> Link and materials. Але туди лізти поки що не варто.
Генерація свого мешу Трикутник
Тепер давайте намалюємо свій меш. Що небудь попростіше. Найпростіше - трикутник, з нього і почнемо. Ось функція яка його створює:
def createtriangle(): obj=NMesh.GetRaw()
v1=NMesh.Vert(0,10,0) v2=NMesh.Vert(-1,0,0) v3=NMesh.Vert(1,0,0)
obj.verts.append(v1) obj.verts.append(v2) obj.verts.append(v3)
f=NMesh.Face() f.v.append(obj.verts) f.v.append(obj.verts) f.v.append(obj.verts)
obj.faces.append(f)
NMesh.PutRaw(obj,"Triangle",1)
А тепер по порядку. Спочатку ми створюємо базовий об'єкт, з чистим мешем:
obj=NMesh.GetRaw()
Якщо цій функції передати параметром ім'я якогось існуючого об'єкта, то меш того об'єкта буде взятий за основу. Тобто побудова почнеться не з нуля. Тільки варто переконатись що такий об'єкт існує. Далі створимо якісь три вершини. Будь-які, основне - аби їх було не менше трьох, бо інакше ми не отримаємо жодної грані.
v1=NMesh.Vert(0,10,0) v2=NMesh.Vert(-1,0,0) v3=NMesh.Vert(1,0,0)
Потім додамо ці вершини у об'єкт.
obj.verts.append(v1) obj.verts.append(v2) obj.verts.append(v3)
Коли вершини створено, можна створювати грані. Починають з порожньої грані.
f=NMesh.Face()
А потім в неї додають потрібні вершини. З об'єкта. (Щоб він міг розібратись з індексами). Десь ось так:
f.v.append(obj.verts) f.v.append(obj.verts) f.v.append(obj.verts)
Після того як грань буде готова додаємо її в об'єкт.
obj.faces.append(f)
А так як наш об'єкт - трикутник, то цього достатньо. Залишилось тільки зробити так, щоб він появився у вікні блендера.
NMesh.PutRaw(obj,"Triangle",1)
Першим параметром передається об'єкт, що буде вставлятись в сцену, другим - його ім'я. Якщо ім'я унікальне - створиться новий об'єкт. Якщо ні - то той об'єкт що носив це ім'я раніше заміниться на наш новий. Тому, будьте обережні. Третій параметр - прапор, що вказує блендеру, про необхідність обчислення нормалей. Нуль - ми задали нормалі. Інакше - обчислити їх.
Карта висот Тепер давайте зробимо щось поскладніше. Наприклад згенеруємо карту висот за функцією. Вони бувають доволі симпатичні:
Зразу покажу скрипт який це намалював:
#!BPY
""" Name: 'Function heightmap' Blender: 244 Group: 'Mesh' Tooltip: 'Draw function heightmap mesh' """
import Blender from Blender import NMesh import bpy import math
def f(x,y): return (x*x+y*y)/5
def genheightmap(f,minx=-5,miny=-5,maxx=5,maxy=5,resolution=0.1,name="heightmap"): width=int( (maxx-minx)/resolution)+1 height = int( (maxy - miny) / resolution)+1 maxx=minx+resolution*width maxy=miny+resolution*height
obj=NMesh.GetRaw() y=miny obj=NMesh.GetRaw() for y in range(height): for x in range(width): fx=minx+x*resolution fy=miny+y*resolution z=f(fx,fy) v=NMesh.Vert(fx,fy,z) obj.verts.append(v) for y in range(height-1): for x in range(width-1): f=NMesh.Face() f.v.append(obj.verts[y*width + x]) f.v.append(obj.verts[(y+1)*width + x]) f.v.append(obj.verts[y*width + x + 1]) obj.faces.append(f)
f=NMesh.Face() f.v.append(obj.verts[y*width + x + 1]) f.v.append(obj.verts[(y+1)*width + x + 1]) f.v.append(obj.verts[(y+1)*width + x]) obj.faces.append(f) NMesh.PutRaw(obj,name,1)
genheightmap(f,-5,-5,5,5,0.7)
Він майже аналогічний скрипту що малював трикутник, з тією відмінністю, що трикутників тут набагато більше. Принцип роботи дуже простий. Подивимось на нашу карту висот в режимі дротяної моделі. Функцію я регулярно змінював, бо цікаво ж.
Може здатись що топологія дуже складна, та насправді, якщо глянути зверху, ми побачим звичайну однорідну сітку:
Тому, нам спочатку треба обчислити координати вершин на однорідному розбитті, та заповнити ними модель:
for y in range(height): for x in range(width): fx=minx+resolution*x fy=miny+resolution*y z=f(fx,fy) v=NMesh.Vert(fx,fy,z) obj.verts.append(v)
А потім, об'єднати їх в трикутники, по два трикутники на клітинку, як на попередній ілюстрації. Зауважте, що якщо ми маємо width вершин в ряді, то клітинок в ряді буде width-1.
Наш скрипт чудово працює, але є одне але. Щоб змінити функцію нам треба міняти його код. А це незручно. Зручніше було б, якщо б скрипт давав нам можливість ввести свою функцію, точність, межі на яких її обчислювати. А для цього нам потрібний якийсь інтерфейс.
Створення графічного інтерфейсу Графічний інтерфейс у блендері створюється ну просто дуже просто. Простіше хіба що в Delphi. За створення інтерфейсу відповідає модуль Blender.Draw. І одна команда:
Blender.Draw.Register(draw,event,button)
Ця команда приймає в параметрах три функції, які вона запускає в разі необхідності: draw - функція що перемальовує інтерфейс. event - функція що обробляє події, такі як рухи миші, та натиснення клавіш клавіатури. button - функція що обробляє події натискання кнопок графічного інтерфейсу.
Давайте поглянемо одразу на можливий інтерфейс для нашого скрипта:
Його малює така функція:
def draw(): Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
