Model-Based Rendering: Models, Renders, and Drawing Operations
Model-Based Rendering: Models, Renders, and Drawing Operations
While Ren'Py is primarily used with two dimensional rectangular images that are common in visual
novels, underneath the hood it has a model-based renderer intended to to take advantage of features
found in modern GPUs. This allows for a number of visual effects that would not otherwise be
possible.
As a warning, this is one of the most advanced features available in Ren'Py. In many cases, it's not
necessary to understand how model-based rendering works behind the scenes - features like
matrixcolor and Live2D support can be used without understanding how Model-Based rendering
works, and more such features will be added to the understanding. This documentation is intended for
very advanced creators, and for developers looking to add to Ren'Py itself.
As of Ren'Py 7.4 (late 2020), Model-Based rendering needs to be enabled to be used. This is done by
setting config.gl2 to True, using:
define config.gl2 = True
As it's expected that model-based rendering will become the only renderer in the near future, the rest of
this documentation is written as if model-based rendering is enabled all the time.
Model-Based Rendering is one of the most advanced features in Ren'Py, and this documentation may
be hard to understand without first looking at the OpenGL, OpenGL ES, GLSL, and GLSL ES manual.
What's more, since there are portions of the models that are passed directly to your GPU drivers, which
may accept erroneous inputs, it's important to check on multiple kinds of hardware.
A Transform creates a model if mesh is true, or if blur is being used. In this case, the children
of the Transform are rendered to textures, with the mesh of the first texture being used for the
mesh associated with the model.
Not every transform creates a Model. Some transforms will simply add shaders and uniforms to a
Render (such as transforms that use blur or alpha). Other transforms simply affect geometry.
Render
A Transform creates a model if its mesh attribute is True. is being used. In this case, the children
of the Render are rendered to textures, with the mesh of the first texture being used for the mesh
associated with the model.
It's expected that Ren'Py will add more ways of creating models in the future.
This registers a shader part. This takes name, and then keyword arguments.
name
A string giving the name of the shader part. Names starting with an underscore or "renpy."
are reserved for Ren'Py.
variables
The variables used by the shader part. These should be listed one per line, a storage
(uniform, attribute, or varying) followed by a type, name, and semicolon. For example:
variables='''
uniform sampler2D tex0;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
'''
vertex_functions
If given, a string containing functions that will be included in the vertex shader.
fragment_functions
If given, a string containing functions that will be included in the fragment shader.
Other keyword arguments should start with vertex_ or fragment_, and end with an integer
priority. So "fragment_200" or "vertex_300". These give text that's placed in the appropriate
shader at the given priority, with lower priority numbers inserted before higher priority numbers.
renpy.register_shader("example.gradient", variables="""
uniform vec4 u_gradient_left;
uniform vec4 u_gradient_right;
uniform vec2 u_model_size;
varying float v_gradient_done;
attribute vec4 a_position;
""", vertex_300="""
v_gradient_done = a_position.x / u_model_size.x;
""", fragment_300="""
gl_FragColor *= mix(u_gradient_left, u_gradient_right, v_gradient_done);
""")
If true, source code for the GLSL shader programs will be written to log.txt on start.
mesh
Type: None or True or tuple
Default: None
If not None, this Transform will be rendered as a model. This means:
• A mesh will be created. If this is a 2-component tuple, it's taken as the number of points in
the mesh, in the x and y directions. (Eacn dimension must be at least 2.) If True, the mesh
is taken from the child.
• The child of this transform will be rendered to a texture.
• The renpy.texture shader will be added.
mesh_pad
Type: None or tuple
Default: None
If not None, this can either be a 2 or 4 component tuple. If mesh is true and this is given, this
applies padding to the size of the textues applied to the the textures used by the mesh. A two
component tuple applies padding to the right and bottom, while a four component tuple applies
padding to the left, top, right, and bottom.
This can be used, in conjunction with the pixel_perfect property, to render text into a mesh. In
Ren'Py, text is rendered at the screen resoltution, which might overflow the boundaries of the
texture that will be applied to the mesh. Adding a few pixels of padding makes the texture bigger,
which will display all pixels. For example:
transform adjust_text:
mesh True
mesh_pad (10, 0)
gl_pixel_perfect True
shader "shaders.adjust_text"
will ensure that the texture passed to the shader contains all of the pixels of the text.
shader
Type: None or str or list of str
Default: None
If not None, a shader part name or list of shader part names that will be applied to the this Render
(if a Model is created) or the Models reached through this Render.
blend
Type: None or str
Default: None
if not None, this should be a string. This string is looked up in config.gl_blend_func to
get the value for the gl_blend_func property. It's used to use alternate blend modes.
The default blend modes this supports are "normal", "add", "multiply", "min", and "max".
In addition, uniforms that start with u_ and not u_renpy are made available as Transform properties. GL
properties are made available as transform properties starting with gl_. For example, the color_mask
property is made available as gl_color_mask.
Blend Functions
define config.gl_blend_func = { ... }
A dictionaryt used to map a blend mode name to a blend function. The blend modes are suppled
to the blend func property, given below.
float u_lod_bias
The level of detail bias to apply to texture lookups. This may be set in a Transform. The default
value, taken from config.gl_lod_bias and defaulting to -0.5, biases Ren'Py to always pick
the next bigger level and scale it down.
mat4 u_transform
The transform used to project virtual pixels to the OpenGL viewport.
float u_time
The time of the frame. The epoch is undefined, so it's best to treat this as a number that increases
by one second a second. The time is modulo 86400, so it will reset to 0.0 once a day.
vec4 u_random
Four random numbers between 0.0 and 1.0 that are (with incredibly high likelyhood) different
from frame to frame.
sampler2D tex0, sampler2D tex1, sampler2D tex2
If textures are available, the corresponding samplers are placed in this variable.
vec2 res0, vec2 res1, vec2 res2
If textures are available, the size of the textures are placed in these variables. When the texture is
loaded from disk, this is the size of the image file. After a render to texture, it's the number of
drawable pixels the rendered texture covered.
GL Properties
GL properties change the global state of OpenGL, or the Model-Based renderer. These properties can
be used with a Transform, or with the Render.add_property() function.
gl_anisotropic
If supplied, this determines if the textures applied to a mesh are created with anisotropy.
Anisotropy is a feature that causes multiple texels (texture pixels) to be sampled when a texture is
zoomed by a different amount in X and Y.
This defaults to true. Ren'Py sets this to False for certain effects, like the Pixellate transition.
gl_blend_func
If present, this is expected to be a six-component tuple, which is used to set the equation used to
blend the pixel being drawn with the pixel it is being drawn to, and the parameters to that
equation.
Specifically, this should be (rgb_equation, src_rgb, dst_rgb, alpha_equation, src_alpha,
dst_alpha). These will be used to call:
glBlendEquationSeparate(rgb_equation, alpha_equation)
glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha)
Please see the OpenGL documentation for what these functions do. OpenGL constants can be
imported from renpy.uguu:
init python:
from renpy.uguu import GL_ONE, GL_ONE_MINUS_SRC_ALPHA
If true, this will clear the depth buffer, and then enable depth rendering for this displayable and
the children of this displayable.
Note that drawing any pixel, even transparent pixels, will update the depth buffer. As a result,
using this with images that have transparency may lead to unexpected problems. (As an
alternative, consider the zorder and behind clauses of the show statement.)
gl_mipmap
If supplied, this determines if the textures supplied to a mesh are created with mipmaps. This
defaults to true.
gl_pixel_perfect
This only makes sense to set when a mesh is being created. When True, Ren'Py will move the
mesh such that the first vertex is aligned with a pixel on the screen. This is mostly used in
conjunction with text, to ensure that the text remains sharp.
gl_texture_wrap
When supplied, this determines how the textures applied to a mesh are wrapped. This expects a
2-component tuple, where the first component is used to set GL_TEXTURE_WRAP_S and the
second component is used to set GL_TEXTURE_WRAP_T, which conventionally are the X and
Y axes of the created textyure.
The values should be OpenGL constants imported from renpy.uguu:
init python:
from renpy.uguu import GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT, GL_REPEAT
Model Displayable
The Model displayable acts as a factory to created models for use with the model-based renderer.
class Model(size=None, **properties)
This is a displayable that causes Ren'Py to create a 2D or 3D model for use with the model-based
renderer, that will be drawn in a single operation with the shaders given here, or selected by an
enclosing Transform or Displayable.
size
If not None, this should be a width, height tuple, that's used to give the size of the Model. If
not given, the model is the size of the area provided to it. The fit parameter to a texture
takes precedence.
If no mesh method is called, a mesh that sets a_position and a_tex_coord to match the way
Ren'Py loads textures is created if at least one texture is supplied. Otherwise, a mesh that only
sets a_position is used.
All methods on this calls return the displayable the method is called on, making it possible to
chain calls.
child(displayable, fit=False)
This is the same as the texture method, except that the focus and main parameters are set to
true.
grid_mesh(width, height)
Creates a mesh that consists of a width x height grid of evenly spaced points, connecting
each point to the closest points vertically and horizontally, and dividing each rectangle in
the grid so created into triangles.
width, height
The number of points in the horizontal vertical directions, a integer that is at least 2.
properties(name, value)
name
A string giving the name of the GL property, including the "gl_" prefix.
value
The value of the gl property.
shader(shader)
shader
A string given the name of a shader to use with this model.
texture(displayable, focus=False, main=False, fit=False)
Add a texture to this model, by rendering the given displayable. The first texture added will
be tex0, the second tex1, a and so on.
focus
If true, focus events are passed to the displayable. It's assumed that coordinate
relative to the model map 1:1 with coordinates relative to the displayable.
main
If true, this is marked as a main child of this displayable, which allows it to be
inspected using the displayable inspector.
fit
If true, the Model is given the size of the displayable. This may only be true for one
texture.
uniform(name, value)
name
A string giving the name of the uniform to set, including the "u_" prefix.
value
The value of the uniform. Either a float, a 2, 3, or 4 element tuple of floats, or a
Matrix.
u_renpy_dissolve 0.0
linear delay u_renpy_dissolve 1.0
Vertex shader:
gl_Position = u_transform * a_position;
Vertex shader:
v_tex_coord = a_tex_coord;
Fragment shader:
gl_FragColor = vec4(0.);
float renpy_blur_norm = 0.;
gl_FragColor /= renpy_blur_norm;
Vertex shader:
v_tex_coord = a_tex_coord;
Fragment shader:
vec4 color0 = texture2D(tex0, v_tex_coord.st, u_lod_bias);
vec4 color1 = texture2D(tex1, v_tex_coord.st, u_lod_bias);
Fragment shader:
vec4 color0 = texture2D(tex0, v_tex_coord.st, u_lod_bias);
vec4 color1 = texture2D(tex1, v_tex_coord.st, u_lod_bias);
vec4 color2 = texture2D(tex2, v_tex_coord.st, u_lod_bias);
Fragment shader:
gl_FragColor = u_renpy_solid_color;
Vertex shader:
v_tex_coord = a_tex_coord;
Fragment shader:
gl_FragColor = texture2D(tex0, v_tex_coord.xy, u_lod_bias);
Fragment shader:
gl_FragColor = u_renpy_matrixcolor * gl_FragColor;
Fragment shader:
gl_FragColor = gl_FragColor * vec4(u_renpy_alpha, u_renpy_alpha, u_renpy_alpha,
u_renpy_alpha * u_renpy_over);