[UNRESOLVED: Must figure out how to apply shader to renpy.layer['master']) - Soft Light blend mode shader in Ren'py.

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
henvu50
Veteran
Posts: 322
Joined: Wed Aug 22, 2018 1:22 am
Contact:

[UNRESOLVED: Must figure out how to apply shader to renpy.layer['master']) - Soft Light blend mode shader in Ren'py.

#1 Post by henvu50 » Wed Oct 26, 2022 8:02 am

EDIT: Although I figured out how to make the shader work, the shader needs to be a screen shader. That's what I need. I have no idea how to make a screen shader in Ren'py. The only thing I know how to do is show an image with a shader effect on it, but there's no documentation on how to apply a shader to Renpy.Layer.Master. That would be powerful. We could then quickly apply special effects to EVERYTHIGN in the scene, that's what we need IMO. I scoured the internet for this information, but couldn't find it; in regards to Ren'py shaders.

EDIT: I made massive progress in the 2nd post of this thread. I've got it 95% working, it looks just like photoshop Soft Light blending. Go to the 2nd post of this thread for the code. It just needs to be cleaned up, and this could possibly be a new blending mode for Ren'py one day. I'm making some rookie mistakes the veterans could easily help me fix.

Renpy has add, min, max, normal and multiply blend modes. I'd like to add Soft Light blend mode. This means you'd have two images shown, overlaid over each other, then the second image on top would be set to Soft Light blend mode.

Hypothetically, it'd work like this:

Code: Select all

transform soft_light_blending:
    blend "softlight"
label start:
    show image1 at center
    show image2 at center, soft_light_blending

image special_fancy_overlay_soft_light = Transform("special_fancy_overlay_soft_light .png", blend="softlight")
This is out of my league, but I've pieced together a lot of code. Maybe you guys can help me complete the code?

Outside Sources
https://www.npmjs.com/package/glsl-blend-soft-light

Code: Select all

void main() {
  vec4 bgColor = texture2D(bg, vUv);
  vec4 fgColor = texture2D(foreground, vUv);
   vec3 color = blend(bgColor.rgb, fgColor.rgb);
  gl_FragColor = vec4(color, 1.0);
}
(blend < 0.5) ? (2.0 * base * blend + base * base * (1.0 - 2.0 * blend)) : (sqrt(base) * (2.0 * blend - 1.0) + 2.0 * base * (1.0 - blend))
Ren'py

This code was made by Remix and shared in Discord. It's a means of allowing two textures to be passed to a shader?

Code: Select all

init python:

    class ShaderDisplayable(renpy.Displayable):

        def __init__(self, *args, **kwargs):
            self.shader_kws, kwargs = renpy.split_properties(kwargs, "u_", "")
            self.shader = kwargs.pop("shader")
            self.mipmap = kwargs.pop("mipmap", None)

            # Pass additional properties on to the constructor.
            super(ShaderDisplayable, self).__init__(**kwargs)

            self.shader_textures = [renpy.easy.displayable(k) for k in args]

        def render(self, width, height, st, at):
            mr = renpy.display.render.render(
                self.shader_textures[0], width, height, st, at)
            w, h = mr.get_size()

            rv = renpy.display.render.Render(w, h, opaque=False)

            rv.blit(mr, (0,0))

            for k in self.shader_textures[1:]:
                cr = renpy.display.render.render(k, w, h, st, at)
                rv.blit(cr, (0,0), focus=False, main=False)

            rv.mesh = True
            rv.add_shader(self.shader)
            for k,v in self.shader_kws.items():
                rv.add_uniform("u_{}".format(k), v)
            rv.add_property("mipmap", 
                renpy.config.mipmap_dissolves if (self.mipmap is None) 
                else self.mipmap)

            return rv

        def event(self, ev, x, y, st):
            return None

        def visit(self):
            return self.shader_textures


Based on the above code by Remix, we'd be able to specify Soft Light blend shader between the two given textures:

Code: Select all

image hello Tom = ShaderDisplayable("bg tom", "fg tom", u_keyword=1.0, shader="....")
I believe ShaderDisplayable was implemented in the form of https://renpy.org/doc/html/model.html#model-displayable, but I'm not sure?

Someone already made a soft light blend mode for Ren'py, but it doesn't actually blend one image over another, instead it applies a single color as a soft light over the base image which isn't very useful IMO.
https://github.com/CrossCouloir/renpy-b ... s/base.rpy
https://github.com/CrossCouloir/renpy-b ... _light.rpy

Code: Select all

soft_light_blend_mode = BlendMode("crosscouloir.soft_light")
  soft_light_blend_mode.vars = """
uniform float u_lod_bias;
uniform sampler2D tex0;
uniform vec4 u_light_color;
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
"""
  soft_light_blend_mode.fragment_functions = """
float blendSoftLight(float base, float blend) {
  return (blend<0.5)?(2.0*base*blend+base*base*(1.0-2.0*blend)):(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend));
}
vec3 blendSoftLight(vec3 base, vec3 blend) {
  return vec3(blendSoftLight(base.r,blend.r),blendSoftLight(base.g,blend.g),blendSoftLight(base.b,blend.b));
}
vec3 blendSoftLight(vec3 base, vec3 blend, float opacity) {
  return (blendSoftLight(base, blend) * opacity + base * (1.0 - opacity));
}
"""
  soft_light_blend_mode.vertex_shader = """
v_tex_coord = a_tex_coord;
"""
  soft_light_blend_mode.fragment_shader = """
vec4 bgcolor = texture2D(tex0, v_tex_coord.st, u_lod_bias);
vec3 blended = blendSoftLight(bgcolor.xyz, u_light_color.xyz, u_light_color.w);
gl_FragColor = vec4(blended, bgcolor.w);
"""

  soft_light_blend_mode.register()
 
Can anyone create a Soft Light blend mode from the above code for Ren'py, between two provided images/textures? So like in Photoshop, you have two layers. The top layer is set to Soft Light, and that image is blended onto the layer below, using Soft Light shader.

Here is more code that might be useful. It's supposed to be a Darken blend mode shader. This was written by user EviteGames from Discord:
https://discord.com/channels/2866338985 ... 8019353652

Code: Select all

float blendDarken(float base, float blend) {
    return min(blend,base);
}
vec3 blendDarken(vec3 base, vec3 blend) {
    return vec3(blendDarken(base.r,blend.r),blendDarken(base.g,blend.g),blendDarken(base.b,blend.b));
}
vec3 blendDarken(vec3 base, vec3 blend, float opacity) {
    return (blendDarken(base, blend) * opacity + base * (1.0 - opacity));
}

gl_FragColor = vec4(min(baseColor.r,blendColor.r), min(baseColor.g, blendColor.g), min(baseColor.b, blendColor.b), 1.0)

renpy.register_shader("evitegames.darkenblend", variables="""
        uniform sampler2D tex0; // this should be the 'background' texture or the image/render itself
        uniform sampler2D tex1; // this should be the 'blend' texture or the video we're trying to darken the image with
        uniform float u_lod_bias;
        attribute vec2 a_tex_coord;
        varying vec2 v_tex_coord;
    """, vertex_300="""
        v_tex_coord = a_tex_coord;
    """, fragment_300="""    
        vec4 baseColor = texture2D(tex0, v_tex_coord.st, u_lod_bias);
        vec4 blendColor = texture2D(tex1, v_tex_coord.st, u_lod_bias);
        gl_FragColor = vec4(min(baseColor.r,blendColor.r), min(baseColor.g, blendColor.g), min(baseColor.b, blendColor.b), 1.0);
    """)
I found someone else who has made the soft light shader. The webpage was in a different language. Here is the primary party of the code, but it looks like it's in C# I think?
https://zhuanlan-zhihu-com.translate.go ... r_pto=wapp

Code: Select all

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];
        }
    }
}
"""
Ren'pys Image Dissolve may be very similar to Soft Light blending.
https://www.renpy.org/doc/html/model.ht ... iority-200

Code: Select all

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);
Last edited by henvu50 on Fri Oct 28, 2022 12:48 am, edited 3 times in total.

henvu50
Veteran
Posts: 322
Joined: Wed Aug 22, 2018 1:22 am
Contact:

Re: How do I complete this "Soft Light" blend shader?

#2 Post by henvu50 » Wed Oct 26, 2022 11:20 am

UPDATE: I've got it 95% figured out.

Minor problems:
1. The images have to be the same dimensions or texture stretching occurs. Would be nice to not have to worry about that.
2. I want the top most layer to be the blend soft light layer, that applies over every image in the scene. I'm not sure how to do that. This only works with 2 images right now.
3. I think you need to always give the shader all visible textures. This means you'd have to give it the base scene layer, like a bedroom. Then if you're showing a character, and that character has lots of clothing or something, you'd have to pass all those texture to the shader. This could turn out to be very complicated.
4. Yea, for some reason it doesn't work if the bottom layer image is transparent or a different size from the 1920x1080 overlay image.

Note: See the 0.5 below? That's the Opacity value, it's like setting Soft Light to 50% in photoshop.

Code: Select all

init python:
    renpy.register_shader("test44.softlight", variables="""
        uniform sampler2D tex0;
        uniform sampler2D tex1;
        attribute vec2 a_tex_coord;
        varying vec2 v_tex_coord;
    """, vertex_300="""
        v_tex_coord = a_tex_coord;
    """, fragment_300="""    
        vec4 base = texture2D(tex0, v_tex_coord);
        vec4 blend = texture2D(tex1, v_tex_coord);
        vec4 ctemp = base * blend;
        gl_FragColor = mix(base, ctemp+(base*(1-((1-base)*(1-blend))-ctemp)), 0.5);
    """)

    show some_image:
        shader("test44.softlight")
        Model().texture("images/base.png").texture("images/blend_soft_light.png")
        
The Soft Light equation is based on this, it's from Blender Sushi, whatever that is:
https://blendersushi.blogspot.com/2013/ ... tions.html

Code: Select all

// COLOR SOFTLIGHT
color colorSoftlight(color BaseMap, color Layer, float LayerOpac){
    color ctemp = BaseMap * Layer;
    return mix(BaseMap, ctemp+(BaseMap*(1-((1-BaseMap)*(1-Layer))-ctemp)), LayerOpac);
}
It looks JUST like Photoshop!

If someone can help me polish this, it could possibly be a feature for Ren'py some day.

Post Reply

Who is online

Users browsing this forum: Google [Bot]