儘管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)。
Render
mesh
屬性(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
linkType: | None 或 True 或 元組 |
---|---|
Default: | None |
若該值不是None,Transform對象將作為模型渲染。同時意味著:
mesh_pad
linkType: | 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
linkType: | None 或 字串 或 字串列表 |
---|---|
Default: | None |
若該值不是None,根據字串或字串列表將對應的著色器應用到Render對象(如果創建了模型對象)或在Render對象樹上該Render對象分支後面的所有模型。
blend
linkType: | 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_size
vec2 u_lod_bias
mat4 u_transform
float u_time
vec4 u_random
sampler2D tex0
, sampler2D tex1
, sampler2D tex2
vec2 res0
, vec2 res1
, vec2 res2
以下attribute型變數對所有模型都可直接使用。
vec4 a_position
如果紋理可用,還有下面的attribute型變數:
vec2 a_tex_coord
GL特性會更改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_masks
gl_depth
gl_mipmap
gl_pixel_perfect
gl_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);