Read this manual in English

Rendering

엔진에 의해 화면에 나타나는 모든 오브젝트(sprites, models, tiles, particles, GUI nodes)는 렌더링 파이프라인에 의해 그려집니다. 이 메뉴얼은 파이프 라인이 어떻게 동작하며 이것을 어떻게 프로그래밍 하면 되는지 설명합니다.

기본적으로 Defold는 모든 2D 오브젝트를 특정 블렌딩과 적당한 Z depth로 적절하게 비트맵을 그려주므로 정렬(ordering)과 블렌딩(blending) 이상의 렌더링에 대하여 고민할 필요가 없습니다. 이 파이프라인은 대부분의 2D 게임에서 잘 동작하지만, 또 다른 특별한 요구사항이 있을 수 있습니다. Defold에서는 이런 경우에 맞춤형 렌더링 파이프라인을 작성할 수 있습니다.

The default renderer

렌더링 파이프라인의 핵심은 렌더 스크립트(render script)입니다. 이 파일은 init(), update(), on_message() 함수를 사용하는 보통의 Lua 스크립트이며 OpenGL 렌더링 API 와 소통하는데 주로 사용됩니다. 프로젝트의 “Builtins” 폴더 안에서 기본 렌더 오브젝트(“default.render”)와 기본 렌더 스크립트(“default.render_script”)를 찾을 수 있습니다. 렌더 오브젝트는 현재의 렌더 스크립트에 대한 참조를 포함하고 있습니다.

Defold는 휴대장치에서 OpenGL ES 2.0 기반으로 렌더링 됩니다. 데스크탑에서는 보통의 Open GL을 사용하므로 OpenGL ES 2.0에서 지원하지 않는 기능을 사용하여 쉐이더를 작성하는 것이 가능하지만 이는 데스크탑과 휴대장치간의 상호 호환을 깨트릴 수 있습니다.

Builtin render

Default render

커스텀 렌더러를 설정하는 방법은 아래와 같습니다.

  1. “default.render” 파일과 “default.render_script” 파일을 복사합니다.
  2. 복사한 파일들을 당신의 프로젝트의 아무데나 (“render” 폴더 같은 곳)에 붙여 넣기 합니다.
  3. 복사한 “default.render” 파일을 연 후 (이름 바꿔도 됨) script 속성을 변경하여 복사한 렌더 스크립트 파일을 참조합니다.
  4. game.project 설정 파일에서 Bootstrap 항목 아래의 render 속성을 변경하여 위에서 복사한 “default.render” 오브젝트를 참조합니다.

물론 그냥 처음부터 렌더 스크립트 파일을 새로 생성해도 되지만, Defold와 OpenGL ES 렌더링을 처음 다뤄보는 사용자라면 기존 스크립트에서 복사해서 편집하는 방식이 좋은 접근법입니다.

렌더 스크립트는 게임의 라이프사이클 내에서 특별한 위치에 있습니다. 자세한 내용은 Application lifecycle 문서에서 찾을 수 있습니다.

Render predicates

render predicates(렌더 술어 or 조건자)는 오브젝트의 그리기 순서(draw order)를 제어할 수 있습니다. predicate는 메터리얼 태그의 선택을 기반으로 무엇을 그릴 것인지 선언합니다. 화면에 그려지는 각 오브젝트는 메터리얼을 포함하고 있으며, 오브젝트를 어떻게 정확히 화면에 그릴지, 어떤 쉐이더 프로그램을 실행할지를 제어합니다. 메터리얼에서는, 메터리얼과 연관된 한 개 이상의 태그를 지정할 수 있습니다. 이것은 게임을 빌드할 때 비트 필드(bit field)로 컴파일 되지만, 에디터상에서는 보통의 텍스트 태그로 나타납니다. 렌더 스크립트에서 한두개 렌더 predicate를 만들고 이 predicate가 속할 태그를 지정해 보세요. 마지막으로 predicate를 그릴 때에는, predicate에 지정된 목록과 일치하는 태그를 포함한 메터리얼이 있는 각 오브젝트가 그려집니다. 메터리얼에 대한 더 자세한 설명은 Material 문서에서 찾을 수 있습니다.

Render predicate

The render script

렌더 스크립트가 어떻게 동작하는지 더 이해하기 위해서 기본 내장된 스크립트를 조금 수정한 버전으로 자세히 살펴 보도록 하겠습니다. init() 이 시작되면 predicate, view, clear color를 설정하는데, 이 변수들은 실제 렌더링 중에 사용됩니다.

init()

function init(self)
    -- render predicate를 정의합니다. 각 predicate는 자기 스스로 드로우되고 이 드로우들 사이에서 OpenGL의 상태를 변경 할 수 있습니다.
    self.predicates = create_predicates("tile", "gui", "text", "particle", "model")

    -- Create and fill data tables will be used in 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

커스텀한 프로젝트 셋팅을 어떻게 정의하고 사용할지에 대한 정보는 Project settings 문서에서 찾을 수 있습니다. 게임 카메라가 동작하는 방법에 대해 알고 싶다면 Camera 문서를 참고하세요.

update()

update() 함수는 매 프레임 마다 호출됩니다. 이 함수는 OpenGL ES API(OpenGL Embedded Systems API)를 사용하여 실제 드로잉을 처리합니다. update() 함수에서 무슨일이 벌어지는지 이해하기 위해서는 OpenGL이 동작하는 방법을 이해해야만 합니다. OpenGL ES에는 훌륭한 리소스가 많이 있으며 https://www.khronos.org/opengles/ 공식 사이트가 이해를 위한 좋은 출발점이 될 수 있습니다.

다음 예제에는 3D 모델을 올바르게 그리는데 필요한 설정을 하는 내장 스크립트에 대한 주요사항이 포함되어 있습니다. 위에서 보았듯이, self.predicates.model predicate가 구성되고 다른 곳에서 이 predicate에 해당하는 메터리얼이 정의되어 3D 모델 컴포넌트에 반영되었습니다. update() 코드는 이 predicate에 대한 특정한 처리를 필요로 합니다.

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
    -- clear screen buffers
    --
    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)


    -- render models
    --
    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)

     -- render world (sprites, tilemaps, particles 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()

    -- render 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

자, 이제 단순하고 직관적인 렌더 스크립트가 완성되었습니다. 이 렌더 스크립트는 매 프레임마다 동일한 방식으로 화면을 그리지만, 만약 렌더 상태(render states)를 도입하여 다른 곳에서 렌더링 파이프라인(render pipeline)을 제어하려고 한다면 어떻게 해야 할까요?

on_message()

이 렌더 스크립트 또한 Defold의 메세지 전달 세상에서는 보통의 시민들과 다를 바 없습니다. 그냥 렌더 스크립트에 on_message() 함수를 정의하는 것으로 게임의 다른 파트에서 렌더 스크립트의 동작에 영향을 주도록 하면 됩니다. 렌더 스크립트에 정보를 보내는 외부 오브젝트에 대한 예제로는 카메라 컴포넌트가 있습니다(자세한 내용은 Camera 문서 참고). 카메라 포커스가 있는 카메라 컴포넌트는 자동적으로 렌더 스크립트에 view와 projection을 보내고 있습니다. 반면 일반 스크립트에서 렌더 스크립트와 통신하려면 특수한 소켓인 @render를 사용하면 됩니다.

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
        -- 어디선가 클리어 컬러(clear color) 메세지를 보냄
        self.clear_color = message.color
    elseif message_id == MSG_SET_VIEW_PROJ then
        -- 카메라 포커스를 가진 카메라 컴포넌트가 @render 소켓으로 set_view_projection 메세지를 보냄. 렌더링의 view(그리고 사용가능한 projection)를 설정하기 위한 카메라 정보를 사용할 수 있음
        camera.view = message.view
        self.camera_projection = message.projection or vmath.matrix4()
        update_camera(camera, state)
    end
end

위의 렌더 스크립트와 on_message() 함수를 사용하면, 아래처럼 메세지를 보내서 clear color를 멋진 다크 스틸 블루(dark steel blue) 색깔로 바꿀 수 있습니다.

msg.post("@render:", "clear_color", { color = vmath.vector4(0.3, 0.4, 0.5, 0) })

System messages

@render 소켓은 몇 가지 내장 메세지를 가지고 있습니다. 우선, 윈도우 사이즈가 변경되었을 경우 엔진이 보내주는 window_resized 메세지가 있습니다. 데스크탑에서는 게임 창 크기가 조절될 경우, 모바일에서는 orientation이 바뀔 경우 이 메세지가 전달됩니다.

local MSG_WINDOW_RESIZED =      hash("window_resized")

function on_message(self, message_id, message)
  if message_id == MSG_WINDOW_RESIZED then
    -- 윈도우가 리사이징 됨. 새 크기가 message.width와 message.height에 포함됨
    ...
  end
end

또한 텍스트와 선을 그릴 수 있는 메세지도 있습니다.

-- (1000, 1000)좌표까지 흰색 선을 그림
msg.post("@render:", "draw_line", { start_point = vmath.vector3(0, 0, 0), end_point = vmath.vector3(1000, 1000, 0), color = vmath.vector4(1, 1, 1, 1) } )

-- 500, 500 좌표에 텍스트 메세지를 그림
msg.post("@render:", "draw_text", { text = "Hello world!", position = vmath.vector3(500, 500, 0) } )

이 메세지들은 디버깅 정보를 그리기 위해 만들어졌습니다. 디버그 메세지를 출력하거나 ray_casts나 vector나 프로그래밍을 위한 개발 통계 따위를 쉽게 시각화 하는데 유용하며, 아래의 설명처럼 렌더 스크립트와 관련이 있습니다.

  • draw_line 메세지를 통해 씬(scene)에 추가된 모든 선(line)들은 render.draw_debug3d() 호출에 의해 그려집니다.

  • draw_text 메세지를 통해 씬(scene)에 추가된 모든 텍스트(text)들은 내장된 always_on_top.font 로 그려집니다. 시스템 폰트는 “text” 태그가 있는 메터리얼을 가지므로 위의 렌더 스크립트 내용중에서 debug_text predicate에 그려집니다.

toggle_profile 메세지를 @system 소켓으로 보내서 접근 할 수 있는 비주얼 프로파일러(visual profiler)는 스크립트로 처리 가능한 렌더러와는 다른 파트이므로 당신의 렌더 스크립트와는 분리되어 그려집니다.