-
Notifications
You must be signed in to change notification settings - Fork 12
3. Rendering & The Orbinaut Shader
Rendering can be complex and confusing, but unlike its predecessor, the Orbinaut Framework 2 aims to simplify the system to its most basic minimum.
A reminder: draw events of c_framework
are the very first draw events to be executed.
Orbinaut uses built-in cameras to simplify the rendering process. Draw events are executed automatically for each existing camera. If you want to prevent anything from being rendered for a specific camera, you only need a few lines at the beginning of your draw event:
// This will prevent executing anything for the Draw Event of the second camera
if view_current == 1
{
exit;
}
Next, the detailed explanation rendering system of c_framework
and how it works.
It all begins with this, initialising a surface for the Orbinaut camera. This is the code you'll come across in the #region Camera (Render)
:
if !surface_exists(view_surface_id[i])
{
view_surface_id[i] = surface_create(_camera.surface_w, _camera.surface_h);
surface_set_target(view_surface_id[i]);
draw_clear_alpha(c_black, 0);
surface_reset_target();
}
This code block initialises a surface associated with the GameMaker camera of the same index.
Note that cameras and surfaces created with camera_new()
function are made a few pixels wider horizontally (specifically, ENGINE_RENDERER_HORIZONTAL_BUFFER
pixels wider) to correctly render distortion effects. During the surface rendering stage, these extra pixels are cropped.
Therefore, the camera will always be positioned at x - ENGINE_RENDERER_HORIZONTAL_BUFFER
, so you MUST use the camera_get_x()
function to get the actual camera position.
GameMaker recommends creating surfaces directly in draw events, but the reason we do this in the End Step
is simple: creating a surface later results in what we call a "missed frame," where the previous frame is duplicated instead.
In the Pre-Draw Event, the back buffer is cleared. This line ensures that the game window background is always filled with solid black, preventing duplication artifacts:
draw_clear(c_black);
Now is where the shader shenanigans begin. Here, we're enabling our main shader, sh_orbinaut
:
if global.gfx_enabled
{
shader_set(sh_orbinaut);
}
The shader remains enabled up until Draw End Event.
sh_orbinaut
performs the following tasks:
- Palette swap & rotation;
- Fade;
- Background parallax.
All three features are toggleable, and for two of them, there are separate functions for easy toggling: draw_toggle_palette()
and draw_toggle_fade()
, respectively.
The shader is working per-sprite, not with the screen. This is the main reason we use a filter for the distortion effect instead of working with layer_script_start()
and layer_script_end()
functions.
The final stage of rendering. The surfaces of all Orbinaut cameras are drawn, as mentioned before, cropped to remove extra pixels from the left and right edges and adjusted for position:
for (var i = 0; i < CAMERA_COUNT; i++)
{
var _camera = camera_get_data(i);
if _camera == noone
{
continue;
}
var _surface = view_surface_id[i];
if surface_exists(_surface)
{
draw_surface_part(_surface, ENGINE_RENDERER_HORIZONTAL_BUFFER, 0, _camera.surface_w - ENGINE_RENDERER_HORIZONTAL_BUFFER * 2, _camera.surface_h, _camera.surface_x, _camera.surface_y);
}
}
As we draw surfaces in a GUI event, rendering occurs on application_surface
. Its resolution is practically the game's but not the window's resolution, as it is resized to global.init_resolution_x
and global.init_resolution_y
dimensions during room initialisation.
Subsequent draw events will not be affected by the shader anymore, but you can still use it. For example, here is the Draw GUI
code from obj_gui_pause
:
shader_set(sh_orbinaut);
draw_toggle_palette(false);
draw_sprite(sprite_index, 0, _x, _y);
if highlight_timer < 8
{
draw_sprite(spr_obj_gui_pause_selection, option_id, _x - 4, _y - 8 + 16 * option_id);
}
draw_toggle_palette(true);
shader_reset();
As you can see, we manually enable the shader (because we need the pause menu to be affected by the fade), disable palette rendering to prevent it from affecting the pause menu, render the pause sprite, then re-enable palette rendering and finally disable the shader.
It's all quite straightforward!