Ren'Py引擎从入门到放弃(支线8·续)——着色器再临

Ren'Py引擎从入门到放弃(支线8·续)——着色器再临

世上无难事,只要肯放弃。

支线系列是独立于基础之外的内容,感觉有困难的同学可以暂时不(fang)看(qi)。

上一篇支线提到,Ren'Py 7.4版本以后可以自定义着色器(shader),实现一些有趣的图像效果。但貌似没有办法读入多张图片实现遮罩(blend)。事后我在万能的全球最大同性交友网站(Github)上发现了一些前人的其他成果,可以绕道实现这个需求。


第一个问题:如何安装和使用?

答:先放项目地址

Readme中阐述了安装方法,简单翻译一下:

  1. 下载整个项目到本地的Ren'Py项目目录;
  2. 安装“pythonlib2.7”,如果使用Ren'Py 7.0版本以上可以略过;
  3. 下载“PyOpenGL”,解压后把名为“OpenGL”的子目录放到步骤1下载内容的“ShaderDemo/game”或者Ren'Py引擎安装目录的“lib/pythonlib2.7”目录下;
  4. 从启动器启动 ShaderDemo 项目。

这个项目的牛逼之处不仅仅是提供了自定义着色器,还实现了灯光、投影、烟雾、真正的3D模型渲染、骨骼绑定动画等。只是最近1年半都没更新,不清楚作者是不是放弃了……


第二个问题:如何自定义着色器(shader)?

答:还是先直接给答案吧……

第一步,在 shadercode.rpy 文件中加GLSL代码(我只写了几种常见的图片遮罩模式):

PS_BLEND_MULTIPLY = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = color0 * color1;
}
"""

PS_BLEND_DIVIDE = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = color0 / color1;
}
"""

PS_BLEND_SCREEN = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = vec4(1.0) - ((vec4(1.0) - color0) * (vec4(1.0) - color1));
}
"""

PS_BLEND_ADD = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = color0 + color1;
}
"""

PS_BLEND_SUBSTRACT = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = vec4(max( color1 - color0, 0.0 ).xyz, 1.0);
}
"""

PS_BLEND_LIGHTEN = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = max(color0, color1);
}
"""

PS_BLEND_DARKEN = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = min(color0, color1);
}
"""

PS_BLEND_DIFFERENCE = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = max(color0, color1) - min(color0, color1);
}
"""

PS_BLEND_ALPHA = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = color0 * color1[3];
}
"""

PS_BLEND_ERASE = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    gl_FragColor = color0 - color1[3];
}
"""

PS_BLEND_OVERLAY = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    for(int i=0; i<4; i++)
    {
        if(color1[i] < 0.5)
        {
            gl_FragColor[i] = 2.0 * color0[i] * color1[i];
        }
        else
        {
            gl_FragColor[i] = 1.0 - 2.0 * (1.0 - color1[i]) * (1.0 - color0[i]);
        }
    }
}
"""

PS_BLEND_HARDLIGHT = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    for(int i=0; i<4; i++)
    {
        if(color1[i] >= 0.5)
        {
            gl_FragColor[i] = 2.0 * color0[i] * color1[i];
        }
        else
        {
            gl_FragColor[i] = 1.0 - 2.0 * (1.0 - color1[i]) * (1.0 - color0[i]);
        }
    }
}
"""

PS_BLEND_SOFTLIGHT = """

VARYING vec2 varUv;

UNIFORM sampler2D tex0;
UNIFORM sampler2D tex1;

void main()
{
    vec4 color0 = texture2D(tex0, varUv);
    vec4 color1 = texture2D(tex1, varUv);

    for(int i=0; i<4; i++)
    {
        if(color1[i] >= 0.5)
        {
            gl_FragColor[i] = 2.0 * color0[i] * color1[i] + color0[i] * color0[i] - 2.0 * color0[i] * color0[i] * color1[i] ;
        }
        else
        {
            gl_FragColor[i] = 2.0 * sqrt(color0[i]) * color1[i] - sqrt(color0[i]) + 2.0 * color0[i] - 2.0 * color0[i] * color1[i];
        }
    }
}
"""

第二步,任意一个文件中,添加图层(ShaderDemo项目写在script.rpy中):

# eileen图层是新增的
define config.layers = ["bottom", "master", "middle", "amy", "eileen", "top", "transient", "screens", "overlay"]

第三步,在images目录中添加两个图片。我就用Ren'Py自带的 eileen happy 图片,以及随便涂的一个遮罩图片……

第四步,使用自定义可视组件 ShaderDisplayable ,并定义一个screen:

screen blendScreen(name, pixelShader, textures={}, uniforms={}, update=None, xalign=0.5, yalign=1.0):
    modal False
    add ShaderDisplayable(shader.MODE_2D, name, shader.VS_2D, pixelShader, textures, uniforms, None, update):
        xalign xalign
        yalign yalign

第五部,使用 show screen 语句显示界面及传入参数指定遮罩模式:

# Multiply,也就是Photoshop中的“正片叠底”模式
show screen blendScreen("eileen", shader.PS_BLEND_MULTIPLY, {"tex1": "blend"}, _tag="eileen", _layer="eileen")

其中第二个参数 shader.PS_BLEND_MULTIPLY 指定渲染模式,来源于第一步定义在 shadercode.rpy 文件中的GLSL代码。效果如下:

其他几个shader的效果我就不上图了。


第三个问题:还有什么骚操作吗?

答:有的,但我不会(理直气壮)~ShaderDemo项目样例代码中有不少很有应用场景的演示,都需要超出Ren'Py范畴的基础,有兴趣的请慢慢学……


预告:存档和读档界面是有亿点点复杂,估计会放到GUI定制化的最后……

编辑于 2021-02-05 15:04