Книга: Написание скриптов для Blender 2.49
Рендер билбордов
Рендер билбордов
Слово Billboard дословно переводится как «доска для объявлений» или «рекламный щит», что конечно же мало подходит для нашего случая. В разработке игр billboards часто применяются, и к сожалению, адекватного перевода для этого нигде нет, везде используется эта уродливая транскрипция «билборд». Придётся и мне ей пользоваться - сожаление переводчика.
Реализм в сценах часто обеспечивается массой деталей, особенно на естественных объектах. Тем не менее, такой реализм даётся небесплатно, так как детализированные модели часто содержат много граней, и эти грани поглощают память и увеличивают время рендера. Реалистичная модель дерева может содержать больше полмиллиона граней, так что лес из них будет почти невозможно отрендерить, и, тем более, если этот лес является частью пейзажа в игре, идущей в быстром темпе.
Блендер поставляется со множеством инструментов, позволяющих уменьшить количество необходимой памяти при рендере множества копий объекта; различные Меш-объекты могут ссылаться на одни и те же данные меша, как, например, при DupliVerts. (Объекты-потомки, которые копируются в позицию каждой вершины родительского объекта. Смотри http://www.is.svitonline.com/sailor/doc/man/specmod/dupliverts.htm более подробно.) Дублирование объектов в системах частиц также позволяет нам создавать множество экземпляров того же самого объекта без действительного дублирования всех данных. Эти методы могут предотвратить потери огромного количества памяти, но детализированные объекты все еще могут требовать процессорных мощностей для рендера, поскольку их детали все еще должны быть отрендерены.
Билборды являются методом, используемым для наложения изображения сложного объекта на простой объект, такой, как например, одиночная квадратная грань, и размножения этого простого объекта столько раз, сколько нужно. Изображение должно иметь подходящую прозрачность, в противном случае каждый объект будет закрывать другие не так, как требуется. За исключением этого момента, такая техника очень проста и может значительно уменьшить время рендера, и она даёт довольно реалистичные результаты для объектов, установленных на средних расстояниях или дальше. Системы частиц Блендера могут использовать билборды или как простые квадраты с наложенными изображениями, или накладывать изображение на простой объект и использовать его, как объект дублирования. Последнее также относится и к объектам duplivert.
Хитрость в том, что нужно сгенерировать изображение с подходящим освещением, чтобы использовать его как изображение, которое можно приложить к квадрату. На самом деле мы хотим создать два изображения: одно снятое с передней стороны, одно справа и построить объект, состоящий из двух квадратных граней, перпендикулярных друг другу с наложенными двумя изображениями. Такой объект даст нам несколько больше свободы в последствии при размещении камеры на нашей сцене, так как он не обязательно должен быть виден точно с одной стороны. Это хорошо работает только для объектов с приблизительно цилиндрической симметрией, как например, деревья или многоэтажки, но зато это очень эффективно.
Рабочий процесс для создания таких объектов достаточно сложен, так что его желательно автоматизировать:
1. Спозиционировать две камеры, спереди и справа от детального объекта.
2. Откадрировать обе камеры, чтобы они захватывали весь объект с одинаковым углом.
3. Отрендерить прозрачные изображения с premultiplied (заранее перемноженным) альфа-каналом и без неба.
4. Создать простой объект из двух перпендикулярных квадратов.
5. Наложить каждое отрендеренное изображение на квадрат.
6. Скрыть детальный объект от рендера.
7. Необязательно, скопировать простой объект в систему частиц (пользователю может не понадобиться автоматизировать эту часть, если он захочет расставить простые объекты вручную).
"Premultiplication", упомянутое в третьем шаге, возможно, требует некоторого пояснения. Очевидно, отрендеренные изображения нашего сложного объекта не должны показывать никакого фонового неба, так как их скопированные клоны могут позиционироваться где угодно, и могут показывать различные части неба через свои прозрачные части. Как мы увидим, это достаточно просто сделать, но когда мы просто рендерим прозрачное изображение и перекрываем им позже некоторый фон, изображение может иметь некрасивые бросающиеся в глаза края.
Способ избежать этого - отрегулировать отрендеренные цвета, перемножив их с величиной альфы и контекст рендера имеет необходимые атрибуты, чтобы включить такой режим. Мы не должны забывать отмечать изображения, рендеренные как "premultiplied", при использовании их в качестве текстур, в противном случае они будут выглядеть слишком тёмными. Различие проиллюстрировано на следующем скриншоте, где мы скомпоновали и расширили правильно premultiplied левую половину и отрендеренную с небом правую половину. У ствола дерева справа проявляется светлый край. (Посмотрите отличную книгу Роджера Викса "Foundation Blender Compositing", если нужна дополнительная информация.)
Буковое дерево (использованное на этой и последующих иллюстрациях) - это высокодетальная модель (свыше 30,000 граней), созданная Yorik van Havre с помощью свободного пакета моделирования растений ngPlant. (Смотри его вебсайт для большего количества отличных примеров: http://yorik.uncreated.net/ greenhouse.html). Далее первый набор изображений показывает буковое дерево спереди и результирующий рендер передней грани билборда слева. (немного темнее из-за premultiplication).
Следующий набор скриншотов показывает то же буковое дерево, отрендеренное справа вместе с рендером правой грани билборда слева. Как может быть заметно, исполнение конечно, не идеально с этой точки зрения, но это крупный план, а разумный трехмерный аспект сохраняется.
Чтобы показать, как устроена конструкция билбордов, следующий скриншот показывает две грани с наложенными отрендеренными изображениями. Прозрачность умышленно уменьшена, чтобы было видно отдельные грани.
Нашей первой проблемой будут некоторые ранее используемые функции, которые мы писали для презентации модели с несколькими видами. Эти функции находятся в текстовом буфере с именем combine.py, и мы не сохраняли его во внешний файл. Мы создадим наш скрипт cardboard.py как новый текстовый буфер в том же .blend файле, где и combine.py, и хотим ссылаться на последний так же, как на внешний модуль. Блендер позволяет это делать, так как он ищет модуль в текущих текстовых буферах, если он не может найти внешний файл.
Поскольку внутренние текстовые буферы не имеют информации о том, когда они последний раз изменялись, мы должны убедиться, что загружена самая последняя версия. Об этом позаботится функция reload(). Если мы её не выполним, Блендер не сможет обнаружить возможных изменений в combine.py, что могло бы провести нас к использованию его более старой скомпилированной версии:
import combine
reload(combine)
Мы не будем использовать заново функцию render() из combine.py, поскольку сейчас у нас другие требования для рендеренных изображений, которые мы наложим на билборды. Как уже объяснялось, мы должны убедиться, что мы не получим никаких светлых краёв в местах с частичной прозрачностью, так что мы заранее включаем premultiply в альфа-канале (выделено). Мы восстанавливаем контекст рендера в 'рендер неба' (rendering the sky) обратно до возврата из этой функции, поскольку легко забыть установить его обратно вручную, и Вы можете потратить время на удивление, куда подевалось ваше небо:
def render(camera):
cam = Object.Get(camera)
scn = Scene.GetCurrent()
scn.setCurrentCamera(cam)
context = scn.getRenderingContext()
frame = context.currentFrame()
context.endFrame(frame)
context.startFrame(frame)
context.displayMode=0
context.enablePremultiply()
context.renderAnim()
filename= context.getFrameFilename()
camera = os.path.join(os.path.dirname(filename),camera)
try:
os.remove(camera) # удаление, в противном случае
# переименование
# потерпит неудачу в windows
except:
pass
os.rename(filename,camera)
context.enableSky()
return camera
Каждое отрендеренное изображение должно быть преобразовано в подходящий материал, чтобы наложить его на квадрат с UV-отображением. Функция imagemat() будет делать это просто; она принимает объект Блендера Image в качестве аргумента и возвращает объект Материала. Этот материал будет сделан полностью прозрачным (выделено), но эта прозрачность и цвет модифицируются текстурой, которую мы назначаем в первый текстурный канал (вторая выделенная строка). Тип текстур установлен в Image и, поскольку мы визуализировали эти изображения с premultiplied альфа-каналом, мы используем метод setImageFlags(), чтобы указать, что мы хотим использовать этот альфа-канал, и устанавливаем атрибут premul изображения в Истину:
def imagemat(image):
mat = Material.New()
mat.setAlpha(0.0)
mat.setMode(mat.getMode()|Material.Modes.ZTRANSP)
tex = Texture.New()
tex.setType('Image')
tex.image = image
tex.setImageFlags('UseAlpha')
image.premul=True
mat.setTexture(0,tex,Texture.TexCo.UV,
Texture.MapTo.COL|Texture.MapTo.ALPHA)
return mat
Каждая грань, к которой мы применяем материал, должна иметь UV-раскладку. В нашем случае, это будет самой простой из возможных раскладок, так как квадратная грань будет отображена так, чтобы в точности соответствовать прямоугольному изображению. Это часто называют сбросом отображения, и следовательно, функция, которую мы определим, называется reset(). Она принимает объект Блендера MFace, который мы считаем четырёхугольником, и присваивает его атрибуту uv список 2D-векторов, по одному для каждой вершины. Эти векторы размещают каждую из вершин в углах изображения:
def reset(face):
face.uv=[vec(0.0,0.0),vec(1.0,0.0),
vec(1.0,1.0),vec(0.0,1.0)]
Функция cardboard() заботится о создании фактического Меш-объекта из двух объектов Image, переданных как аргументы. Она начинается с создания двух квадратных граней, которые пересекают друг друга вдоль оси z. Следующий шаг должен добавить UV-слой (выделено) и сделать его активным:
def cardboard(left,right):
mesh = Mesh.New('Cardboard')
verts=[(0.0,0.0,0.0),(1.0,0.0,0.0),
(1.0,0.0,1.0),(0.0,0.0,1.0),
(0.5,-0.5,0.0),(0.5,0.5,0.0),
(0.5,0.5,1.0),(0.5,-0.5,1.0)]
faces=[(0,1,2,3),(4,5,6,7)]
mesh.verts.extend(verts)
mesh.faces.extend(faces)
mesh.addUVLayer('Reset')
mesh.activeUVLayer='Reset'
Затем мы создаем подходящие материалы из обоих изображений, и назначаем эти материалы в атрибут меша materials. Далее, мы сбрасываем (reset) UV-координаты обеих граней, и назначаем им материалы (выделено). Мы обновляем (update) меш, чтобы сделать изменения видимыми до возврата из функции:
mesh.materials=[imagemat(left),imagemat(right)]
reset(mesh.faces[0])
reset(mesh.faces[1])
mesh.faces[0].mat=0
mesh.faces[1].mat=1
mesh.update()
return mesh
Чтобы заменить меш дублированием объекта системой частиц, мы строим утилиту setmesh(). Она принимает имя объекта со связанной системой частиц и Меш-объект как аргументы. Она находит Объект по имени, и извлекает первую систему частиц (выделено в следующем куске кода). Объект дублирования находится в атрибуте duplicateObject. Заметьте, что этот атрибут только для чтения, так что к настоящему времени нет возможности поменять объект из Питона. Но мы можем заменить данные объекта и, мы это делаем посредством передачи Меш-объекта в метод link(). Оба объекта, эмиттер и объект дублирования системой частиц изменятся, так что мы удостоверимся, что изменения станут видимыми, вызывая метод makeDisplayList() для них обоих перед запуском обновления изображения (redraw) всех окон Блендера:
def setmesh(obname,mesh):
ob = Object.Get(obname)
ps = ob.getParticleSystems()[0]
dup = ps.duplicateObject
dup.link(mesh)
ob.makeDisplayList()
dup.makeDisplayList()
Window.RedrawAll()
Функция run() включает всю работу, которую нужно сделать, чтобы преобразовать активный объект в набор билбордов, и назначить его в систему частиц. Сначала мы извлекаем ссылку на активный объект, и убеждаемся, что он будет видимым при рендере:
def run():
act_ob = Scene.GetCurrent().objects.active
act_ob.restrictRender = False
Следующим шагом нужно сделать остальные объекты на сцене невидимыми до того, как мы отрендерим билборды. Некоторые из них, возможно, уже были сделаны невидимыми пользователем, следовательно, мы должны запомнить эти состояния, чтобы мы могли восстановить их позже. Также мы не изменяем состояние ламп или камер, так как сделав их невидимыми, мы останемся с полностью черными изображениями (выделено):
renderstate = {}
for ob in Scene.GetCurrent().objects:
renderstate[ob.getName()] = ob.restrictRender
if not ob.getType() in ('Camera','Lamp' ):
ob.restrictRender = True
act_ob.restrictRender = False
Как только всё настроено, чтобы рендерить только активный объект, мы рендерим переднее и правое изображения с должным образом откадрированными камерами, просто подобно тому, как мы это делали в скрипте combine.py. Фактически, здесь мы заново используем функцию frame() (выделено):
cameras = ('Front','Right')
combine.frame(cameras,act_ob.getBoundBox())
images={}
for cam in cameras:
im=Image.Load(render(cam))
im.reload()
images[cam]=im
bpy.data.images.active = im
Window.RedrawAll()
Затем мы восстанавливаем предыдущую видимость всех объектов на сцене прежде, чем мы создадим новый меш из двух изображений. Мы заканчиваем, делая активный объект невидимым для рендера и заменяя меш объекта дублирования в определенной системе частиц нашим новым мешем:
for ob in Scene.GetCurrent().objects:
ob.restrictRender = renderstate[ob.getName()]
mesh = cardboard(images['Front'],images['Right'])
act_ob.restrictRender = True
setmesh('CardboardP',mesh)
Последние строки кода создают камеры, необходимые для рендера билбордов (если эти камеры в данный момент отсутствуют), вызывая функцию createcams() из модуля combine до вызова run():
if __name__ == "__main__":
combine.createcams()
run()
Полный код доступен как cardboard.py в файле combine.blend.