Python para Desenvolvedores

2ª edição, revisada e ampliada

Apêndice B: Blender


Blender é um software de modelagem 3D de código aberto, capaz de gerar animações e também funciona como Game Engine (infraestrutura especializada para criação de jogos para computador). Além disso, o software possui um renderizador interno e pode ser integrado a renderizadores externos, como os projetos, também de código aberto, Yafaray e LuxRender.

Tela do Blender

No Blender, uma cena é composta por objetos, que precisam ser fixados em posições e conectados a cena. Se o objeto não estiver conectado a cena, ele será eliminado ao fim do processamento. Para cada sólido, é possível configurar vários materiais, que podem ter zero ou mais texturas.

A cena padrão do Blender é composta por três objetos: uma câmera, uma lâmpada e um cubo (representado como mesh). A escala no Blender é medida em BUs (Blender Units).

Com Python é possível acessar todas essas estruturas do Blender através de módulos, incluindo:

  • Blender: permite abrir arquivos, salvar e outras funções correlatas.
  • Object: operações com objetos 3D.
  • Materials: manipulação de materiais.
  • Textures: manipulação de texturas.
  • World: manipulação do ambiente da cena.
  • Draw: rotinas de interface com o usuário.
  • Nmesh: manipulação de malhas.
  • BGL: acesso direto as funções do OpenGL.

A API do Blender oferece várias texturas procedurais e uma coleção de sólidos primitivos prontos, que podem ser criados e alterados através de código.

Exemplo de código para a criação de uma cena:

In [ ]:
import math
import Blender

# Pega a cena atual
cena = Blender.Scene.GetCurrent()

# Elementos da cena "default"
camera = Blender.Object.Get()[0]
cubo = Blender.Object.Get()[1]
lamp = Blender.Object.Get()[2]

# Move a câmera
camera.setLocation(8., -8., 4.)
camera.setEuler(math.radians(70), 0.,
    math.radians(45))

# Muda a lente
camera.data.lens = 30

# Remove da cena o objeto "default"
cena.objects.unlink(cubo)

# Altera a intensidade da luz
lamp.data.energy = 1.2

# Muda o tipo para "Sun"
lamp.data.type = 1

# Aumenta o número de samples
lamp.data.raySamplesX = 16
lamp.data.raySamplesY = 16

# E a cor
lamp.data.col = 1., .9, .8

# Cria outra fonte de luz
lamp1 = Blender.Lamp.New('Lamp')
lamp1.energy = 0.5
lamp1.col = .9, 1., 1.
_lamp1 = Blender.Object.New('Lamp')

# Muda o lugar da fonte (default = 0.0, 0.0, 0.0)
_lamp1.setLocation(6., -6., 6.)

# "Prende" a fonte de luz na cena
_lamp1.link(lamp1)
cena.objects.link(_lamp1)

# Cria um material
material1 = Blender.Material.New('newMat1')
material1.rgbCol = [.38, .33, .28]
material1.setAlpha(1.)

# Cria uma textura
textura1 = Blender.Texture.Get()[0]
textura1.setType('Clouds')
textura1.noiseType = 'soft'
textura1.noiseBasis = Blender.Texture.Noise['VORONOICRACKLE']

# Coloca no material
material1.setTexture(0, textura1)
mtex1 = material1.getTextures()[0]
mtex1.col = .26, .22, .18
mtex1.mtNor = 1
mtex1.neg = True
mtex1.texco = Blender.Texture.TexCo['GLOB']
material1.mode += Blender.Material.Modes['RAYMIRROR']
material1.rayMirr = 0.2
material1.glossMir = 0.8

# Cria o piso
mesh = Blender.Mesh.Primitives.Plane(40.)
piso = cena.objects.new(mesh,'Mesh')
piso.setLocation(0., 0., .05)
# Rotaciona o piso
piso.setEuler(0., 0., math.radians(45))

# Coloca o material no piso
piso.setMaterials([material1])
piso.colbits = 1

# Cria outro material
material2 = Blender.Material.New('newMat2')
material2.rgbCol = [.77, .78, .79]
material2.setAlpha(1.)
material2.mode += Blender.Material.Modes['RAYMIRROR']
material2.rayMirr = 0.6
material2.glossMir = 0.4

# Coloca textura no outro material
material2.setTexture(0, textura1)
mtex2 = material2.getTextures()[0]
mtex2.col = .3, .3, .4
mtex2.mtNor = 1
mtex2.neg = True
mtex2.texco = Blender.Texture.TexCo['GLOB']

mat = [material2]

# Cria objetos na cena
def objeto(local, tam, mat, prim=Blender.Mesh.Primitives.Cube):

    mesh = prim()
    obj = cena.objects.new(mesh, 'Mesh')
    obj.setLocation(*local)
    obj.size = tam
    obj.setMaterials(mat)
    obj.colbits = 1
    return obj

def coluna(x=0., y=0., z=0., mat=mat):

    # Cilindro
    prim = Blender.Mesh.Primitives.Cylinder

    # Topo
    local = x, y, 2.5 + z
    tam = .25, .25, .1
    objeto(local, tam, mat)

    # Base
    local = x, y, 0. + z
    objeto(local, tam, mat)

    # Corpo
    for k in xrange(10):
        local = x, y, .25 * k + z
        tam = .2 - k / 200., .2 - k / 200., .25
        objeto(local, tam, mat, prim)

# Cria colunas no fundo
for i in xrange(16):

    # Primeira fileira
    coluna(i - 8., 8)

    # Segunda fileira
    coluna(-8., i - 8.)

    # Aqueduto
    local = -8., i - 8., 3.
    tam = .5, .5, .5
    objeto(local, tam, mat)

    local = i - 8., 8., 3.
    objeto(local, tam, mat)

z = .25

# Cria colunas em cima do piso
for i in (-3, 3):
    for j in range(-2, 3):

        # Fileiras X
        coluna(i, j, z)

        # Fileiras Y
        coluna(j, i, z)

    # Cantos
    for j in (-3, 3):
        coluna(i, j, z)

# Cria escada
for i in xrange(8):
    local = 0., 0., i / 32. - .25
    tam = 3.3 + (8. - i) / 8., 3.3 + (8. - i) / 8., .25
    objeto(local, tam, mat)

# Cria teto
for i in xrange(35):
    local = 0., 0., 2.9 + i / 60.
    tam = 3.5 - i / 200., 3.5 * ( 1. - i / 35.), .1
    objeto(local, tam, mat)

# Pega o "mundo"
world = Blender.World.Get()[0]

# Modo "blend" no fundo
world.skytype = 1

# Atualiza a cena
cena.update()

Saída renderizada:

Blender

Exemplo com interface:

In [ ]:
from math import *
from Blender.Draw import *
from Blender import BGL, Window, Mesh, Scene


class Buttons:
    """
    Classe com para armazenar os botões para que
    os valores possam ser usados por outras rotinas
    sem usar variáveis globais
    """
    # Equação
    formula = Create('cos(x) - sin(3*x)')
    # Variação
    delta = Create(.1)


def interface():
    """
    Função que desenha a interface
    """
    # Pega o tamanho da janela
    x, y = Window.GetAreaSize()

    # Desenha caixa de texto
    # Parâmetros: rótulo, evento, x, y, largura, altura, valor inicial,
    # tamanho máximo do texto, tooltip
    Buttons.formula = String('Fórmula: ', 0, 10, y - 30, 300, 20,
        Buttons.formula.val, 300, 'Entre com uma expressão Python')
    # Desenha caixa de número
    # Parâmetros: rótulo, evento, x, y, largura, altura, valor inicial,
    # mínimo, máximo, tooltip
    Buttons.delta = Number('Delta: ', 0, 10, y - 60, 300, 20,
        Buttons.delta.val, .01, 1., 'Entre com a variação')
    # Desenha os botões
    # Parâmetros: texto do botão, evento, x, y, largura, altura, tooltip
    PushButton('Ok', 1, 10, y - 90, 100, 20, 'Aplica as mudanças')
    PushButton('Fim', 2, 10, y - 120, 100, 20, 'Termina o programa')

    # Comandos OpenGL através da BGL
    BGL.glClearColor(.7, .7, .6, 1)
    BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)

def events(evt, val):
    """
    Função que responde a eventos diversos,
    menos os gerados por botões
    """

    # Os eventos das teclas estão definidas em Draw
    if evt == ESCKEY:
        # Termina o programa
        Exit()

def buttons(evt):
    """
    Função que responde a eventos dos botões
    """

    if  evt == 2:
        Exit()

    elif evt == 1:
        gen3d()

def gen3d():

        # Cena 3D
        cena = Scene.GetCurrent()
        x = y = z = 0

        while x < 2 * pi:

            # Calcula os valores de z
            z = eval(Buttons.formula.val)

            # Cria uma esfera de 16 segmentos, 16 anéis e 0.1 BU de raio
            s = Mesh.Primitives.UVsphere(16, 16, .1)
            esfera = cena.objects.new(s, 'Mesh')
            
            # Transfere a esfera para o local calculado
            esfera.setLocation(x, y, z)

            # Próximo x
            x += Buttons.delta.val
            
        # Atualiza a cena
        cena.update()

if __name__ == '__main__':

    # Registra as funções callback
    Register(interface, events, buttons)

Interface:

Blender

Saída:

Blender

Exemplo de criação de malha:

In [ ]:
from Blender import NMesh, Redraw

# Cria uma nova malha
mesh = NMesh.New()

# Um dicionário para armazenar os vértices
# conforme a posição
vertices = {}

for X in xrange(3):
    for Y in range(3):
        # Uma face é determinada pelos vértices que fazem parte dela
        face = []
        coords = [(X + 0, Y + 0), (X + 0, Y + 1),
            (X + 1, Y+ 1), (X + 1, Y + 0)]

        # Vértices da face
        for x, y in coords:
            vertices[(x, y)] = vertices.get((x, y), NMesh.Vert(x, y, 0))
            face.append(vertices[(x, y)])

        # Adiciona um objeto "face" na lista de faces da malha
        mesh.faces.append(NMesh.Face(face))

# Adiciona os vértices na malha
for vertice in vertices.values():
    mesh.verts.append(vertice)

# Carrega a malha na cena
NMesh.PutRaw(mesh, 'chess', True)
Redraw()

Saída:

Malha no Blender

Para executar código em Python no ambiente do Blender, basta carregar o programa na janela de editor de texto do Blender e usar a opção de execução no menu.

Game engine

Game engine é um software que facilita a criação de jogos, simulando determinados aspectos da realidade, de forma a possibilitar a interação com objetos em tempo real. Para isso, ele deve implementar várias funcionalidades que são comuns em jogos, como por exemplo a capacidade de simulação física. O objetivo principal do uso de game engines é centrar o foco da criação do jogo no conteúdo, ou seja, mapas (níveis), personagens, objetos, diálogos, trilha sonora e cenas. É comum que vários jogos usem o mesmo engine, reduzindo assim, o esforço de desenvolvimento.

Um dos principais recursos fornecidos por game engines é a capacidade de renderizar cenas em 2D ou 3D em tempo real, geralmente usando uma biblioteca gráfica, como o OpenGL, permitindo animações e efeitos especiais. O componente especializado para esta função é conhecido como render engine.

Além disso, a simulação física também é essencial para um jogo, para representar de forma adequada os movimentos dos personagens sendo influenciados pela gravidade, inércia, atrito, detecção de colisões e outros. O componente que realiza esses cálculos é chamado Physics Engine.

Outra funcionalidade importante é a lógica, que é como o jogo determina o comportamento do ambiente e dos personagens. Em muitos casos, o game engine suporta uma ou mais linguagens para descrevê-la.

Os game engines podem incluir outros recursos importantes para determinados tipos de jogos, como conectividade. No caso de MMOG (Massively Multiplayer Online Games), que são muito complexos, a infraestrutura de software é mais conhecida como middleware.

A popularização dos game engines aconteceu durante a década de 90, graças a Id Software, que desenvolveu os jogos que definiram o gênero chamado FPS (First Person Shooter), jogos de ação em primeira pessoa. Esses títulos tiveram seus engines licenciados para outras empresas, que criaram outros jogos desenvolvendo o conteúdo do jogo. Em paralelo, os processadores de vídeo foram incorporando suporte as funções gráficas cada vez mais sofisticadas, o que facilitou a evolução dos engines. A Id também liberou os game engines das séries DOOM e Quake em GPL.

Além de entretenimento, outras áreas podem se beneficiar desses engines. Chamadas genericamente de serious games, aplicações nas áreas de treinamento, arquitetura, engenharia, medicina e marketing estão se popularizando aos poucos.

O Blender inclui um game engine genérico, que permite a criação de jogos 3D, usando o próprio aplicativo para criação de conteúdo e Python para as partes com lógica mais complexa.

O Blender Game Engine (BGE) usa como physics engine o projeto, também Open Source, chamado Bullet. Com ele, é possível simular o comportamento de corpos rígidos (como peças de maquinaria), macios (como tecidos), estáticos (fixos) e intangíveis (que não são afetados por colisões).

Blender Bullet

O render engine do Blender suporta GLSL (OpenGL Shading Language), o que permite que ele utilize recursos avançados disponíveis nos processadores de vídeo mais recentes.

Já a lógica é definida no BGE através de Logic Bricks, que segue um modelo baseado em eventos. Eventos são associados a um objeto da cena e podem ser gerados por periféricos de entrada (como teclado e mouse), pelo sistema (tempo), pelo próprio BGE (colisões, por exemplo) ou por mensagens enviadas por outros objetos. Quando um ou mais eventos são detectados, o BGE toma uma decisão e reage de acordo.

Existem três tipos de bricks:

  • Sensores (sensors), que detectam os eventos.
  • Controladores (controllers), que relacionam os sensores com os ativadores adequados.
  • Ativadores (actuators), que ativam as reações.

No painel Logic, as associações entre os bricks pode ser definida de forma interativa. O BGE tem diversos ativadores prontos, para realizar tarefas como encerrar a execução ou mudar a velocidade do objeto.

O BGE pode evocar código em Python para responder aos eventos, através do controlador “Python”. Quando uma função em Python é executada, ela recebe como argumento o controlador que realizou a chamada, com isso é possível identificar e modificar o objeto (owner) que possui o controlador.

Exemplo (módulo com função para teleporte):

In [ ]:
# Módulo de interface com o Game Engine
import GameLogic

def teleport(cont):

    # obtêm o dono do controller
    own = cont.owner

    # obtêm a cena
    scene = GameLogic.getCurrentScene()
    
    # obtêm o destino
    dest = scene.getObjectList()['OBr_portal']
    
    # obtêm as coordenadas do destino
    x, y, z = dest.getPosition()
    
    # move a câmera para 1 BU acima do destino
    own.setPosition([x, y, z + 1])

Essa função muda a posição do objeto, para um BU a cima do objeto chamado “r_portal”, independente do lugar na cena em que estiver localizado.

In [1]:
 
Out[1]: