This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English
Todo objeto exibido na tela pela engine - sprites, modelos, tiles, partículas ou nós de GUI - é desenhado por um renderizador. No centro do renderizador está um script de renderização que controla o pipeline de renderização. Por padrão, todo objeto 2D é desenhado com o bitmap correto, com a mesclagem especificada e na profundidade Z correta. Assim, talvez você nunca precise pensar em renderização além da ordem e de mesclagens simples. Para a maioria dos jogos 2D, o pipeline padrão funciona bem, mas seu jogo pode ter requisitos especiais. Se esse for o caso, o Defold permite escrever um pipeline de renderização sob medida.
O pipeline de renderização controla o que renderizar, quando renderizar e também onde renderizar. O que renderizar é controlado por predicados de renderização. Quando renderizar um predicado é controlado no script de renderização, e onde renderizar um predicado é controlado pela projeção de visualização. O pipeline de renderização também pode descartar gráficos desenhados por um predicado de renderização que estejam fora de uma caixa delimitadora ou frustum definido. Esse processo é chamado de frustum culling.
O arquivo de renderização contém uma referência ao script de renderização atual, além de materiais personalizados que devem ficar disponíveis no script de renderização (use com render.enable_material())
No centro do pipeline de renderização está o script de renderização. Esse é um script Lua com as funções init(), update() e on_message(), usado principalmente para interagir com a API gráfica subjacente. O script de renderização tem um lugar especial no ciclo de vida do seu jogo. Detalhes podem ser encontrados na documentação do ciclo de vida da aplicação.
Na pasta “Builtins” dos seus projetos, você encontra o recurso de renderização padrão (“default.render”) e o script de renderização padrão (“default.render_script”).

Para configurar um renderizador personalizado:
Copie os arquivos “default.render” e “default.render_script” para um local na hierarquia do seu projeto. É claro que você pode criar um script de renderização do zero, mas é uma boa ideia começar com uma cópia do script padrão, especialmente se você ainda está começando no Defold e/ou em programação gráfica.
Edite sua cópia do arquivo “default.render” e altere a propriedade Script para apontar para sua cópia do script de renderização.
Altere a propriedade Render (em bootstrap) no arquivo de configurações game.project para apontar para sua cópia do arquivo “default.render”.
Para controlar a ordem de desenho dos objetos, você cria predicados de renderização. Um predicado declara o que deve ser desenhado com base em uma seleção de tags de material.
Cada objeto desenhado na tela tem um material associado que controla como o objeto deve ser desenhado na tela. No material, você especifica uma ou mais tags que devem ser associadas ao material.
No seu script de renderização, você pode então criar um render predicate e especificar quais tags devem pertencer a esse predicado. Quando você manda a engine desenhar o predicado, cada objeto com um material contendo todas as tags especificadas para o predicado será desenhado.
Sprite 1 Sprite 2 Sprite 3 Sprite 4
Material A Material A Material B Material C
outlined outlined greyscale outlined
tree tree tree house
-- um predicado que corresponde a todos os sprites com a tag "tree"
local trees = render.predicate({"tree"})
-- desenhará Sprite 1, 2 e 3
render.draw(trees)
-- um predicado que corresponde a todos os sprites com a tag "outlined"
local outlined = render.predicate({"outlined"})
-- desenhará Sprite 1, 2 e 4
render.draw(outlined)
-- um predicado que corresponde a todos os sprites com as tags "outlined" E "tree"
local outlined_trees = render.predicate({"outlined", "tree"})
-- desenhará Sprite 1 e 2
render.draw(outlined_trees)
Uma descrição detalhada de como os materiais funcionam pode ser encontrada na documentação de Material.
O script de renderização padrão é configurado para usar uma projeção ortográfica adequada para jogos 2D. Ele fornece três projeções ortográficas diferentes: Stretch (padrão), Fixed Fit e Fixed. Como alternativa às projeções ortográficas no script de renderização padrão, você também tem a opção de usar a matriz de projeção fornecida por um componente de câmera.
A projeção stretch sempre desenha uma área do seu jogo igual às dimensões definidas em game.project, mesmo quando a janela é redimensionada. Se a proporção de tela mudar, o conteúdo do jogo será esticado vertical ou horizontalmente:

Projeção Stretch com o tamanho original da janela

Projeção Stretch com a janela esticada horizontalmente
A projeção stretch é a projeção padrão, mas se você mudou para outra e precisa voltar, faça isso enviando uma mensagem ao script de renderização:
msg.post("@render:", "use_stretch_projection", { near = -1, far = 1 })
Assim como a projeção stretch, a projeção fixed fit sempre mostra uma área do jogo igual às dimensões definidas em game.project, mas se a janela for redimensionada e a proporção de tela mudar, o conteúdo do jogo manterá a proporção original e conteúdo adicional será mostrado vertical ou horizontalmente:

Projeção Fixed Fit com o tamanho original da janela

Projeção Fixed Fit com a janela esticada horizontalmente

Projeção Fixed Fit com a janela reduzida a 50% do tamanho original
Você ativa a projeção fixed fit enviando uma mensagem ao script de renderização:
msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 })
A projeção fixed manterá a proporção de tela original e renderizará o conteúdo do jogo com um nível fixo de zoom. Isso significa que, se o nível de zoom for definido para algo diferente de 100%, ela mostrará mais ou menos que a área do jogo definida pelas dimensões em game.project:

Projeção Fixed com zoom definido como 2

Projeção Fixed com zoom definido como 0.5

Projeção Fixed com zoom definido como 2 e janela reduzida a 50% do tamanho original
Você ativa a projeção fixed enviando uma mensagem ao script de renderização:
msg.post("@render:", "use_fixed_projection", { near = -1, far = 1, zoom = 2 })
Ao usar o script de renderização padrão, se houver componentes Camera ativados disponíveis no projeto, eles terão precedência sobre qualquer outra visualização/projeção definida no script de renderização. Para ler mais sobre como trabalhar com componentes de câmera em scripts de renderização, consulte a documentação de Camera.
Câmeras ortográficas suportam um Orthographic Mode que controla como a câmera se adapta à janela:
Fixed usa o valor Orthographic Zoom da câmera.Auto Fit (contain) mantém toda a área de design visível.Auto Cover (cover) preenche a janela e pode cortar.Você pode alternar modos no Editor ou em tempo de execução pela Camera API:
-- Usa comportamento auto-fit com uma câmera ortográfica
camera.set_orthographic_mode("main:/go#camera", camera.ORTHO_MODE_AUTO_FIT)
-- Consulta o modo atual
local mode = camera.get_orthographic_mode("main:/go#camera")
A API de renderização do Defold permite que desenvolvedores façam algo chamado frustum culling. Quando frustum culling está ativado, qualquer gráfico fora de uma caixa delimitadora ou frustum definido será ignorado. Em um mundo de jogo grande, onde apenas uma parte fica visível por vez, frustum culling pode reduzir drasticamente a quantidade de dados que precisa ser enviada à GPU para renderização, aumentando o desempenho e economizando bateria (em dispositivos móveis). É comum usar a visualização e a projeção da câmera para criar a caixa delimitadora. O script de renderização padrão usa a visualização e a projeção (da câmera) para calcular um frustum.
Frustum culling é implementado na engine por tipo de componente. Status atual (Defold 1.9.0):
| Component | Supported |
|---|---|
| Sprite | YES |
| Model | YES |
| Mesh | YES (1) |
| Label | YES |
| Spine | YES |
| Particle fx | NO |
| Tilemap | YES |
| Rive | NO |
1 = A caixa delimitadora de Mesh precisa ser definida pelo desenvolvedor. Saiba mais.
Quando componentes são renderizados, normalmente falamos em qual sistema de coordenadas eles são renderizados. Na maioria dos jogos, alguns componentes são desenhados em espaço de mundo e outros em espaço de tela.
Componentes GUI e seus nós geralmente são desenhados no sistema de coordenadas de espaço de tela, com o canto inferior esquerdo da tela na coordenada (0,0) e o canto superior direito em (largura da tela, altura da tela). O sistema de coordenadas de espaço de tela nunca é deslocado nem traduzido de outra forma por uma câmera. Isso mantém os nós de GUI sempre desenhados na tela, independentemente de como o mundo é renderizado.
Sprites, tilemaps e outros componentes usados por objetos de jogo que existem no mundo do seu jogo geralmente são desenhados no sistema de coordenadas de espaço de mundo. Se você não fizer modificações no script de renderização e não usar nenhum componente de câmera para alterar a projeção de visualização, esse sistema de coordenadas será o mesmo que o sistema de coordenadas de espaço de tela. Mas assim que você adicionar uma câmera e movê-la ou alterar a projeção de visualização, os dois sistemas de coordenadas passarão a divergir. Quando a câmera se move, o canto inferior esquerdo da tela será deslocado de (0, 0) para que outras partes do mundo sejam renderizadas. Se a projeção mudar, as coordenadas serão traduzidas (ou seja, deslocadas de 0, 0) e modificadas por um fator de escala.
Abaixo está o código de um script de renderização personalizado, que é uma versão levemente modificada do script integrado.
init() é usada para configurar os predicados, a visualização e a cor de limpeza. Essas variáveis serão usadas durante a renderização propriamente dita.function init(self)
-- Define os predicados de renderização. Cada predicado é desenhado separadamente e
-- isso permite alterar o estado do OpenGL entre os desenhos.
self.predicates = create_predicates("tile", "gui", "text", "particle", "model")
-- Cria e preenche tabelas de dados que serão usadas em update()
local state = create_state()
self.state = state
local camera_world = create_camera(state, "camera_world", true)
init_camera(camera_world, get_stretch_projection)
local camera_gui = create_camera(state, "camera_gui")
init_camera(camera_gui, get_gui_projection)
update_state(state)
end
update() é chamada uma vez a cada frame. Sua função é realizar o desenho propriamente dito chamando as APIs OpenGL ES subjacentes (OpenGL Embedded Systems API). Para entender corretamente o que acontece na função update(), você precisa entender como o OpenGL funciona. Há muitos ótimos recursos sobre OpenGL ES disponíveis. O site oficial é um bom ponto de partida. Você o encontra em https://www.khronos.org/opengles/
Este exemplo contém a configuração necessária para desenhar modelos 3D. A função init() definiu um predicado self.predicates.model. Em outro lugar, um material com a tag “model” foi criado. Também há alguns componentes de modelo que usam esse material:
function update(self)
local state = self.state
if not state.valid then
if not update_state(state) then
return
end
end
local predicates = self.predicates
-- limpa os buffers da tela
--
render.set_depth_mask(true)
render.set_stencil_mask(0xff)
render.clear(state.clear_buffers)
local camera_world = state.cameras.camera_world
render.set_viewport(0, 0, state.window_width, state.window_height)
render.set_view(camera_world.view)
render.set_projection(camera_world.proj)
-- renderiza modelos
--
render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
render.enable_state(render.STATE_CULL_FACE)
render.enable_state(render.STATE_DEPTH_TEST)
render.set_depth_mask(true)
render.draw(predicates.model_pred)
render.set_depth_mask(false)
render.disable_state(render.STATE_DEPTH_TEST)
render.disable_state(render.STATE_CULL_FACE)
-- renderiza o mundo (sprites, tilemaps, partículas etc)
--
render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
render.enable_state(render.STATE_DEPTH_TEST)
render.enable_state(render.STATE_STENCIL_TEST)
render.enable_state(render.STATE_BLEND)
render.draw(predicates.tile)
render.draw(predicates.particle)
render.disable_state(render.STATE_STENCIL_TEST)
render.disable_state(render.STATE_DEPTH_TEST)
-- debug
render.draw_debug3d()
-- renderiza GUI
--
local camera_gui = state.cameras.camera_gui
render.set_view(camera_gui.view)
render.set_projection(camera_gui.proj)
render.enable_state(render.STATE_STENCIL_TEST)
render.draw(predicates.gui, camera_gui.frustum)
render.draw(predicates.text, camera_gui.frustum)
render.disable_state(render.STATE_STENCIL_TEST)
end
Até aqui, este é um script de renderização simples e direto. Ele desenha da mesma maneira em cada frame. Porém, às vezes é desejável introduzir estado no script de renderização e realizar operações diferentes dependendo desse estado. Também pode ser desejável comunicar-se com o script de renderização a partir de outras partes do código do jogo.
on_message() e receber mensagens de outras partes do seu jogo ou app. Um caso comum em que um componente externo envia informações ao script de renderização é a câmera. Um componente de câmera que adquiriu foco de câmera enviará automaticamente sua visualização e projeção ao script de renderização a cada frame. Essa mensagem se chama "set_view_projection":local MSG_CLEAR_COLOR = hash("clear_color")
local MSG_WINDOW_RESIZED = hash("window_resized")
local MSG_SET_VIEW_PROJ = hash("set_view_projection")
function on_message(self, message_id, message)
if message_id == MSG_CLEAR_COLOR then
-- Alguém nos enviou uma nova cor de limpeza a ser usada.
update_clear_color(state, message.color)
elseif message_id == MSG_SET_VIEW_PROJ then
-- O componente de câmera que tem foco de câmera enviará mensagens
-- set_view_projection para o socket @render. Podemos usar as informações
-- da câmera para definir a visualização (e possivelmente a projeção) da renderização.
camera.view = message.view
self.camera_projection = message.projection or vmath.matrix4()
update_camera(camera, state)
end
end
No entanto, qualquer script ou script de GUI pode enviar mensagens ao script de renderização pelo socket especial @render:
-- Altera a cor de limpeza.
msg.post("@render:", "clear_color", { color = vmath.vector4(0.3, 0.4, 0.5, 0) })
Para passar certos recursos da engine para o script de renderização, você pode adicioná-los à tabela Render Resources no arquivo .render atribuído ao projeto:

Usando esses recursos em um script de renderização:
-- "my_material" agora será usado para todas as chamadas de desenho associadas ao predicado
render.enable_material("my_material")
-- tudo que for desenhado pelo predicado acabará em "my_render_target"
render.set_render_target("my_render_target")
render.draw(self.my_full_screen_predicate)
render.set_render_target(render.RENDER_TARGET_DEFAULT)
render.disable_material()
-- vincula a textura de resultado do render target ao que estiver sendo renderizado pelo predicado
render.enable_texture(0, "my_render_target", render.BUFFER_COLOR0_BIT)
render.draw(self.my_tile_predicate)
No momento, o Defold suporta apenas Materials e Render Targets como recursos de renderização referenciados, mas com o tempo mais tipos de recurso serão suportados por esse sistema.
Texturas no Defold são representadas internamente como um handle, que essencialmente equivale a um número que deve identificar de forma única um objeto de textura em qualquer lugar da engine. Isso significa que você pode conectar o mundo de game objects ao mundo de renderização passando esses handles entre o sistema de renderização e um script de game object. Por exemplo, um script pode criar uma textura dinâmica em um script anexado a um game object e enviá-la ao renderizador para ser usada como textura global em um comando de desenho.
Em um arquivo .script:
local my_texture_resource = resource.create_texture("/my_texture.texture", tparams)
-- observação: my_texture_resource é um hash para o caminho do recurso, que não pode ser usado como handle!
local my_texture_handle = resource.get_texture_info(my_texture_resource)
-- my_texture_handle contém informações sobre a textura, como largura, altura e assim por diante
-- ele também contém o handle, que é o que queremos
msg.post("@render:", "set_texture", { handle = my_texture_handle.handle })
Em um arquivo .render_script:
function on_message(self, message_id, message)
if message_id == hash("set_texture") then
self.my_texture = message.handle
end
end
function update(self)
-- vincula a textura personalizada ao estado de desenho
render.enable_texture(0, self.my_texture)
-- desenha..
end
Atualmente não há como alterar para qual textura um recurso deve apontar; você só pode usar handles brutos como este no script de renderização.
A API de script de renderização do Defold traduz operações de renderização para as seguintes APIs gráficas:
| Sistema | API gráfica | Observação |
|---|---|---|
| macOS | OpenGL 3.3 ou Metal | Vulkan via MoltenVK |
| Windows | OpenGL 3.3 ou Vulkan 1.1 | |
| Linux | OpenGL 3.3 ou Vulkan 1.1 | |
| Android | OpenGLES 3.0 ou Vulkan 1.1 | Fallback para OpenGLES 2.0 |
| iOS | OpenGLES 3.0 ou Metal | Vulkan via MoltenVK |
| HTML5 | WebGL 2.0 ou WebGPU | Fallback para WebGL 1.0 |
"set_view_projection""window_resized"local MSG_WINDOW_RESIZED = hash("window_resized")
function on_message(self, message_id, message)
if message_id == MSG_WINDOW_RESIZED then
-- A janela foi redimensionada. message.width e message.height contêm as novas dimensões.
...
end
end
"draw_line"render.draw_debug3d().-- desenha uma linha branca
local p1 = vmath.vector3(0, 0, 0)
local p2 = vmath.vector3(1000, 1000, 0)
local col = vmath.vector4(1, 1, 1, 1)
msg.post("@render:", "draw_line", { start_point = p1, end_point = p2, color = col } )
"draw_text"always_on_top.font. A fonte do sistema tem um material com a tag debug_text e é renderizada com outros textos no script de renderização padrão.-- desenha uma mensagem de texto
local pos = vmath.vector3(500, 500, 0)
msg.post("@render:", "draw_text", { text = "Hello world!", position = pos })
O profiler visual acessível pela mensagem "toggle_profile" enviada ao socket @system não faz parte do renderizador programável. Ele é desenhado separadamente do seu script de renderização.
Uma draw call é o termo usado para descrever o processo de configurar a GPU para desenhar um objeto na tela usando uma textura e um material com configurações adicionais opcionais. Esse processo geralmente consome muitos recursos, e recomenda-se que o número de draw calls seja o menor possível. Você pode medir o número de draw calls e o tempo que elas levam para renderizar usando o profiler integrado.
O Defold tentará agrupar operações de renderização para reduzir o número de draw calls de acordo com um conjunto de regras definidas abaixo. As regras diferem entre componentes GUI e todos os outros tipos de componente.
A renderização é feita com base na ordem z, de baixo para alto. A engine começará ordenando a lista de coisas a desenhar e iterará dos valores z mais baixos para os mais altos. Cada objeto na lista será agrupado na mesma draw call que o objeto anterior se as seguintes condições forem atendidas:
Isso significa que, se dois componentes sprite no mesmo proxy de coleção tiverem valores z adjacentes ou iguais (e, portanto, ficarem próximos na lista ordenada), usarem a mesma textura, material e constantes, eles serão agrupados na mesma draw call.
A renderização dos nós em um componente GUI é feita de cima para baixo na lista de nós. Cada nó da lista será agrupado na mesma draw call que o nó anterior se as seguintes condições forem atendidas:
A renderização de nós é feita por componente. Isso significa que nós de componentes GUI diferentes não serão agrupados em batch.
A capacidade de organizar nós em hierarquias facilita agrupá-los em unidades gerenciáveis. Mas hierarquias podem efetivamente quebrar a renderização em batch se você misturar tipos de nós diferentes. É possível agrupar nós GUI em batch de forma mais eficiente mantendo hierarquias de nós usando camadas de GUI. Você pode ler mais sobre camadas de GUI e como elas afetam draw calls no manual de GUI.