基於模型的渲染器 link

儘管Ren’Py在視覺小說中主要使用二維矩形圖形,但為了利用各類主流GPU的功能特性其實Ren’Py內建了一個基於模型的渲染器。 使用該渲染器可以實現很多視覺特效。

事先預警,該渲染器是Ren’Py最新添加的功能。很多情況下,不需要理解基於模型渲染器在後台的工作原理,就可以使用 新增的特性,比如 matrixcolor 和Live2D的支持。Ren’Py後續也會添加類似的功能。 本頁文件主要面向最前沿的創作者,以及嘗試在Ren’Py中添加新功能的開發人員。

在Ren’Py 7.4中,基於模型的渲染器需要修改設置才能啟用。只需要將 config.gl2 設置為True

define config.gl2 = True
define config.gl2 = False link

若此項為True,Ren’Py將預設使用基於模型的渲染器。

未來可能Ren’Py可能會將基於模型渲染器作為唯一的渲染器。本頁文件的後續內容都基於該渲染器啟用的情況下展開。

基於模型渲染器是Ren’Py中最新的功能特性,如果事先沒有看過OpenGL、OpenGL ES、GLSL和GLSL ES手冊的話,理解本頁文件可能十分困難。 此外,不同圖形處理單元驅動對輸入模型數據的要求可能略有不同,需要事先檢查硬體訊息。

模型,渲染和繪圖操作 link

Ren’Py繪製到螢幕上的內容,本質上是一個模型。該模型包含以下內容:

  • 由一個或多個三角形組成的網格(mesh)。 一個三角形包含三個頂點(vertice)。 每個頂點包含二維或三維空間中的位置訊息,還可能包含其他訊息,比如最常見的是紋理(texture)坐標。
  • 0個或若干個紋理(texture),最大數量受限於遊戲實際運行平台的圖形處理單元(GPU)。所有的圖形處理單元都至少需要支持每個模型3個紋理。 紋理是一個含有圖像數據的矩形,可以直接或渲染加工後,載入進圖形處理單元中。
  • 著色器(shader)名稱的一個列表。Ren’Py使用該列表創建著色器。著色器是在圖形處理單元中運行的程序,用於渲染模型。 著色器名稱前綴為 “-” 表示禁用此著色器。
  • uniform型變數值。uniform型變數保存添加到模型中的數據。比如,要使某個模型顯示為某個純色,指定的顏色就需要是一個uniform型。
  • GL特性(property)。GL特性是控制渲染方式的標識型數據,比如模型的縮放模式以及顏色遮色片(mask)。

Ren’Py通常會在螢幕上同時繪製好幾樣東西,並創建一棵 Render 對象樹。 樹上的Render對象的子對象可能是模型或其他Render對象。(後面會提到,Render對象也可以轉為模型。) 一個Render對象包含:

  • 子對象列表,包括每個子對象的二維偏移量。
  • 一個 Matrix 對象,用於描述各子對象變換到三維空間的方法。
  • 模型繪製時需要用到的著色器名稱、uniform變數和GL特性列表。
  • 判斷可繪制空間分割多邊形是否更新的一些標識(flag)。

Ren’Py使用深度優先算法遍歷Render對象樹,遍歷搜索到模型對象才繪製畫面。 執行遍歷時,Ren’Py會更新一個矩陣,該矩陣對模型的位置,分割多邊形,著色器、uniform變數和GL特性列表進行轉換。 當遍歷的某一步中搜索到模型對象時,圖形處理單元上對應的著色器程序將啟動,並傳入(矩陣內)所有訊息,最後執行繪圖操作。

模型對象在哪裡創建的 link

Ren’Py在常規操作中將自動創建模型對象。 理解模型創建細節的主要用處是,主動使用繪圖操作生成模型對象以及直接在模型對象上的應用著色器。

圖像和圖像處理器(Image Manipulator)
這兩類創建的模型是由兩個三角形組成的網格(mesh),兩個三角形正好覆蓋原矩形圖像。 該網格包含紋理坐標。創建的模型使用 “renpy.texture” 著色器。
Solid()
純色可視組件創建的模型是由兩個三角形組成的網格(mesh),但沒有紋理坐標。 創建的模型使用 “renpy.solid” 著色器,該著色器使用的顏色訊息存儲在 u_renpy_solid_color uniform變數中。
Dissolve(), ImageDissolve(), AlphaDissolve(), Pixellate(), AlphaMask(), Flatten()
以上這些變換和可視組件創建的模型對象可以根據實際需要包含對應的網格、著色器和uniform變數。
Live2D
Live2D型可視組件在渲染時可能會創建多個模型對象,即每個圖層一個模型對象。
Transform() 和 ATL

Transform對象在 mesh 為True或 blur 特性時創建一個模型對象。 模型創建後,Transform對象的所有子對象都將渲染為紋理,第一個紋理的網格作為整個模型的網格。

並非所有的Transform都會創建模型對象。一些Transform值只是在渲染器中簡單添加著色器和uniform變數(比如使用 bluralpha 的Transform)。 其他Transform只影響幾何體(geometry)。

Render
當Transform對象的 mesh 屬性(attribute)為True時,將創建模型對象。 這種情況下,Render對象的所有子對象將被渲染為紋理,第一個紋理的網格作為整個模型的網格。

未來Ren’Py將添加更多創建模型的方法。

著色器程序概述 link

Ren’Py生成著色器程序的第一步是識別著色器名稱列表。該列表包含 “renpy.geometry”,即從Render對象獲取的著色器列表,以及模型對象繪製過程中用到的其他著色器。

接著所有著色器程序將被複製一份。以“-”開頭的著色器將會從複製後的列表中刪除,以及同名但開頭不是“-”的著色器也刪除。 (名為“-renpy.geometry”的著色器會導致自身和“renpy.geometry”都被刪除)

接著,Ren’Py將根據列表中的著色器名,檢索變數、函數、頂點著色器(vertex shade)和片元(fragment shader)列表, 並按優先度數值從小到大的順序依次生成著色器原始碼。優先度數值定義在頂點著色器和片元著色器中。

Ren’Py會將使用過的所有著色器組合快取在 game/cache/shaders.txt 文件中,並在啟動時載入這個文件。 如果使用著色器方面有比較大改動,就需要編輯清空或刪除這個文件。這樣就可以重新生成有效數據。

創建自訂著色器 link

透過調用 renpy.register_shader 函數可以基於GLSL規範創建新的著色器。

著色器名的格式必須是“命名空間.著色器名稱”,比如“mygame.recolor”和“mylibrary.warp”。 Ren’Py已經占用了“renpy.”和“live2d.”兩個命名空間,所有以下劃線“_”開頭的命名空間也是預留的不可使用。

renpy.register_shader(name, **kwargs) link

This registers a shader part. This takes name, and then keyword arguments. 該函數註冊一個著色器名。入參 name,其他關鍵字入參如下:

name
指定著色器名稱的字串。以下劃線或“renpy.”開頭的名稱已經被Ren’Py預留。
variables

著色器使用的各個變數。每行一個變數,存儲類型(uniform、attribute或varying)後面跟變數類型、變數名稱,結尾用分號。舉例:

variables='''
uniform sampler2D tex0;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
'''
vertex_functions
如果給定,這個字串內容會用作頂點著色器函數。
fragment_functions
如果給定,這個字串內容會用作頂片元色器函數。

著色器函數相關的兩個關鍵字入參應該以 vertex_fragment_ 開頭,結尾帶一個整數表示優先度,比如“fragment_200”和“vertex_300”。 這些優先度數值會決定著色器的應用方式,優先度數值低的函數會插入到優先度數值高的函數前面執行。

Ren’Py只支持一下變數類型:

  • float (Python中的浮點數)
  • vec2 (兩個浮點數組成的元組)
  • vec3 (三個浮點數組成的元組)
  • vec4 (四個浮點數組成的元組)
  • mat4 ( Matrix 類)
  • sampler2D (Ren’Py提供)

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:

define config.log_gl_shaders = False link

若該配置項為True,GLSL著色器程序的原始碼會在啟動階段寫入 log.txt 文件中。

Transform類和基於模型的渲染 link

基於模型的渲染功能在ATL和 Transform() 類中添加了下面兩個特性:

mesh link
Type:None 或 True 或 元組
Default:None

若該值不是None,Transform對象將作為模型渲染。同時意味著:

  • 將創建一個網格。如果值是一個2元元組,將分別使用兩個數值作為網格的x和y方向大小(任意方向至少為2)。如果值是True,根據子對象創建網格。
  • 該變換的子對象將渲染為紋理。
  • 渲染時將添加 renpy.texture 著色器。
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。

遮罩函數 link

define 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和attribute變數 link

以下uniform型變數對所有模型都可直接使用。

vec2 u_model_size
模型的寬度和高度。
vec2 u_lod_bias
根據細節偏移等級(level of detail bias),決定紋理查詢精度。
mat4 u_transform
此變換(transform)將項目虛擬像素轉換為OpenGL的視口(viewport)。
float u_time
該幀的時間。由於Epoch(譯者註:1970年1月1日00:00:00 UTC)可能沒有明確定義,所以最好把這個值看作是以秒為單位的數值,並按86400取模,每過一天都歸零。
vec4 u_random
4個介於0.0到1.0之間的隨機數。每幀生成的隨機數都不同(儘管可能比較相近)。
sampler2D tex0, sampler2D tex1, sampler2D tex2
如果紋理可用,對應的sampler2D類型數據可以存入這些變數。
vec2 res0, vec2 res1, vec2 res2
如果紋理可用,紋理的尺寸訊息將存入這些變數。當紋理是從磁碟載入時,這些數值就是圖片文件的尺寸。 在渲染為紋理後,這些數值是實際可繪製像素的最小外接矩形的尺寸。

以下attribute型變數對所有模型都可直接使用。

vec4 a_position
待渲染頂點位置訊息。

如果紋理可用,還有下面的attribute型變數:

vec2 a_tex_coord
頂點相對紋理內部的坐標。

GL特性(property) link

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
該特性應是一個布爾型4元元組,分別對應像素中的4個通道(紅、綠、藍和alpha)。只有當元組中對應通道的元素值為True時,繪圖操作才會實際繪製像素的顏色值。
gl_depth
若為True,將會清理深度快取,然後可視組件和其子組件的深度渲染。
gl_mipmap
該項決定紋理是否提供了網格用於創建mipmap。預設值為True.
gl_pixel_perfect
只有創建網格時該特性才會生效。若該值是True,Ren’Py會把網格的第一個頂點與螢幕某個像素對齊。 該特性常用於文本內容的銜接,確保文字的清晰度。
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

模型可視組件 link

模型可視組件(model displayable)就像是使用基於模型渲染器創建並使用模型的一個工廠。

class Model(size=None) link

該類是一種可視組件,讓Ren’Py使用基於模型渲染器創建一個2D或3D模型,並根據指定渲染器繪製模型, 或放入指定的Transform(或Displayable)對象中。

size
若非None,該參數應是一個分辨便是寬度和高度的二元元組,用於模型的尺寸。 若沒有指定,模型的尺寸將與其占用的區域一致。合適的參數也將影響模型使用到紋理的大小。

如果沒有調用 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的空間點網格,將所有點在水平和垂直方向上距離最近的其他點連接生成矩形,然後將矩形沿對角線切割生成三角形網格。

width, height
水平和垂直方向上點的數量,都必須是不小於2的整數。
texture(displayable, focus=False, main=False, fit=False) link

通過指定可視組件,為該模型添加紋理。 第一張紋理會成為 tex0,第二張紋理則是 tex1 ,以此類推。

focus
若為True,獲得焦點事件將傳給可視組件。 默認作為入參的可視組件坐標以1:1映射到紋理。
main
若為True,作為入參的可視組件將作為模型的主要子組件,可以被可視組件檢測器探知。
fit
若為True,根據可視組件的尺寸創建模型。 只有在單一紋理的情況下才可以設置為True。
child(displayable, fit=False) link

該方法與texture方法相同,除了 focusmain 參數已被設置為True。

shader(shader) link

為模型添加著色器(shader)。

shader
表示著色器名稱的字串,並將應用到模型上。
uniform(name, value) link

設置著色器中用到的各uniform型變數的值。

name
一個字串,表示uniform型變數的名稱,前綴是“u_”。
value
uniform型變數的值。可以是浮點型數值,2元、3元、4元數組,或者矩陣。
properties(name, value) link

設置GL特性(property)的值。

name
一個字串,表示GL特性的名稱,前綴是“gl_”。
value
GL特性的值。

模型可視組件樣例 link

模型可視組件可用在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

默認著色器名稱 link

renpy.geometry (priority 100) link

變數列表:

uniform mat4 u_transform;
attribute vec4 a_position;

頂點著色器:

gl_Position = u_transform * a_position;

renpy.blur (priority 200) link

變數列表:

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;

renpy.dissolve (priority 200) link

變數列表:

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);

renpy.imagedissolve (priority 200) link

變數列表:

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);

renpy.solid (priority 200) link

變數列表:

uniform vec4 u_renpy_solid_color;

片元著色器:

gl_FragColor = u_renpy_solid_color;

renpy.texture (priority 200) link

變數列表:

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);

renpy.matrixcolor (priority 400) link

變數列表:

uniform mat4 u_renpy_matrixcolor;

片元著色器:

gl_FragColor = u_renpy_matrixcolor * gl_FragColor;

renpy.alpha (priority 500) link

變數列表:

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);