Question: Applying shaders to the overall mesh image of rectangles

Hello,
I’ve been working a lot more with meshes recently, and I’m wondering if there is a way to apply a shader to the overall mesh with rectangles inside it. If that doesn’t make sense, say I have a mesh with 4 rectangles with images in them, but I want to apply a shader effect to all of the overall image, not individual rectangles. The only way I can think of how to do this is render the old mesh into an image, apply the image to another mesh’s texture, then apply the shader to the new mesh, but that’s too slow.
Thanks!

The shader should affect the mesh overall by default. If you create a mesh, then set a texture to it, then set a shader to it, then add rectangles to the mesh and set the textures of the rectangles to that current mesh, all of the rectangles added will be effected by the shader.

m = mesh()
m.texture = sprite()
m.shader = shader()
rect = m:addRect(x,y,w,h)
m:setRectTex(rect,0,0,1,1)

Keep on adding rectangles and setting the texture of them using the mesh with the shader, and every rectangle will have the shader effect.

For my RPGenerator project, I store my meshes into tables for future use. If you do the same, then you can add rectangles at any time and apply the texture with the shader to them as well.

Another scenario… If you are making a mesh of rectangles which are utilizing textures of different meshes, then it may be better to simply utilize one mesh with all the textures combined into one image.

For example, I have 4 separate images I use for textures, so you would assume I would use 4 different meshes. Instead, I combine my 4 images into a single image (edit in paint or photoshop or other program). Assuming each texture image is the same size, you can then do the following:

m = mesh()
m.texture = sprite(2x2image)
m.shader = shader()
rect = m:addRect(x,y,w,h)
m:setRectTex(rect,0,0,0.5,0.5) -- bottom left texture
rect = m:addRect(x,y,w,h)
m:setRectTex(rect,0.5,0,0.5,0.5) -- bottom right texture
rect = m:addRect(x,y,w,h)
m:setRectTex(rect,0,0.5,0.5,0.5) -- top left texture
rect = m:addRect(x,y,w,h)
m:setRectTex(rect,0.5,0.5,0.5,0.5) -- top right texture

Now I am using 4 different textures in the same mesh and they are all affected by the shader.

A shader always applies to the entire mesh anyway

@Ignatz, @Slashin8r - No, the shader is being applied to every rect in this test:

displayMode(FULLSCREEN)

function setup()
    fixtures = {}
    local gSize = vec2(WIDTH,HEIGHT/2)
    local gScale = 5
    local funnleSize = 5
    funnle1 = physics.body(CHAIN,false,vec2(-gSize.x,gSize.y),
                                vec2(-funnleSize,-gSize.y/3),
                                vec2(-funnleSize,-gSize.y)
                                )
    local funnle2 = physics.body(CHAIN,false,vec2(gSize.x,gSize.y),
                                vec2(funnleSize,-gSize.y/3),
                                vec2(funnleSize,-gSize.y)
                                )
    local split = physics.body(CHAIN,false,vec2(-WIDTH/2,-HEIGHT/8),
                                vec2(0,HEIGHT/80),
                                vec2(WIDTH/2,-HEIGHT/8)
                                )
    funnle1.x = WIDTH / 2
    funnle1.y = HEIGHT - HEIGHT / 4
    funnle1.type = STATIC
    funnle2.x = WIDTH / 2
    funnle2.y = HEIGHT - HEIGHT / 4
    funnle2.type = STATIC
    split.x = WIDTH / 2
    split.y = HEIGHT/8
    split.type = STATIC
    split.restitution = .3
    table.insert(fixtures,funnle1)
    table.insert(fixtures,funnle2)
    table.insert(fixtures,split)
    
    
    blur = 15
    water = {}
    waterimage = softImage(color(0, 155, 255),5,blur)
    
    waterMesh = mesh()
    waterMesh.shader = shader("Effects:Ripple")
    --[[waterMesh.shader.smoothness = 0
    waterMesh.shader.threshold = .9
    waterMesh.shader.unpremultiply = 1]]--
    waterMesh.texture = waterimage
end


function draw()
    waterMesh.shader.time = ElapsedTime
    waterMesh.shader.freq = 10
    background(40, 40, 50)
    
    physics.gravity(Gravity)
    
    noStroke()
    fill(0, 255, 253, 255)
    tint(0, 16, 255, 255)
    smooth()
    for i,v in ipairs(water) do
        --ellipse(v.body.x,v.body.y,v.body.radius*2)
        --sprite(waterimage,v.body.x,v.body.y,blur,blur)
        waterMesh:setRect(v.rect,v.body.x,v.body.y,blur,blur)
        if v.body.x <= 0 or v.body.x >= WIDTH or v.body.y <= 0 or v.body.y >= HEIGHT then
            table.remove(water,i)
            v.body:destroy()
        end
    end
    
    waterMesh:draw()
    
    strokeWidth(3)
    for i,v in ipairs(fixtures) do
        pushMatrix()
        translate(v.x,v.y)
        local points = v.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
        popMatrix()
    end
    popMatrix()
end

function touched(t)
    addWater(10,t.x,t.y)
end

function softImage(f,r,s)
    simg = image(r,r)
    setContext(simg)
    pushStyle()
    noStroke()
    fill(f)
    ellipse(simg.width/2,simg.height/2,simg.width)
    popStyle()
    setContext()
    limg = image(s,s)
    setContext(simg)
    pushStyle()
    smooth()
    sprite(simg,0,0,s,s)
    popStyle()
    setContext()
    return simg
end

function addWater(amnt,x,y)
    local posRand = 20
    local vRand = 50
    for i = 1,amnt do
        local b = physics.body(CIRCLE,2)
        b.x = x + math.random(-posRand,posRand)
        b.y = y + math.random(-posRand,posRand)
        b.linearVelocity = vec2(math.random(-vRand,vRand),math.random(-vRand,vRand))
        b.restitution = .1
        
        r = waterMesh:addRect(b.x,b.y,blur,blur)
        table.insert(water,{body = b, rect = r})
    end
end

```


As you can see, I apply the ripple effect to the shader, but it's applied to every rectangle instead.  
Thanks!

If I understand… most shaders are written to apply an effect based on the texture coordinates, eg the ripple, which as you say will ripple each rectangle.

What you want to do is have a mesh, but apply an effect like ripple (as an example) to the whole mesh based on the distance of each pixel from the centre of the mesh regardless of the texture coordinates?

That should be doable, I can run up an example tonight if that is what you desire.

@Zoyt a shader cannot effect outside the bounds of a mesh’s polygons — in order to do that you would have to create the effect as you describe: render the mesh into an image and then use that image on another mesh with the shader applied.

@Zoyt, ah, I understand what you are trying to accomplish now. It really looks like what you described in your first post is the only way to go about it, which seems to also be what @Simeon described above.

I noticed something a bit odd with that code. You had the water objects being 50% destroyed when they go off screen or get close to off screen. By 50% I mean that you destroyed the physics body, but you never actually removed them from the mesh. Easy way to guarantee that they are removed from the mesh is to use your table to create the rectangles every frame and then also clear the mesh every frame. This got rid of the water objects that get stuck on the sides of the screen.

    for i,v in ipairs(water) do
        --ellipse(v.body.x,v.body.y,v.body.radius*2)
        --sprite(waterimage,v.body.x,v.body.y,blur,blur)
        waterMesh:addRect(v.body.x,v.body.y,blur,blur)
        if v.body.x <= 0 or v.body.x >= WIDTH or v.body.y <= 0 or v.body.y >= HEIGHT then
            table.remove(water,i)
            v.body:destroy()
        end
    end
 
    waterMesh:draw()
    waterMesh:clear()

Edit: you can also remove r = waterMesh:addRect(b.x,b.y,blur,blur) from addWater and you no longer need to store the rect in your water table. I also moved waterMesh.shader.freq = 10 into setup as it only needs to be called once since the frequency is hard coded to 10.

I modified your code a bit more to draw the mesh into another image and then set the shader to that image. The ripple effect makes it look crazy, but a different shader or modifying the ripple effect may give you the effect desired.

displayMode(FULLSCREEN)
 
function setup()
    fixtures = {}
    local gSize = vec2(WIDTH,HEIGHT/2)
    local gScale = 5
    local funnleSize = 5
    funnle1 = physics.body(CHAIN,false,vec2(-gSize.x,gSize.y),
                                vec2(-funnleSize,-gSize.y/3),
                                vec2(-funnleSize,-gSize.y)
                                )
    local funnle2 = physics.body(CHAIN,false,vec2(gSize.x,gSize.y),
                                vec2(funnleSize,-gSize.y/3),
                                vec2(funnleSize,-gSize.y)
                                )
    local split = physics.body(CHAIN,false,vec2(-WIDTH/2,-HEIGHT/8),
                                vec2(0,HEIGHT/80),
                                vec2(WIDTH/2,-HEIGHT/8)
                                )
    funnle1.x = WIDTH / 2
    funnle1.y = HEIGHT - HEIGHT / 4
    funnle1.type = STATIC
    funnle2.x = WIDTH / 2
    funnle2.y = HEIGHT - HEIGHT / 4
    funnle2.type = STATIC
    split.x = WIDTH / 2
    split.y = HEIGHT/8
    split.type = STATIC
    split.restitution = .3
    table.insert(fixtures,funnle1)
    table.insert(fixtures,funnle2)
    table.insert(fixtures,split)
 
 
    blur = 15
    water = {}
    waterimage = softImage(color(0, 155, 255),5,blur)
    allWater = image(WIDTH,HEIGHT)
 
    waterMesh = mesh()
    --waterMesh.shader = shader("Effects:Ripple")
    --waterMesh.shader.freq = 10
    --[[waterMesh.shader.smoothness = 0
    waterMesh.shader.threshold = .9
    waterMesh.shader.unpremultiply = 1]]--
    waterMesh.texture = waterimage
end
 
 
function draw()
    --waterMesh.shader.time = ElapsedTime
    physics.gravity(Gravity)
    smooth()
    
    setContext(allWater)
    background(40, 40, 50)
    noStroke()
    fill(0, 255, 253, 255)
    tint(0, 16, 255, 255)
    smooth()
    for i,v in ipairs(water) do
        --ellipse(v.body.x,v.body.y,v.body.radius*2)
        --sprite(waterimage,v.body.x,v.body.y,blur,blur)
        waterMesh:addRect(v.body.x,v.body.y,blur,blur)
        if v.body.x <= 0 or v.body.x >= WIDTH or v.body.y <= 0 or v.body.y >= HEIGHT then
            table.remove(water,i)
            v.body:destroy()
        end
    end
 
    waterMesh:draw()
    waterMesh:clear()
    setContext()
    
    m = mesh()
    m.texture = allWater
    --m.shader = shader("Effects:Ripple")
    --m.shader.time = ElapsedTime
    --m.shader.freq = 10
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m:draw()
    m:clear()
 
    strokeWidth(3)
    for i,v in ipairs(fixtures) do
        pushMatrix()
        translate(v.x,v.y)
        local points = v.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
        popMatrix()
    end
    popMatrix()
end
 
function touched(t)
    addWater(10,t.x,t.y)
end
 
function softImage(f,r,s)
    simg = image(r,r)
    setContext(simg)
    pushStyle()
    noStroke()
    fill(f)
    ellipse(simg.width/2,simg.height/2,simg.width)
    popStyle()
    setContext()
    limg = image(s,s)
    setContext(simg)
    pushStyle()
    smooth()
    sprite(simg,0,0,s,s)
    popStyle()
    setContext()
    return simg
end
 
function addWater(amnt,x,y)
    local posRand = 20
    local vRand = 50
    for i = 1,amnt do
        local b = physics.body(CIRCLE,2)
        b.x = x + math.random(-posRand,posRand)
        b.y = y + math.random(-posRand,posRand)
        b.linearVelocity = vec2(math.random(-vRand,vRand),math.random(-vRand,vRand))
        b.restitution = .1
        table.insert(water,{body = b})
    end
end

Actually, the ripple effect doesn’t look bad with lower frequencies. With the modified code above, try this shader effect.

    m = mesh()
    m.texture = allWater
    m.shader = shader("Effects:Ripple")
    m.shader.time = ElapsedTime
    m.shader.freq = 0.1
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m:draw()
    m:clear()

Now, to get ripple to propagate from the bottom of the screen instead of the center you can modify the ripple fragment shader with this code:

    highp vec2 tc = vTexCoord.xy;
    highp vec2 p = vec2(0.0,tc.y);
    highp float len = length(p);
    highp vec2 uv = tc + (p/len)*freq*cos(len*24.0-time*4.0)*0.03;
    highp vec4 col = texture2D(texture,uv);

This makes just horizontal ripples flow upwards from the bottom of the screen, giving us the effect of water moving up and down. A perfect effect for 2D water :smiley:

@Slashin8r - Thanks a lot for the help. I’m not going for the ripple effect, I just put it there because I didn’t feel like sharing my alpha threshold shader I was using. Anyways, I’m almost done with the code, but the alpha threshold shader is glitching out in program, but not in the shader lab. It’s not really related to the original topic, but I thought you might be willing to help anyways. Here’s the code:

displayMode(FULLSCREEN)

function setup()
    fixtures = {}
    local gSize = vec2(WIDTH,HEIGHT/2)
    local gScale = 5
    local funnleSize = 5
    funnle1 = physics.body(CHAIN,false,vec2(-gSize.x,gSize.y),
                                vec2(-funnleSize,-gSize.y/3),
                                vec2(-funnleSize,-gSize.y)
                                )
    local funnle2 = physics.body(CHAIN,false,vec2(gSize.x,gSize.y),
                                vec2(funnleSize,-gSize.y/3),
                                vec2(funnleSize,-gSize.y)
                                )
    local split = physics.body(CHAIN,false,vec2(-WIDTH/2,-HEIGHT/8),
                                vec2(0,HEIGHT/80),
                                vec2(WIDTH/2,-HEIGHT/8)
                                )
    funnle1.x = WIDTH / 2
    funnle1.y = HEIGHT - HEIGHT / 4
    funnle1.type = STATIC
    funnle2.x = WIDTH / 2
    funnle2.y = HEIGHT - HEIGHT / 4
    funnle2.type = STATIC
    split.x = WIDTH / 2
    split.y = HEIGHT/8
    split.type = STATIC
    split.restitution = .3
    table.insert(fixtures,funnle1)
    table.insert(fixtures,funnle2)
    table.insert(fixtures,split)


    blur = 15
    water = {}
    waterimage = softImage(color(0, 155, 255),5,blur)
    allWater = image(WIDTH,HEIGHT)

    --[[waterMesh = mesh()
    waterMesh.texture = waterimage]]--
    
    mDisplay = mesh()
    mDisplay.texture = allWater
    mDisplay:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    mDisplay.shader = shader(alphaThresholdShader())--shader("Documents:Alpha Threshold")
    mDisplay.shader.smoothness = 0
    mDisplay.shader.threshold = .5
    mDisplay.shader.unpremultiply = 1
end


function draw()
    
    
    physics.gravity(Gravity)
    smooth()

    setContext(allWater)
    background(255, 0, 0, 255)
    noStroke()
    smooth()
    for i,v in ipairs(water) do
        sprite(waterimage,v.body.x,v.body.y,blur,blur)
        --waterMesh:addRect(v.body.x,v.body.y,blur*extraSpace,blur*extraSpace)
        if v.body.x <= 0 or v.body.x >= WIDTH or v.body.y <= 0 or v.body.y >= HEIGHT then
            table.remove(water,i)
            v.body:destroy()
        end
    end

    --waterMesh:draw()
    --waterMesh:clear()
    setContext()
    
    mDisplay.texture = allWater
    mDisplay:draw()

    strokeWidth(3)
    for i,v in ipairs(fixtures) do
        pushMatrix()
        translate(v.x,v.y)
        local points = v.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
        popMatrix()
    end
    popMatrix()
end

function touched(t)
    addWater(10,t.x,t.y)
end

function softImage(f,r,s)
    simg = image(r,r)
    setContext(simg)
    pushStyle()
    noStroke()
    fill(f)
    ellipse(simg.width/2,simg.height/2,simg.width)
    popStyle()
    setContext()
    limg = image(s,s)
    setContext(simg)
    pushStyle()
    smooth()
    sprite(simg,0,0,s,s)
    popStyle()
    setContext()
    return simg
end

function addWater(amnt,x,y)
    local posRand = 20
    local vRand = 50
    for i = 1,amnt do
        local b = physics.body(CIRCLE,2)
        b.x = x + math.random(-posRand,posRand)
        b.y = y + math.random(-posRand,posRand)
        b.linearVelocity = vec2(math.random(-vRand,vRand),math.random(-vRand,vRand))
        b.restitution = .1
        table.insert(water,{body = b})
    end
end

function alphaThresholdShader()
    return [[//
// A basic vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vTexCoord = texCoord;
    
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}]],
[[

precision highp float;

uniform lowp sampler2D samplerFront;

uniform float threshold;
uniform float smoothness;
uniform float unpremultiply;

varying vec2 vTexCoord;

void main()
{
    lowp vec4 color = texture2D( samplerFront, vTexCoord ) ;
    if( unpremultiply > 0.0 ) { color.rgb /= color.a ; }
    
    float range = ( color.a - (1.0 - threshold) - (smoothness * 0.05) ) / (0.0001 + smoothness * 0.1) ;
    color.a = smoothstep( 0.0, 1.0, range ) ;
    color.rgb *= color.a ;
    
    gl_FragColor = color ;
}]]
end

```

BTW, is it more efficient to draw individual sprites into an image, or create a mesh every frame and draw it?  
Thanks!

@Zoyt, according to a previous update I think it is now about the same either way you go about it. I still think meshes are more efficient, but I haven’t gone back to sprites to test how efficient they may be now.

Going to test out your code and see what I can come up with.

@Slashin8r - Thanks.

Wow, I can’t figure out for the life of me what is happening with your shader. I can see the effect in the shader lab like you described, but in the program it is just placing a huge blue ellipse in the middle of the screen. If I change it to the modified ripple effect, it works as expected. This is very strange. I don’t get why it places it in the middle since you are only modifying colors and not position…

@Slashin8r - Thanks for the help.
Simeon - Could this be a bug, or my issue?
Thanks!

@Zoyt Whenever I find that there’s a difference between what happens in the shader lab and what happens in my code it is inevitably because I haven’t set up all the parameters correctly. In the shader lab it assigns defaults but the main code doesn’t.

In your case, in the shader you refer to a uniform texture samplerFront but in the code you don’t set this, rather you set the texture. When I change samplerFront to texture then I get something like water droplets in a funnel, which is what I expect you were looking for.

So, no, not a bug.

@Andrew_Stacey, hah, I assumed we were allowed to change the variable names. I didn’t even think about changing it, lol.