Text Shaders
Ren'Py contains a text shader system that makes it possible to control how Ren'Py displays text. When enabled, the text shader system uses Model-Based Rendering to render two triangles for each unicode character. Shader parts, either specified from the creator or generated by Ren'Py, are applied to the model, and can change how text is displayed.
The text shader documentation is in three parts:
How to use text shaders
What text shaders are included in Ren'Py
How to create new text shaders
Note that while text shaders are intended to be easily used by game creators, making your own text shaders requires some knowledge of GLSL, the OpenGL Shading Language, as well as Model-Based Rendering, and hence is more advanced than most Ren'Py features.
Using Text Shaders
There are three ways to use text shaders:
Default Text Shader The first is to set the default
text shader, using config.default_textshader
.
define config.default_textshader = "wave:10"
When set this way, the text shader will be used for all text that does not specify a text shader. It will also be combined with text shaders that include the default text shader, which is most of them.
Generally, the default textshader should take care of slow text and shouldn't add more complicated effects.
Styles The second way to use text shaders is to set the textshader
style property, either directly or in one of the many ways provided by Ren'Py to
set styles.
style default:
textshader "dissolve"
define goldfinger = Character("Goldfinger", what_textshader="linetexture:gold.png")
screen purchase():
vbox:
text "Buy the DLC for more!" textshader "wave"
textbutton "Buy Now":
text_textshader "wave" action iap.Purchase("dlc")
Text Tags The third way to use text shaders is to use the appropriate text tag
to change the look of a portion of text.
"What's this? A letter from {shader=zoom}out of nowhere{/shader}?"
Note A text block should either use text shaders or not - mixing is not
supported. For example, you should set config.default_textshader
or textshader
style property if you use the text tag like above.
Specifying Text Shaders
Text shaders are specified as strings like:
"dissolve"
"jitter:u__jitter=1.0, 3.0"
"texture:gold.png"
The first part of the string, before the first colon, is the name of the text shader. The rest of the string is a series of uniforms that are passed to the shader, separated by colons. (Uniforms are parameters that are passed to the shader, that can be used to control how the shader works.)
Uniforms can be specified by name followed by =, or the name can be omitted to set each uniform in order. (Omitting the name is not supported in Ren'Py 7.) While internally all uniforms begin with u_, the u_ can be omitted for brevity.
The value of a uniform can be:
Between 1 and 4 numbers, separated by commas. These can be used with the the float, vec2, vec3, or vec4 types.
A color, beginning with #. (For example, #f00 or #ff0000 for red.) This creates a vec4 corresponding to that color. This color will be premultiplied by its alpha channel.
A displayable that will be used as a texture. This creates a sampler2D that can be used to sample the texture.
Uniform values can't be expressions or access variables, though it is possible to use text interpolation to create a string that can be evaluated as a textshader tag or its parameter.
Finally, text shaders can be combined with each other using the | operator. For example:
"jitter:1.0, 3.0|wave"
This will apply both the jitter and wave shaders to the text. This only works if the shaders are compatible with each other, and do not use the same uniforms (or use the uniform in a way that is compatible with each other, in which case it takes the value from the last shader in the list).
Unless a textshader has include_default set to False, the default textshader will be combined with the textshader specified in the style or tag.
Text Shader Callbacks
The config.textshader_callbacks
variable can be used to set a callback that
is run when a text shader is applied. This can be used to customize the text
shader based on a preference.
default persistent.dissolve_text = True
init python:
def get_default_textshader():
if persistent.dissolve_text:
return "dissolve"
else:
return "typewriter"
define config.default_textshader = "default"
define config.textshader_callbacks["default"] = get_default_textshader
Built-In Text Shaders
Ren'Py includes a number of built-in text shaders. These are:
- dissolve
The dissolve text shader handles text by dissolving it in slowly, with the start dissolving in first, and the end dissolving in last.
- u__duration = 10.0
The number of characters that will be changing alpha at a time. If set to 0, the wave will move across the text one pixel at a time.
Using this shader will prevent the default text shader from being used.
- flip
The flip shader flips slow text by flipping the text horizontally, with the start flipping in first, and the end flipping in last.
- u__duration = 10.0
The number of characters that will be changing alpha at a time. If set to 0, the characters will instantly flip.
Using this shader will prevent the default text shader from being used.
- jitter
The jitter text shader moves the text to random positions relative to where it would be normally drawn. The position changes once per frame.
- u__jitter = (3.0, 3.0)
The amount of jitter to apply to the text, in pixels.
- linetexture
Multiplies the text with a texture, one line at a time. The texture is aligned with the left side of the text. The vertical center of the texture is aligned with the baseline of the text - this meas that most of the lower half of the texture will not be visible.
- u__texture = ...
The texture to multiply the text by.
- u__scale = (1.0, 1.0)
A factor to scale the texture by. For example (1.0, 0.5) will make the texture half as tall as it otherwise would be.
- offset
The offset text shader moves the text by a fixed amount.
- u__offset = (0.0, 0.0)
The amount to move the text by, in virtual pixels.
- slowalpha
The slowalpha shader is intended to be used with another slow text shader, like typewriter or dissolve. It causes the text that has yet to be revealed to be drawn with an alpha value of u__alpha = 0.2, rather than being invisible.
- u__alpha = 0.2
The alpha value of the text that has not been revealed yet.
- texture
The texture text shader multiplies the text with the colors from a texture. This not done to outlines or offset text. The texture is aligned with the top left of the text.
- u__texture = ...
The texture to multiply the text by.
- typewriter
The typewriter text shader handles slow text by making the text appear one character at a time, as if it were being typed out by a typewriter.
Using this shader will prevent the default text shader from being used.
- wave
The wave text shader makes the text bounce up and down in a wave.
- u__amplitude = 5.0
The number of pixels up and down the text will move.
- u__frequency = 2.0
The number of waves per second.
- u__wavelength = 20.0
The number of characters between peaks in the wave.
- zoom
The zoom text shader handles slow text to cause it to zoom in from an initial size of u__zoom = 0.0 to full size.
- u__zoom = 0.0
The initial amount of zoom to apply to a character when it first starts showing.
- u__duration = 10.0
The number of characters that will be changing alpha at a time. If set to 0, the characters will instantly flip.
Using this shader will prevent the default text shader from being used.
Creating Text Shaders
Text shaders are GLSL programs that are run on the GPU. These shaders are registered using the renpy.register_text_shader function.
- renpy.register_textshader(name, shaders=(), extra_slow_time=0.0, extra_slow_duration=0.0, redraw=None, redraw_when_slow=0.0, include_default=True, adjust_function=None, doc=None, **kwargs)
This creates a textshader and registers it with the name name.
This function takes the following arguments:
- name
This is the name of the textshader. It's also used to register a shader part named textshader.`name`.
- shaders
Shader parts to apply to the text. This can be a string, or a list or tuple of strings. This should be a shader part registered with
renpy.register_shader()
, or this function. If a shader part begins with '-', then it is removed from the list of shader parts. (For example, '-textshader.typewriter' will remove that part.)Note that the shader parts registered with this function are prefixed with textshader., which needs to be supplied when used with this function.
- extra_slow_time
Extra time to add to the slow time effect beyond what Ren'Py will compute from the current characters per second. This is useful for shaders that might take more time to transition a character than the default time. If True, the shader is always updated.
- extra_slow_duration
Added to extra_slow_time, but this is multiplied by the time per character to get the extra time to add to the slow time effect. (Time per character is 1 / characters per second.)
- redraw
The amount in time in seconds before the text is redrawn, after all slow text has been show and extra_slow_time has passed.
- redraw_when_slow
The amount of time in seconds before the text is redrawn when showing slow text.
- include_default
If True, when this textshader is used directly, it will be combined with
config.default_textshader
.- adjust_function
A function that is called with an object and the uniforms being passed to the text shader as keyworkd arguments. This function can set the extra_slow_time, extra_slow_duration, redraw, and redraw_when_slow fields of the object
- doc
A string containing documetation information. This is mostly intended for Ren'Py's documentation system.
Keyword argument beginning with
u_
are passed as uniforms to the shader, with strings beginning with#
being interpreted as colors. Most uniforms should begin withu__
, using shader local variables to prevent conflicts with other shaders.A keyword argument named variables and all keyword arguments that begin with fragment_ or vertex_ are passed to
renpy.register_shader()
, which registers the shader part.
Variables in Text Shaders
In additions to the uniforms you provided to the text shader (generally beginning with u__
),
Ren'Py makes the following variables available to text shaders. To use a variable in a text shader,
it needs to be declared in the variables argument to renpy.register_text_shader.
In addition to these, the model uniforms and attributes are available, with a_position, a_tex_coord, u_time and u_random being particularly useful.
Uniforms
float u_text_depth
The depth of the text from the top. The topmost text has a depth of 0.0, the first outline or shadow has a depth of 1.0, the second outline or shadow has a depth of 2.0, and so on.
float u_text_main
If this is 1.0, the text is the main text. If this is 0.0, the text is the outline or shadow of the main text.
float u_text_max_depth
The maximum value of u_text_depth. This is the number of outlines and shadows that will be drawn. When u_text_depth is equal to this value, the texct is the last outline or shadow, which may be useful for drawing backgrounds.
vec2 u_text_offset
The offset of the text from the center of the character. This is in drawable pixels in x, y order.
float u_text_outline
The width of the outline around the text. This is in drawable pixels, and is the distance from the edge of the text to the edge of the outline.
float u_text_slow_duration
The duration of a single slow character, when showing slow text. 0.0 if not showing slow text.
float u_text_slow_time
The time in seconds since the start of the slow text effect. This will only increase until the end of slow text, when it will max out. If the user clicks to terminate slow text, this will max out. It should only be used for slow text - use
float u_text_to_drawable
The ratio of virtual pixels to drawable pixels. This is used to convert from virtual pixels to drawable pixels.
float u_text_to_virtual
The ratio of drawable pixels to virtual pixels. This is used to convert from drawable pixels to virtual pixels.
sampler2D tex0
This texture contains the rendered text at the current depth.
vec2 res0
The resolution of tex0, in drawable pixels.
Attributes
When drawing text, each vertex corresponds to a single glyph. Multiple glyphs may have vertices that share locations, but these are passed to the shader as different vertices.
float a_text_ascent
The ascent of the current glyph's font above the baseline, in drawable pixels.
vec2 a_text_center
The position of the center of the glyphs's baseline, in drawable pixels. This is not the center of the rectangle, it's a point on the baseline and around the center of the character.
float a_text_descent
The descent of the current glyph below the baseline, in drawable pixels.
float a_text_index
The index of the glyph being drawn. This is 0 for the first vertex and goes up by one for each vertex.
vec2 a_text_min_time
The minimum time at which any vertex of the glyph should be shown. When showing from left-to-right, this is the time the leftmost vertices should be shown. When the text is meant to be shown instantly but
u_text_slow_duration
is not 0.0, this will be -3600.0.vec2 a_text_max_time
The maximum time at which any vertex of the glyph should be shown. When showing from left-to-right, this is the time the rightmost vertices should be shown. When the text is meant to be shown instantly but
u_text_slow_duration
is not 0.0, this will be -3600.0.float a_text_time
The time at which this vertex should be shown. When the text is meant to be shown instantly but
u_text_slow_duration
is not 0.0, this will be -3600.0.vec4 a_text_pos_rect
The rectangle containing the glyph, in drawable pixels. This is a vec4 with the x, y, width, and height of the rectangle, in drawable pixels. This can be converted to texture coordinates by dividing it by
res0
.
Pseudo-Glyphs
When drawing outlined text, Ren'Py will creates pseudo-glyphs that cover the start and end of each line. If a line is blank, one pseudo-glyph is created covering the whole line. These pseudo-glyphs are necessary to cover areas where outlines may extend into a line above or below the current line.
Example
This is an example of a text shader that spins text when shown.
init python:
def adjust_extra_slow_time(ts, u__delay, **kwargs):
"""
Adjusts a text shader's extra slow time to support the spinning text shader.
"""
ts.extra_slow_time = u__delay
renpy.register_textshader(
"spin",
adjust_function = adjust_extra_slow_time,
variables = """
uniform float u__delay;
uniform float u__offset;
uniform float u_text_slow_time;
attribute vec2 a_text_center;
attribute float a_text_min_time;
""",
vertex_50 = """
float l__angle = clamp((u_text_slow_time - a_text_min_time) / u__delay, 0.0, 1.0) * 2.0 * 3.1415926536;
float l__sin = sin(l__angle);
float l__cos = cos(l__angle);
gl_Position.y -= u__offset;
gl_Position.xy -= a_text_center;
gl_Position = vec4(
gl_Position.x * l__cos - gl_Position.y * l__sin,
gl_Position.x * l__sin + gl_Position.y * l__cos,
gl_Position.z,
gl_Position.w
);
gl_Position.xy += a_text_center;
gl_Position.y += u__offset;
""",
u__delay = 1.0,
u__offset = 0,
)
It can be used witt the following script:
define config.default_textshader = "typewriter"
label start:
"This is a test of the {shader=spin:0.5:-5}spin{/shader} text shader."