Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Question: Applying shaders to the overall mesh image of rectangles

edited July 2013 in Questions Posts: 2,820

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!

Comments

  • Posts: 391

    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.

  • Posts: 391

    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.

  • edited July 2013 Posts: 391

    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.

  • IgnatzIgnatz Mod
    Posts: 5,396

    A shader always applies to the entire mesh anyway

  • Posts: 2,820

    @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!

  • Posts: 454

    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.

  • SimeonSimeon Admin Mod
    Posts: 4,882

    @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.

  • Posts: 391

    @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.

  • edited July 2013 Posts: 391

    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.

  • Posts: 391

    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
    
  • Posts: 391

    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()
    
  • edited July 2013 Posts: 391

    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 :D

  • Posts: 2,820

    @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!

  • Posts: 391

    @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.

  • Posts: 2,820

    @Slashin8r - Thanks.

  • Posts: 391

    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...

  • edited July 2013 Posts: 2,820

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

  • Posts: 2,161

    @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.

  • Posts: 391

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

  • edited July 2013 Posts: 2,820

    @Andrew_Stacey - Thanks a lot.

Sign In or Register to comment.