[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: 337
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 »

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: 337
Joined: Wed Aug 22, 2018 1:22 am
Contact:

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

#2 Post by henvu50 »

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.

Griatch
Newbie
Posts: 2
Joined: Fri Aug 18, 2023 5:47 am
Contact:

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

#3 Post by Griatch »

After a lot of experimenting (including trying to rewrite the shader vector coordinates, which works but showed quirks in the renpy rendering pipeline
- basically it causes the image to flip up-side down briefly, very strange - I got this working for me now. Here's the complete code:

Code: Select all

transform softlight(child, xalign=0.5, yalign=0.5, alpha=0.8, blendalpha=1.0, blendimg="images/overlay_blend.png"):
    xalign xalign 
    yalign yalign 
    alpha alpha 
    Model().shader(\
        "test44.softlight)\
        .child(child, fit=True)\    # important!
        .texture(blendimg)
        .uniform("u_blendalpha", blendalpha)
        

init python:
    renpy.register_shader(
        "test44.softlight",
        variables="""
            uniform sampler2D tex0;
            uniform sampler2D tex1;
            attribute vec2 a_tex_coord;
            varying vec2 v_tex_coord;
            uniform float u_blendalpha;
        """,
        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)), u_blendalpha);
        """
          
 # example 
 label start: 
     show eileen at softlight
     
     pause 
     
     show eileen happy    # this will still have the softlight effects 
     
In the end I used the same shader layout as already shown in this thread. I added a `u_blendalpha` variable to set the opacity of the blend image.

It turns out that using `.child(child, fit=True)` was important here when creating the transform. This makes the child (that is, the base image) to the main image, and importantly, it passes its placement on the screen to the rendering pipeline, which fixes all the issues with coordinates also if the base is smaller than the blend image (in my case the blend covers the entire screen while the base images are smaller and meant to move "below" it).

For alignments, I couldn't get the softlight transform to work with regular `show eileen at center, softlight` - the center transform will not work. It works if you do a separate `show eileen at center` first on the line above, but that's a bit weird. So that's why I embedded the xalign,yalign,alpha in the softlight transform directly, you can then just do

Code: Select all

label start: 
    show eileen at softlight(xalign=0.8)
to move the image (with softlight blending on) to the right, which seems to be as good as I can do it at this time. Until you apply another transform to another image, the softlight will apply to any subsequent image you show within the label.

If you have a screen background, this will not be affected unless you apply softlight transform on it separately. So this is not quite the desired "put the blend image on a layer and blend with everything below" solution. Renpy's renderer just doesn't work that way as far as I understand. But at least it's workable. And the effect is really quite nice. :)

Example of base + blend = result (From my wip Interview with a lonely Machine):
ImageImageImage

User avatar
birctreel
Regular
Posts: 53
Joined: Wed Dec 20, 2017 7:17 am
Projects: Knell of st.Godhrkar
Organization: -Andinomie-
Tumblr: birctreel
Location: Beijing, China
Contact:

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

#4 Post by birctreel »

Same question Dude!
Now usual shader works good but I wonder if there are anyway to make layer effect shader, maybe applying the shader transform to the whole layer.
I managed to make a transform of my shader, for example:

Code: Select all

transform shadercrt:
    shader "shadertoy.test"
And it works well if I write something like

Code: Select all

show overlay at shadercrt
It will perfectly work the shader effect on the overlay.

I noticed there are some code in renpy called camera, which allows you to put transform to a whole layer:
https://www.renpy.org/doc/html/displayi ... tml#camera

camera at XXX(transform) = show layer master at XXX(transform)

However, if I use the shader transform there there is a error:
Exception: Shader ('renpy.geometry', 'renpy.solid', 'shadertoy.test') has not been given uniform tex0.
Guess it's because shader transform not support layers now? Or if there are any other ways?

pke1029
Newbie
Posts: 1
Joined: Sat Mar 02, 2024 11:04 pm
Github: pke1029
itch: pke1029
Contact:

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

#5 Post by pke1029 »

birctreel wrote: Tue Nov 21, 2023 4:00 am However, if I use the shader transform there there is a error:
Exception: Shader ('renpy.geometry', 'renpy.solid', 'shadertoy.test') has not been given uniform tex0.
Guess it's because shader transform not support layers now? Or if there are any other ways?
As I understand it, the shaders did not work because the default Transform.render function has model-based rendering disabled. Below is a piece of code to overwrite the function and enable model-based rendering.

Code: Select all

init python hide:
    
    # Overwrite `Transform.render` function 
    from renpy.display.accelerator import RenderTransform     # Ren'Py 7.6/8.1 and above

    def my_render_func(self, width, height, st, at):

        # Copy of `renpy.display.transform.Transform.render` function
        if st + self.st_offset <= self.st:
            self.st_offset = self.st - st
        if at + self.at_offset <= self.at:
            self.at_offset = self.at - at
        self.st = st = st + self.st_offset
        self.at = at = at + self.at_offset
        self.update_state()
        r = RenderTransform(self).render(width, height, st, at)

        # Enable model-based rendering 
        r.mesh = True 
        r.add_shader("renpy.texture")

        return r

    renpy.display.transform.Transform.render = my_render_func
Then you would apply the shader to the master layer with

Code: Select all

transform shadercrt:
    shader "shadertoy.test"

label start:
    camera master at shadercrt
    # show stuff here
For Ren'Py 7.5/8.0 and earlier, use instead

Code: Select all

init python hide:
    
    # Overwrite `Transform.render` function 
    from renpy.display.accelerator import transform_render     # Ren'Py 7.5/8.0 and below

    def my_render_func(self, width, height, st, at):

        # Copy of `renpy.display.transform.Transform.render` function
        r = transform_render(self, width, height, st, at)

        # Enable model-based rendering 
        r.mesh = True 
        r.add_shader("renpy.texture")

        return r

    renpy.display.transform.Transform.render = my_render_func
As a follow-up question, I'm still looking for a way to apply multiple shaders to the same layer. If I place the shaders inside an ATL block like so

Code: Select all

transform shadercrt:
    shader "shader.test1"
    shader "shader.test2"
only "shader.test2" is applied. Does anyone experienced with ATL know another way to do this?

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Google [Bot], halnye, Semrush [Bot]