尽管Ren’Py在视觉小说中主要使用二维矩形图形,但为了利用各类主流GPU的功能特性其实Ren’Py内置了一个基于模型的渲染器。 使用该渲染器可以实现很多视觉特效。
事先预警,该渲染器是Ren’Py最新添加的功能。很多情况下,不需要理解基于模型渲染器在后台的工作原理,就可以使用
新增的特性,比如 matrixcolor 和Live2D的支持。Ren’Py后续也会添加类似的功能。
本页文档主要面向最前沿的创作者,以及尝试在Ren’Py中添加新功能的开发人员。
在Ren’Py 7.4中,基于模型的渲染器需要修改设置才能启用。只需要将 config.gl2 设置为True
define config.gl2 = True
config.gl2 = False link若此项为True,Ren’Py将默认使用基于模型的渲染器。
未来可能Ren’Py可能会将基于模型渲染器作为唯一的渲染器。本页文档的后续内容都基于该渲染器启用的情况下展开。
基于模型渲染器是Ren’Py中最新的功能特性,如果事先没有看过OpenGL、OpenGL ES、GLSL和GLSL ES手册的话,理解本页文档可能十分困难。 此外,不同图形处理单元驱动对输入模型数据的要求可能略有不同,需要事先检查硬件信息。
Ren’Py绘制到屏幕上的内容,本质上是一个模型。该模型包含以下内容:
Ren’Py通常会在屏幕上同时绘制好几样东西,并创建一棵 Render 对象树。
树上的Render对象的子对象可能是模型或其他Render对象。(后面会提到,Render对象也可以转为模型。)
一个Render对象包含:
Matrix 对象,用于描述各子对象变换到三维空间的方法。Ren’Py使用深度优先算法遍历Render对象树,遍历搜索到模型对象才绘制画面。 执行遍历时,Ren’Py会更新一个矩阵,该矩阵对模型的位置,分割多边形,着色器、uniform变量和GL特性列表进行转换。 当遍历的某一步中搜索到模型对象时,图形处理单元上对应的着色器程序将激活,并传入(矩阵内)所有信息,最后执行绘图操作。
Ren’Py在常规操作中将自动创建模型对象。 理解模型创建细节的主要用处是,主动使用绘图操作生成模型对象以及直接在模型对象上的应用着色器。
Solid()u_renpy_solid_color uniform变量中。Dissolve(), ImageDissolve(), AlphaDissolve(), Pixellate(), AlphaMask(), Flatten()Transform() 和 ATLTransform对象在 mesh 为True或 blur 特性时创建一个模型对象。
模型创建后,Transform对象的所有子对象都将渲染为纹理,第一个纹理的网格作为整个模型的网格。
并非所有的Transform都会创建模型对象。一些Transform值只是在渲染器中简单添加着色器和uniform变量(比如使用 blur 和 alpha 的Transform)。
其他Transform只影响几何体(geometry)。
Rendermesh 属性(attribute)为True时,将创建模型对象。
这种情况下,Render对象的所有子对象将被渲染为纹理,第一个纹理的网格作为整个模型的网格。未来Ren’Py将添加更多创建模型的方法。
Ren’Py生成着色器程序的第一步是识别着色器名称列表。该列表包含 “renpy.geometry”,即从Render对象获取的着色器列表,以及模型对象绘制过程中用到的其他着色器。
接着所有着色器程序将被复制一份。以“-”开头的着色器将会从复制后的列表中删除,以及同名但开头不是“-”的着色器也删除。 (名为“-renpy.geometry”的着色器会导致自身和“renpy.geometry”都被删除)
接着,Ren’Py将根据列表中的着色器名,检索变量、函数、顶点着色器(vertex shade)和片元(fragment shader)列表, 并按优先级数值从小到大的顺序依次生成着色器源码。优先级数值定义在顶点着色器和片元着色器中。
Ren’Py会将使用过的所有着色器组合缓存在 game/cache/shaders.txt 文件中,并在启动时加载这个文件。 如果使用着色器方面有比较大改动,就需要编辑清空或删除这个文件。这样就可以重新生成有效数据。
通过调用 renpy.register_shader 函数可以基于GLSL规范创建新的着色器。
着色器名的格式必须是“命名空间.着色器名称”,比如“mygame.recolor”和“mylibrary.warp”。 Ren’Py已经占用了“renpy.”和“live2d.”两个命名空间,所有以下划线“_”开头的命名空间也是预留的不可使用。
renpy.register_shader(name, **kwargs) linkThis registers a shader part. This takes name, and then keyword arguments. 该函数注册一个着色器名。入参 name,其他关键词入参如下:
着色器使用的各个变量。每行一个变量,存储类型(uniform、attribute或varying)后面跟变量类型、变量名称,结尾用分号。举例:
variables='''
uniform sampler2D tex0;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
'''
着色器函数相关的两个关键词入参应该以 vertex_ 或 fragment_ 开头,结尾带一个整数表示优先级,比如“fragment_200”和“vertex_300”。
这些优先级数值会决定着色器的应用方式,优先级数值低的函数会插入到优先级数值高的函数前面执行。
Ren’Py只支持一下变量类型:
Matrix 类)uniform变量开头必须为 u_,attribute变量开头必须为 a_,varying变量开头必须为 v_。 以 u_renpy_、 a_renpy 和 v_renpy 开头的变量都是Ren’Py预留变量名,不能用在自定义着色器中。
概览优先级的情况,优先级100设置几何体(geometry),优先级200决定初始片元色彩(gl_FragColor),更高数值优先级才能实际影响和改变片元色彩。
这里有一个自定义着色器样例,实现模型的色彩渐变:
init python:
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);
""")
自定义着色器可以用作一个变换(transform):
transform gradient:
shader "example.gradient"
u_gradient_left (1.0, 0.0, 0.0, 1.0)
u_gradient_right (0.0, 0.0, 1.0, 1.0)
show eileen happy at gradient
还有一个变量可用于自定义着色器的debug:
config.log_gl_shaders = False link若该配置项为True,GLSL着色器程序的源代码会在启动阶段写入 log.txt 文件中。
基于模型的渲染功能在ATL和 Transform() 类中添加了下面两个特性:
mesh link| Type: | None 或 True 或 元组 |
|---|---|
| Default: | None |
若该值不是None,Transform对象将作为模型渲染。同时意味着:
mesh_pad link| Type: | None 或 元组 |
|---|---|
| Default: | None |
若该值不是None,其可能是2元或4元元组。 如果mesh的值是True,mesh_pad表示网格纹理的四边留白大小。 2元元组分别对应纹理的右侧和底部留白,4元元组分别对应纹理的左、顶、右、底留白。
该特性可以与 pixel_perfect 一起使用,将文本渲染为网格。 在Ren’Py中,文本渲染与屏幕分辨率有关,可能出现超出纹理无法覆盖网格的情况。 添加一些留白会让纹理稍微大一点,可以完整显示所有像素。例如:
transform adjust_text:
mesh True
mesh_pad (10, 0)
gl_pixel_perfect True
shader "shaders.adjust_text"
可以确保传入着色器的纹理包含文本的所有像素。
shader link| Type: | None 或 字符串 或 字符串列表 |
|---|---|
| Default: | None |
若该值不是None,根据字符串或字符串列表将对应的着色器应用到Render对象(如果创建了模型对象)或在Render对象树上该Render对象分支后面的所有模型。
blend link| Type: | None 或 str |
|---|---|
| Default: | None |
若该值不是None,其应该是一个字符串。根据该字符串在 config.gl_blend_func 搜索对应的遮罩函数gl_blend_func特性后,用作图像遮罩模式。
默认的遮罩模式包括“normal”(正常或覆盖)、“add”(相加)、“multiply”(相乘或正片叠底)、“min”(最小值)和“max”(最大值)。
以 u_ 而非 u_renpy 开头的uniform型变量可以当作Transform的特性(property)来使用。 以 gl_ 开头的GL特性(property)变量可以当作Transform的特性(property)来使用。 例如,想使用GL中的 color_mask 特性,在Transform中需要改为 gl_color_mask。
config.gl_blend_func = { … } link一个字典型数据,用作遮罩模式名与遮罩函数的映射关系。 遮罩模式名称见下表:
默认的遮罩模式有:
gl_blend_func["normal"] = (GL_FUNC_ADD, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_FUNC_ADD, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
gl_blend_func["add"] = (GL_FUNC_ADD, GL_ONE, GL_ONE, GL_FUNC_ADD, GL_ZERO, GL_ONE)
gl_blend_func["multiply"] = (GL_FUNC_ADD, GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_FUNC_ADD, GL_ZERO, GL_ONE)
gl_blend_func["min"] = (GL_MIN, GL_ONE, GL_ONE, GL_MIN, GL_ONE, GL_ONE)
gl_blend_func["max"] = (GL_MAX, GL_ONE, GL_ONE, GL_MAX, GL_ONE, GL_ONE)
以下uniform型变量对所有模型都可直接使用。
vec2 u_model_sizevec2 u_lod_biasmat4 u_transformfloat u_timevec4 u_randomsampler2D tex0, sampler2D tex1, sampler2D tex2vec2 res0, vec2 res1, vec2 res2以下attribute型变量对所有模型都可直接使用。
vec4 a_position如果纹理可用,还有下面的attribute型变量:
vec2 a_tex_coordGL特性会更改OpenGL或基于模型渲染器的全局状态。
这些特性可以与Transform对象一起使用,或者 Render.add_property() 函数一起使用。
gl_anisotropic该特性决定了,应到网格上的纹理是否创建各向异性(anisotropy)。 各向异性是一种功能特性,能让纹理在X和Y方向的缩放系数不同时,采样出多个纹理元素(texel)。
该项默认为True。Ren’Py将其设置为False,为了避免对其他效果产生影响,比如Pixellate(像素化)转场。
gl_blend_func该特性应是一个6元元组,分别用作功能调节像素、原像素、遮罩像素和功能条件像素相关参数。 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.
一个例子是(rgb_equation, src_rgb, dst_rgb, alpha_equation, src_alpha, dst_alpha)。 调用方式如下:
glBlendEquationSeparate(rgb_equation, alpha_equation)
glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha)
这些函数的功能请参考OpenGL文档。 OpenGL常量可以从renpy.uguu中引入:
init python:
from renpy.uguu import GL_ONE, GL_ONE_MINUS_SRC_ALPHA
更通用的建议方式是使用 blend 变换特性。
gl_color_masksgl_depthgl_mipmapgl_pixel_perfectgl_texture_wrap该项决定了将纹理提高到网格的方式。其值应该是一个2元元组, 第一个元素用于设置GL_TEXTURE_WRAP_S,第二个元素用于设置GL_TEXTURE_WRAP_T, 这两项通常用作纹理的X和Y轴信息。
这些OpenGL常量应从renpy.uguu中引入:
init python:
from renpy.uguu import GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT, GL_REPEAT
模型可视组件(model displayable)就像是使用基于模型渲染器创建并使用模型的一个工厂。
Model(size=None) link该类是一种可视组件,让Ren’Py使用基于模型渲染器创建一个2D或3D模型,并根据指定渲染器绘制模型, 或放入指定的Transform(或Displayable)对象中。
如果没有调用 mesh 方法,且存在至少一个纹理,Ren’Py将根据加载纹理时的方式设置网格的 a_postion 和 a_tex_coord 值。 否则,将只设置网格的a_postion。 All methods on this calls return the displayable the method is called on, making it possible to chain calls.(译者注:看不懂这句写的是什么鸡掰意思……)
grid_mesh(width, height) link生成一个width×height的空间点网格,将所有点在水平和垂直方向上距离最近的其他点连接生成矩形,然后将矩形沿对角线切割生成三角形网格。
texture(displayable, focus=False, main=False, fit=False) link通过指定可视组件,为该模型添加纹理。
第一张纹理会成为 tex0,第二张纹理则是 tex1 ,以此类推。
child(displayable, fit=False) link该方法与texture方法相同,除了 focus 和 main 参数已被设置为True。
shader(shader) link为模型添加着色器(shader)。
uniform(name, value) link设置着色器中用到的各uniform型变量的值。
properties(name, value) link设置GL特性(property)的值。
模型可视组件可用在ATL变换中间,并使用内置着色器(shader)创建溶解(dissolve)变换效果。
transform dt(delay=1.0, new_widget=None, old_widget=None):
delay delay
Model().texture(old_widget).child(new_widget)
shader [ 'renpy.dissolve' ]
u_renpy_dissolve 0.0
linear delay u_renpy_dissolve 1.0
变量列表:
uniform mat4 u_transform;
attribute vec4 a_position;
顶点着色器:
gl_Position = u_transform * a_position;
变量列表:
uniform sampler2D tex0;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
uniform float u_renpy_blur_log2;
顶点着色器:
v_tex_coord = a_tex_coord;
片元着色器:
gl_FragColor = vec4(0.);
float renpy_blur_norm = 0.;
for (float i = 0.; i < u_renpy_blur_log2 + 5.; i += 1.) {
float renpy_blur_weight = exp(-0.5 * pow(u_renpy_blur_log2 - i, 2.));
gl_FragColor += renpy_blur_weight * texture2D(tex0, v_tex_coord.xy, i);
renpy_blur_norm += renpy_blur_weight;
}
gl_FragColor /= renpy_blur_norm;
变量列表:
uniform float u_lod_bias;
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform float u_renpy_dissolve;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
顶点着色器:
v_tex_coord = a_tex_coord;
片元着色器:
vec4 color0 = texture2D(tex0, v_tex_coord.st, u_lod_bias);
vec4 color1 = texture2D(tex1, v_tex_coord.st, u_lod_bias);
gl_FragColor = mix(color0, color1, u_renpy_dissolve);
变量列表:
uniform float u_lod_bias;
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform float u_renpy_dissolve_offset;
uniform float u_renpy_dissolve_multiplier;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
顶点着色器:
v_tex_coord = a_tex_coord;
片元着色器:
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);
float a = clamp((color0.a + u_renpy_dissolve_offset) * u_renpy_dissolve_multiplier, 0.0, 1.0);
gl_FragColor = mix(color1, color2, a);
变量列表:
uniform vec4 u_renpy_solid_color;
片元着色器:
gl_FragColor = u_renpy_solid_color;
变量列表:
uniform float u_lod_bias;
uniform sampler2D tex0;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
顶点着色器:
v_tex_coord = a_tex_coord;
片元着色器:
gl_FragColor = texture2D(tex0, v_tex_coord.xy, u_lod_bias);
变量列表:
uniform mat4 u_renpy_matrixcolor;
片元着色器:
gl_FragColor = u_renpy_matrixcolor * gl_FragColor;
变量列表:
uniform float u_renpy_alpha;
uniform float u_renpy_over;
片元着色器:
gl_FragColor = gl_FragColor * vec4(u_renpy_alpha, u_renpy_alpha, u_renpy_alpha, u_renpy_alpha * u_renpy_over);