Meta balls shader

I am trying to get this working with Codea:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua

I have recorded a video but the image is flipped, dont know why exactly xD

Here is the Main.lua for Codea


balls    = {}
v        = {}
x        = 0
y        = 0
radius   = 66
width    = WIDTH-radius
height   = HEIGHT-radius
paused   = false
time     = 0
strobo   = false
m        = nil
--[[ 
original source code , love2d version:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua
]]--
displayMode(STANDARD)
function setup()
    parameter.number("schwelle",0.002, 2.0, 0.002)
    parameter.boolean("drawMesh",false)
    local i = 0
    for i=1,40 do
        balls[i]    = {math.random(0,width), math.random(0,height)}
        local v_ges = math.random(200,400)
        local v_x   = math.random(0,v_ges)
        local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
        v[i]        = {v_x, v_y}
    end
    
    m = mesh()
    m.texture = "Cargo Bot:Codea Icon"
    m.shader  = shader("Documents:MetaBall")
    rIdx = m:addRect(0, 0, 0, 0)
    m:setRectColor(i, 127,0,0)
    m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    m.shader.width    = width
    m.shader.height   = height
    m.shader.time     = time
    m.shader.schwelle = schwelle
end

function draw()
    background(0)
    strokeWidth(2)
    fill(17, 120, 223, 255)
    stroke(17, 120, 223, 255)
    local dt = DeltaTime
    time = time + dt
    if paused then
        return
    end
    for i=1,#balls do
        for j=1,#balls do
            if i ~= j then
                local left, right
                local bottom, top
                if balls[i][1] < balls[j][1] then
                    left = i
                    right = j
                else
                    left = j
                    right = i
                end
                if balls[i][2] < balls[j][2] then
                    bottom = i
                    top = j
                else
                    bottom = j
                    top = i
                end

                local x = balls[right][1]-balls[left][1]
                local y = balls[right][2]-balls[left][2]
                local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
                local f = 200000/math.pow(r,2) * dt
                local fx = x/r*f
                local fy = y/r*f
                balls[left][1] = balls[left][1] - fx
                balls[right][1] = balls[right][1] + fx
                balls[left][2] = balls[left][2] - fy
                balls[right][2] = balls[right][2] + fy
            end
        end
    end

    for i=1,#balls do
        balls[i][1] = balls[i][1] + v[i][1] * dt
        if (balls[i][1] < 10 and v[i][1] < 0) or (balls[i][1] >= width -10 and v[i][1] > 0) then
            v[i][1] = v[i][1] * (-1)
        end
        balls[i][2] = balls[i][2] + v[i][2] * dt
        if (balls[i][2] < 10 and v[i][2] < 0) or (balls[i][2] >= height-10 and v[i][2] > 0) then
            v[i][2] = v[i][2] * (-1)
        end
        if not drawMesh then
            ellipse(balls[i][1],balls[i][2],radius)
        end
    end

    if drawMesh then
        --local cw,ch = spriteSize(m.texture)
        --m:setRect(rIdx, WIDTH/2, HEIGHT/2, cw, ch) -- uncomment if texture size changes
    
        -- Configure out custom uniforms for the shader
        if strobo then
            m.shader.time = time --ElapsedTime
        end
        --m.shader.schwelle = schwelle
        --m.shader.width = cw
        --m.shader.height = ch
        m.shader.balls = balls
        
        -- Draw the mesh
        m:draw()
    end
end

function touched(touch)
    if touch.state == BEGAN then
        paused = not paused
        print("paused:",paused)
    end
end

This is the vertex program for the metaballs shader:


//
// A metaballs shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform float width;
uniform float height;
uniform float schwelle;
uniform vec2 balls[40];
uniform float time;
//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;
varying highp vec4 bColor;
float metaball(vec2 center, vec2 point) {
    vec2 v = point-center;
    return 1.0/dot(v, v);
}

vec4 effect(
    vec4 color, vec2 tex_c,vec2 coord
) {
        float val = 0.0;
        for (int i =0; i<40; i++) {
            val = val + metaball(balls[i],coord);
        }
        
    vec4 bar;
        
    if (val < schwelle) {
            val = val / schwelle;
            bar = vec4(
                0.8*(1.0-coord.x/width),
                0.8*(1.0-coord.y/height),
                0.3*val,
                1.0
            );
        }
        else {
            bar = vec4(
                0.8*coord.x/width,
                0.8*coord.y/height,
                0.0,1.0
            );
        }
        float foo = mod(time,1.0);
        if (foo < 0.1) {
         return bar + vec4(vec3(foo*3.14*50.0), 1.0);
        }   
    return bar;
}
void main()
{
    //Pass the mesh color to the fragment shader
    vColor    = color;
    vTexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
    //Pass the balls effect color to the fragment
    bColor    = effect(color, texCoord, vTexCoord);
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
    
}


and here it is the fragment program:


//
// A metaballs fragment shader
//

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;


//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
//balls effect
varying highp vec4 bColor;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = texture2D( texture, vTexCoord );

    //Set the output color to the texture color
    gl_FragColor = bColor+col;
    
}


Hope you smart people know How to fix this :wink:

Not quite the shader you are referring too, but something in that direction. Performance is poor all code is nicked off the web. Coder as string, so paste it all into main and it should run.

Only tested landscape ipad2



--[[ 
original source code , love2d version:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua
]]--
displayMode(STANDARD)
function setup()
    numballs = 20
    balls = {}
    v = {}
    x = 0
    y = 0
    radius = 66
    width = WIDTH-radius
    height = HEIGHT-radius
    paused = false
    
    parameter.boolean("drawMesh",false)
    local i = 0
    for i=1,numballs do
        balls[i]    = vec3(math.random(0,width), math.random(0,height), radius)
        local v_ges = math.random(200,400)
        local v_x   = math.random(0,v_ges)
        local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
        v[i]        = {v_x, v_y}
    end
    
    m = mesh()
    m.texture = "Cargo Bot:Codea Icon"
    m.shader  = shader(plasmaShader.vertexShader, plasmaShader.fragmentShader)
    rIdx = m:addRect(0, 0, 0, 0)
    m:setRectColor(i, 127,0,0)
    m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    m.shader.u_width    = WIDTH
    m.shader.numballs = numballs
end

function draw()
    background(0)
    output.clear()
    print(1/DeltaTime)
    strokeWidth(2)
    fill(17, 120, 223, 255)
    stroke(17, 120, 223, 255)
    local dt = DeltaTime/5
    if paused then
        return
    end
    for i=1,numballs do
        for j=1,numballs do
            if i ~= j then
                local left, right
                local bottom, top
                if balls[i][1] < balls[j][1] then
                    left = i
                    right = j
                else
                    left = j
                    right = i
                end
                if balls[i][2] < balls[j][2] then
                    bottom = i
                    top = j
                else
                    bottom = j
                    top = i
                end

                local x = balls[right][1]-balls[left][1]
                local y = balls[right][2]-balls[left][2]
                local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
                local f = 200000/math.pow(r,2) * dt
                local fx = x/r*f
                local fy = y/r*f
                balls[left][1] = balls[left][1] - fx
                balls[right][1] = balls[right][1] + fx
                balls[left][2] = balls[left][2] - fy
                balls[right][2] = balls[right][2] + fy
            end
        end
    end

    for i=1,numballs do
        balls[i][1] = balls[i][1] + v[i][1] * dt
        if (balls[i][1] < 10 and v[i][1] < 0) or (balls[i][1] >= width -10 and v[i][1] > 0) then
            v[i][1] = v[i][1] * (-1)
        end
        balls[i][2] = balls[i][2] + v[i][2] * dt
        if (balls[i][2] < 10 and v[i][2] < 0) or (balls[i][2] >= height-10 and v[i][2] > 0) then
            v[i][2] = v[i][2] * (-1)
        end
        if not drawMesh then
            ellipse(balls[i][1],balls[i][2],balls[i][3])
        end
    end
    
    if drawMesh then
        m.shader.balls = balls
        
        -- Draw the mesh
        m:draw()
    end
end


function touched(touch)
    if touch.state == BEGAN then
        paused = not paused
        print("paused:",paused)
    end
end

plasmaShader = {
vertexShader = [[


//
// A metaballs 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 vec2 texCoord;

varying vec2 vtexCoord;
void main()
{
    vtexCoord = texCoord;
    gl_Position = modelViewProjection * position;
    
}

]],
fragmentShader = [[


//
// A metaballs fragment shader
//

//This represents the current texture on the mesh
uniform highp vec3 balls[20];
precision mediump float;
  
uniform float u_width;
uniform int numballs;

varying vec2 vtexCoord;
 
float energyField(vec2 p, float iso)
{
    float en = (balls[0].z / max(0.0001, length(balls[0].xy - p)))
    +(balls[1].z / max(0.0001, length(balls[1].xy - p)))
    +(balls[2].z / max(0.0001, length(balls[2].xy - p)))
    +(balls[3].z / max(0.0001, length(balls[3].xy - p)))
    +(balls[4].z / max(0.0001, length(balls[4].xy - p)))
    +(balls[5].z / max(0.0001, length(balls[5].xy - p)))
    +(balls[6].z / max(0.0001, length(balls[6].xy - p)))
    +(balls[7].z / max(0.0001, length(balls[7].xy - p)))
    +(balls[8].z / max(0.0001, length(balls[8].xy - p)))
    +(balls[9].z / max(0.0001, length(balls[9].xy - p)))
    +(balls[10].z / max(0.0001, length(balls[10].xy - p)))
    +(balls[11].z / max(0.0001, length(balls[11].xy - p)))
    +(balls[12].z / max(0.0001, length(balls[12].xy - p)))
    +(balls[13].z / max(0.0001, length(balls[13].xy - p)))
    +(balls[14].z / max(0.0001, length(balls[14].xy - p)))
    +(balls[15].z / max(0.0001, length(balls[15].xy - p)))
    +(balls[16].z / max(0.0001, length(balls[16].xy - p)))
    +(balls[17].z / max(0.0001, length(balls[17].xy - p)))
    +(balls[18].z / max(0.0001, length(balls[18].xy - p)))
    +(balls[19].z / max(0.0001, length(balls[19].xy - p))); 
    return (en - iso);
}
 
void main()
{
    float power = energyField(vtexCoord.xy * u_width, 3.0);
    // got the power, now add our 'flourish'...
    float hwidth = u_width/2.0;
    // rescale from 0 -> width to -1 -> 1
    float left = 1.0 - (vtexCoord.x/hwidth);
    left = (left * left) * (left * left);
    gl_FragColor = vec4(power-(left*2.0), power-left, power-left, left+0.5);
}


]]}

Not really sure what the effect should look like, so this is just a pointer. Main things: the shader looks like a fragment shader, not a vertex one. And you passed the balls’ centres as an array of arrays of floats, not an array of vec2s.


balls    = {}
v        = {}
x        = 0
y        = 0
radius   = 66
width    = WIDTH-radius
height   = HEIGHT-radius
paused   = false
time     = 0
strobo   = false
m        = nil
--[[ 
original source code , love2d version:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua
]]--
displayMode(STANDARD)
function setup()
    parameter.number("schwelle",0.002, 2.0, 0.002)
    parameter.boolean("drawMesh",false)
    local i = 0
    for i=1,40 do
        balls[i]    = vec2(math.random(0,width),
                            math.random(0,height))
        local v_ges = math.random(200,400)
        local v_x   = math.random(0,v_ges)
        local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
        v[i]        = {v_x, v_y}
    end
    
    m = mesh()
    m.texture = "Cargo Bot:Codea Icon"
    local s = shader("Documents:Meta Ball")
    --s.vertexProgram, s.fragmentProgram = mbshader()
    m.shader  = s -- shader("Documents:MetaBall")
    rIdx = m:addRect(0, 0, 0, 0)
    m:setRectColor(i, 127,0,0)
    m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    m.shader.width    = width
    m.shader.height   = height
    m.shader.time     = time
    m.shader.schwelle = schwelle
end

function draw()
    background(0)
    strokeWidth(2)
    fill(17, 120, 223, 255)
    stroke(17, 120, 223, 255)
    local dt = DeltaTime
    time = time + dt
    if paused then
        return
    end

    for i=1,#balls do
        for j=1,#balls do
            if i ~= j then
                local left, right
                local bottom, top
                if balls[i].x < balls[j].x then
                    left = i
                    right = j
                else
                    left = j
                    right = i
                end
                if balls[i].y < balls[j].y then
                    bottom = i
                    top = j
                else
                    bottom = j
                    top = i
                end

                local x = balls[right].x-balls[left].x
                local y = balls[right].y-balls[left].y
                local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
                local f = 200000/math.pow(r,2) * dt
                local fx = x/r*f
                local fy = y/r*f
                balls[left].x = balls[left].x - fx
                balls[right].x = balls[right].x + fx
                balls[left].y = balls[left].y - fy
                balls[right].y = balls[right].y + fy
            end
        end
    end

    for i=1,#balls do
        balls[i].x = balls[i].x + v[i][1] * dt
        if (balls[i].x < 10 and v[i][1] < 0) or (balls[i].x >= width -10 and v[i][1] > 0) then
            v[i][1] = v[i][1] * (-1)
        end
        balls[i].y = balls[i].y + v[i][2] * dt
        if (balls[i].y < 10 and v[i][2] < 0) or (balls[i].y >= height-10 and v[i][2] > 0) then
            v[i][2] = v[i][2] * (-1)
        end
        if not drawMesh then
            ellipse(balls[i].x,balls[i].y,radius)
        end
    end

    if drawMesh then
        local cw,ch = spriteSize(m.texture)
        --m:setRect(rIdx, WIDTH/2, HEIGHT/2, cw, ch) -- uncomment if texture size changes
    
        -- Configure out custom uniforms for the shader
        if strobo then
            m.shader.time = time --ElapsedTime
        end
        m.shader.schwelle = schwelle
        m.shader.width = cw
        m.shader.height = ch
        m.shader.balls = balls
        
        -- Draw the mesh
        m:draw()
    end
end

function touched(touch)
    if touch.state == BEGAN then
        paused = not paused
        print("paused:",paused)
    end
end

Vertex shader

//
// A metaballs 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;
    
}

Fragment shader


//
// A metaballs fragment shader
//
precision highp float;
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform float width;
uniform float height;
uniform float schwelle;
uniform vec2 balls[40];
uniform float time;
highp vec2 size = vec2(width,height);
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;


//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

float metaball(vec2 center, vec2 point) {
    vec2 v = size*point-center;
    float l = dot(v,v);
    if (l < .05) return 2.;
    return .1/l;
}

lowp vec4 effect(
  highp vec2 coord
) {
        float val = 0.0;
        for (int i =0; i<40; i++) {
            val = val + metaball(balls[i],coord);
        }

    vec4 bar;
        
    if (val < schwelle) {
            val = val / schwelle;
            bar = vec4(
                0.8*(1.0-coord.x),
                0.8*(1.0-coord.y),
                0.3*val,
                1.0
            );
        }
        else {
            bar = vec4(
                0.8*coord.x,
                0.8*coord.y,
                0.0,1.0
            );
        }
        //float foo = mod(time,1.0);
        //if (foo < 0.1) {
         //return bar + vec4(vec3(foo*3.14*50.0), 1.0);
        //}   
    return bar;
}

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = texture2D( texture, vTexCoord );
    //Pass the balls effect color to the fragment
    lowp vec4 bColor    = effect(vTexCoord);
    //Set the output color to the texture color
    gl_FragColor = bColor*col;
    
}

.@spacemonkey you did it!!
Here is a video

http://www.youtube.com/watch?v=41xcMGpf2u0

.@andrew_stacey ok, I understand now thanks

Here it is another approximation using this shader:
http://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html

WebGL:
http://glsl.heroku.com/e#6920.0

Lua code:


displayMode(FULLSCREEN)
function setup()
    numballs = 12
    balls = {}
    v = {}
    x = 0
    y = 0
    radius = 66
    width  = WIDTH
    height = HEIGHT
    paused = false
    drawMesh = true
    
    
    local i = 0
    for i=1,numballs do
        balls[i]    = vec2(math.random(0,width), math.random(0,height))
        local v_ges = math.random(333,666)
        local v_x   = math.random(0,v_ges)
        local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
        v[i]        = {v_x, v_y}
    end
 
    m = mesh()
    --m.texture = "Cargo Bot:Codea Icon"
    m.shader  = shader("Documents:Metaballs2d")
    rIdx = m:addRect(0, 0, 0, 0)
    m:setRectColor(i, 127,0,0)
    m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    m.shader.u_width    = WIDTH
    m.shader.numballs = numballs
end
 
function draw()
    background(0)
    output.clear()
    --print(1/DeltaTime)
    strokeWidth(2)
    fill(17, 120, 223, 255)
    stroke(17, 120, 223, 255)
    local dt = DeltaTime/5
    
    if paused then
        return
    end
    for i=1,numballs do
        for j=1,numballs do
            if i ~= j then
                local left, right
                local bottom, top
                if balls[i][1] < balls[j][1] then
                    left = i
                    right = j
                else
                    left = j
                    right = i
                end
                if balls[i][2] < balls[j][2] then
                    bottom = i
                    top = j
                else
                    bottom = j
                    top = i
                end
 
                local x = balls[right][1]-balls[left][1]
                local y = balls[right][2]-balls[left][2]
                local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
                local f = 200000/math.pow(r,2) * dt
                local fx = x/r*f
                local fy = y/r*f
                balls[left][1] = balls[left][1] - fx
                balls[right][1] = balls[right][1] + fx
                balls[left][2] = balls[left][2] - fy
                balls[right][2] = balls[right][2] + fy
            end
        end
    end
 
    for i=1,numballs do
        balls[i][1] = balls[i][1] + v[i][1] * dt
        if (balls[i][1] < 10 and v[i][1] < 0) or (balls[i][1] >= width -10 and v[i][1] > 0) then
            v[i][1] = v[i][1] * (-1)
        end
        balls[i][2] = balls[i][2] + v[i][2] * dt
        if (balls[i][2] < 10 and v[i][2] < 0) or (balls[i][2] >= height-10 and v[i][2] > 0) then
            v[i][2] = v[i][2] * (-1)
        end
        if not drawMesh then
          ellipse(balls[i][1],balls[i][2],radius)
        end
    end
 
    if drawMesh then
        m.shader.balls = balls
        
        -- Draw the mesh
        m:draw()
    end
end
 
 
function touched(touch)
    if touch.state == BEGAN then
        --paused = not paused
        print("paused:",paused)
    elseif touch.state == MOVING then
        balls[11][1] = touch.x
        balls[11][2] = touch.y
    end
end

Vertex Shader part:

//
// A metaballs 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 vec2 texCoord;
 
varying vec2 vtexCoord;
void main()
{
    vtexCoord = texCoord;
    gl_Position = modelViewProjection * position;
 
}
 

Fragment Shader Part:

//
// A metaballs fragment shader
//
 
//This represents the current texture on the mesh
uniform int numballs;
uniform highp vec2 balls[12];
precision mediump float;
varying vec2 vtexCoord;
 
void main()
{
    float sum = 0.0;
    float size = 66.0;
    float r = 23.0;
    float g = 0.66;
    for (int i = 0; i <= numballs; ++i) {
        
        float dist = length(gl_FragCoord.xy - balls[i]);
        
        sum += size / pow(dist, g);
    }
    
    vec3 color = vec3(0,0,0);
    if (sum>r) color = vec3(r/sum,r/sum,1);
 
    
    gl_FragColor = vec4(color, 1);
}

And the video:
http://www.youtube.com/watch?v=YyUHQCyHE0w

OK, now we have physics, we can say this is the first fluid simulation in Codea :slight_smile:

-- 2DWater
supportedOrientations(LANDSCAPE_RIGHT)
-- Use this function to perform your initial setup
function setup()
    numDrops = 12
    drops    = {}
    radius   = 34
    rradius  = radius*2
    local d  = radius*6
    wallWidth= 6
    -- create ground 
    ground = physics.body(POLYGON, vec2(0,wallWidth), 
        vec2(0,0), vec2(WIDTH,0), vec2(WIDTH,wallWidth))
    ground.type = STATIC
    -- create roof
     roof = physics.body(POLYGON, 
            vec2(0,HEIGHT), 
            vec2(0,HEIGHT-wallWidth), 
            vec2(WIDTH,HEIGHT-wallWidth), vec2(WIDTH,HEIGHT)
    )
    roof.type = STATIC
    -- create walls
    lwall = physics.body(POLYGON, vec2(0,HEIGHT), vec2(0,0), vec2(-2,0), vec2(-2,HEIGHT))
    lwall.type = STATIC
    rwall = physics.body(POLYGON, vec2(WIDTH+1,HEIGHT), 
        vec2(WIDTH+1,0), 
        vec2(WIDTH,0), vec2(WIDTH,HEIGHT))
    rwall.type = STATIC
    effect   = mesh()
    effect.shader = shader("Documents:Metaballs2d")
    for i = 1, numDrops do
        drops[i] = physics.body(CIRCLE, radius )
        drops[i].x = math.random(d,WIDTH - d)
        drops[i].y = math.random(d,HEIGHT - d)
        drops[i].type = DYNAMIC
        drops[i].interpolate = true
        drops[i].restitution = 0.25
        drops[i].sleepingAllowed = false
    end
    effect.shader.numballs = numDrops
  --  effect.shader.balls = drops
    rIdx = effect:addRect(0,0,0,0)
    effect:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    parameter.boolean("UseShader",false)
    defaultGravity = physics.gravity()
    parameter.boolean("UseGravity",false)
end
 
function mpos()
    r = {}
    for i= 1, numDrops do
       r[i] = drops[i].position
    end
    return r
end
 
function drawWall(w)
    local points = w.points
    for j=1, #points do
        a = points[j]
        b = points[(j % #points)+1]
        line(a.x,a.y,b.x,b.y)
    end
end
 
-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
 
    -- This sets the line thickness
    strokeWidth(5)
    stroke(255)
    -- chose drawing system
    if UseShader then
        effect:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
        effect.shader.balls = mpos()
        effect:draw()
    else    
        for i=1, numDrops do
          ellipse(drops[i].x,drops[i].y,rradius)
        end
    end
    if UseGravity then
        physics.gravity (Gravity*10)
    else
        physics.gravity ( defaultGravity)
    end
    -- draw ground
    drawWall(ground)
end

The shader is the same of my last post here.

This is the result in the video:
http://www.youtube.com/watch?v=NV7Amd-RSFg

  1. there are only balls with default Gravity
  2. iPad accelerometer gravity activated
  3. glsl shader activate
    moving around, water simulation ready :wink:

That’s fantastic!

Thanks @Jmv38 ,has anyone tried this webgl shader?
http://madebyevan.com/webgl-water/
wouldnt be amazing to have this working with our beloved Codea? :))

That’s a fairly hectic example. It contains multiple shaders, the biggest barrier is it uses a couple of capabilities which we don’t have. Specifically it uses textures in vertex shaders which the iPad doesn’t support. It looks like it uses some cube maps which I’m not sure if codea supports. Also it uses some extensions such as OES_Texture_Float and I’m not sure whether ipad has this or not and if so how you use it via codea.

But other than that, it would be amazing :wink:

I understand, maybe we just need to understand how this kind of shaders ( vertex, fragment and pixel shaders…because we cant use geometry shaders with a good framerate ) works to replicate the behaviour of the surface in the water of this video (3d):

http://www.youtube.com/watch?v=e6Pa94y-85c

Shaders explained here in this video at 1:35

http://www.youtube.com/watch?v=to3T6IwXeWc

You can create a fairly simple moving water effect by using noise and overlaying a slightly transparent rectangle. The video below shows the result using the noise demo project with a single line of code to overlay a rect, showing the effect of using different colours and alpha values to create water, cloud, dust, etc. it’s amazing how much it improves the basic noise effect.

https://www.youtube.com/watch?v=ThmMEZIzdIo

NB the original looks better than the video

@Ignatz I think that is faster but with low res , do you think that we can achieve to traslate this
http://www.bonzaisoftware.com/water_tut.html#glsl

to Codea + GLSL Shaders?

or this one!:

I think Codea is going to struggle with detailed 3D surface rendering, but I am not an expert in this, so don’t take my word for it.

This is a new version based on processing, this time I’m using the virtual space of an image to read/write values of the metaball…it is still too slow for gaming…
Opinions?

function setup()
  metaball = Metaball()
end
 
function draw()
	background(0)
	metaball:draw()
end
 
    --[[
    ?* @Original code Info
    ?* 
    ?* from Proce55ing
    ?* Metaball Demo Effect
    ?* by luis2048. 
     * 
    ]]--
Metaball = class()
TOTAL_BALL_COUNT = 6
STAGE_W  = WIDTH
STAGE_H  = HEIGHT
BT_WIDTH = 80
BT_HEIGHT= 80
 
function Metaball:init()
	self.bitmap = image(BT_WIDTH,BT_HEIGHT)
	self.bitmapData = self.bitmap.data
	self.balls_ary  = {}
	self.count = 0
	self.mouseX = 0
	self.mouseY = 0
	for i = 0 , TOTAL_BALL_COUNT do
		self.balls_ary[i] = Ball(
			vec2(random1(), random1()),
			vec2(math.random(1,BT_WIDTH) , math.random(1,BT_HEIGHT)),
			self
		)
					                        
	end
	
	self.width  = STAGE_W
	self.height = STAGE_H
	count = Math.random()*10
	self:bomb()
end
	
function Metaball:touched(touch)
	if touch.state = ENDED then
		self.count = self.count + 1
		self:bomb()
	end
end
	
function Metaball:bomb()
	for i = 0 , TOTAL_BALL_COUNT  do
		self.balls_ary[i]:bomb()
	end
end
	
function Metaball:draw(evt:Event):void {
		for i = 0 , TOTAL_BALL_COUNT do
			self.balls_ary[i]:update()
		end
		self:render()
end
	
function Metaball:render()
		--bitmapData.lock()
		for y = 0 , BT_HEIGHT do
			for x = 0 , BT_WIDTH do
				local pixelsValue = 0
				for i = 0,  TOTAL_BALL_COUNT do
					local ball = self.balls_ary[i]
					pixelsValue = pixelsValue + ball.currentRadius / (1 + ball:getPixelValue(x, y))
				end
				
				self.bitmapData:set(x, y, self:convertRGBColor(pixelsValue))
			end
		end
		--bitmapData.unlock()
		sprite(self.bitmap, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end
	
function Metaball:convertRGBColor(pixelsValue)
		local c   = math.fmod(count, 6)
		if c == 0 then
			return self:getRGB(0, pixelsValue/2 , pixelsValue)
		elseif c == 1 then
			return self:getRGB( pixelsValue, pixelsValue / 2, 0)
		elseif c == 2 then
			return self:getRGB( pixelsValue, pixelsValue/3, pixelsValue/2)
		elseif c == 3 then 
			return self:getRGB( pixelsValue/2, pixelsValue*0.8, pixelsValue/5) 
		elseif c == 4 then
			return self:getRGB( pixelsValue*0.8, pixelsValue/4, pixelsValue/7)
		elseif c == 5 then
			return self:getRGB(pixelsValue/6, pixelsValue/3 , pixelsValue*0.8)
		end
end			
 
function Metaball:shuffle(ary)
		local i = #ary
		while (i>0) do
			local j = math.floor(math.random()*(i+1))
			local t = ary[i]
			ary[i] = ary[j]
			ary[j] = t
			i = i - 1
		end
		return ary
end
	
function Metaball:getRGB(red , green , blue )
	return (math.min(red, 255)*2*16 or math.min(green, 255)*2*8 or math.min(blue, 255))
end
	
--[[function random(min , max)
		if (max == min) {
			return max
		}else if (max < min) {
			var _temp: Number = max
			max = min
			min = _temp
		}
		return Math.random() * (max - min) + min
	}
]]--
	
function Metaball:getPoint()
	return vec2(
		BT_WIDTH * ( self.mouseX / STAGE_W), 
		BT_HEIGHT* ( self.mouseY / STAGE_H)
	)
end
 
 
 
Ball = class ()
function Ball:init(vel , pos, metaballfather)
	self.pixelX_ary = {}
	self.pixelY_ary = {}
	self.velocity   = vel
	self.position   = pos
	self.friction   = vec2(0,0)
	self.metaball   = metaballfather
	self.maxRadius  = 0
	self.currentRadius = 0
	self:reset()
end
 
function Ball:update()
	self.currentRadius = self.currentRadius + (self.maxRadius - self.currentRadius) / 10.0
	self.position = self.position + self.velocity
	self.velocity = self.velocity - self.friction
	self:checkBorderline()
	self:setPixels()
end
 
function Ball:checkBorderline()
	if self.position.y > (BT_HEIGHT + 10) then
		self:reset()
		self.velocity.y = 1
		self.position.y = -10    
	elseif (self.position.y < -10) then
		self:reset()
		self.velocity.y = -1
		self.position.y = BT_HEIGHT + 10        
	end
	
	if (self.position.x > (BT_WIDTH+ 10)) then
		self:reset()
		self.position.x = -10
		self.velocity.x = 1
	else if (position.x < -10) then
		self:reset()
		self.position.x = BT_WIDTH + 10    
		self.velocity.x = -1            
	end
end
 
function Ball:reset()
	self.friction= vec2(random1() * math.random() / 50 , random1() * math.random() / 50)
	self.currentRadius = math.random( 5, 100)
	self.maxRadius = math.random(30000, 60000)
end
 
function Ball:bomb()
	self:reset()
	self.position = self.metaball.getPoint()
end
 
function Ball:setPixels()
	self.pixelX_ary = {}
	self.pixelY_ary = {}
	for (y = 0 , BT_HEIGHT ) do
		self.pixelY_ary[y] = ((self.position.y - y)*(self.position.y - y))
	end
	for (x= 0 , BT_WIDTH ) do
		self.pixelX_ary[x] = ((position.x - x)*(position.x - x))
	end
end
 
function Ball:getPixelValue(x , y)
	return self.pixelX_ary[x] + self.pixelY_ary[y]
end
    
function random1(pct)
	if pct == nil then pct =  0.5 end
	if math.random() < pct then
		return 1
	else
		return -1
	end
end

Video:
http://www.youtube.com/watch?v=VuTthIlycxE

I had a idea on this, it does the metaballs by first creating a texture representing the fall off for a point, then rendering all points addatively to an off screen image, and then finally using that image as a texture to render back to screen based on a threshold.

It’s a bit ugly (2 color) but the methodology could definitely be extended and it’s very fast. It’s also a little fuzzy round the edges…

displayMode(STANDARD)
function setup()
    numballs = 30
    balls = {}
    v = {}
    x = 0
    y = 0
    radius = 66
    width = WIDTH-radius
    height = HEIGHT-radius
    paused = false
    
    parameter.boolean("thresholded",true)
    parameter.number("threshold",0,1.0,0.7)
    local i = 0
    for i=1,numballs do
        balls[i]    = vec3(math.random(0,width), math.random(0,height), radius)
        local v_ges = math.random(200,400)
        local v_x   = math.random(0,v_ges)
        local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
        v[i]        = {v_x, v_y}
    end
    
    --create a texture for the falloff curve
    img = image(500,500)
    setContext(img)
    for i=250,1,-1 do
        --fill(255/100*(100-i),255)
        fill(255*1/((i/20)^2),255)
        ellipse(250,250,i*2)
    end
    
    ballSize = 500
    
    ballMesh = mesh()
    ballMesh.shader  = shader(aballShader.vertexShader, aballShader.fragmentShader)
    --ballMesh.shader = shader("Documents:layershade")
    ballMesh:addRect(0, 0, ballSize, ballSize)
    ballMesh:setRectTex(1, 0,0,1,1)
    ballMesh.texture = img
    --ballMesh:setRect(rIdx, 0, 0, ballSize, ballSize)
    firstPass = image(WIDTH,HEIGHT)
    secondPass = mesh()
    secondPass:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    secondPass:setRectTex(1,0,0,1,1)
    secondPass.texture = firstPass
    secondPass.shader = shader(thresholdShader.vertexShader, thresholdShader.fragmentShader)
end

function draw()
    output.clear()
    print(1/DeltaTime)
    local dt = DeltaTime/5
    if paused then
        return
    end
    if thresholded then
        setContext(firstPass) 
    end
    background(0)
    for i=1,numballs do

        balls[i][1] = balls[i][1] + v[i][1] * dt
        if (balls[i][1] < 10 and v[i][1] < 0) or (balls[i][1] >= width -10 and v[i][1] > 0) then
            v[i][1] = v[i][1] * (-1)
        end
        balls[i][2] = balls[i][2] + v[i][2] * dt
        if (balls[i][2] < 10 and v[i][2] < 0) or (balls[i][2] >= height-10 and v[i][2] > 0) then
            v[i][2] = v[i][2] * (-1)
        end
        
        translate(balls[i][1],balls[i][2])
        ballMesh:draw()
        resetMatrix()
    end
    if thresholded then
        setContext()
        background(0)
        secondPass.shader.threshold = threshold
        secondPass:draw()
    end
end


function touched(touch)
    if touch.state == BEGAN then
        paused = not paused
        print("paused:",paused)
    end
end

aballShader = {
vertexShader = [[
//
// 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 vec2 texCoord;

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

void main()
{
    //Pass the mesh color to the fragment shader
    vTexCoord = texCoord;
    
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
#extension GL_EXT_shader_framebuffer_fetch : require
//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = gl_LastFragData[0];
    
    col.xyz += texture2D( texture, vTexCoord ).xyz;
    col.a = 1.0;
    //Set the output color to the texture color
    gl_FragColor = col;
}
]] }

thresholdShader = {
vertexShader = [[
//
// 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 vec2 texCoord;

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

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

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform lowp float threshold;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = texture2D( texture, vTexCoord );
    
    if (col.r < threshold) {
        col = vec4(0.0,0.0,0.0,0.0);
    }
    else {
        col = vec4(1.0,1.0,1.0,1.0);
    }
    
    //Set the output color to the texture color
    gl_FragColor = col;
}
]] }

I wish I had seen this before I made my own version. I’ll post my version once I finish optimizing it.

@spacemonkey - what does this do?

extension GL_EXT_shader_framebuffer_fetch : require

@Ignatz it enables an extension beyond the base OpenGL ES capabilities. Extensions allow hardware manufacturers to add interesting things as options on their gear, but of course it reduces the compatability of the shader as it now only works on hardware that supports the extension.

Anyway, this specific one allows you to read data back from the current framebuffer. So in the fragment shader I can additively blend to the current screen, gl_LastFragData[0]; reads the current color of the pixel from the screen (or image if you are using setContext) when you are in the fragment shader.

Here’s another one, it takes my old bouncing balls, and applies the metaballs approach as above.



displayMode(STANDARD)
function setup()
    balls = {}
    touches = {}
    nextball = 1
        
    base = physics.body(EDGE, vec2(100,0), vec2(WIDTH-100,0))

    
    parameter.boolean("thresholded",true)
    parameter.number("threshold",0,1.0,0.7)
    
    --create a texture for the falloff curve
    img = image(500,500)
    setContext(img)
    for i=250,1,-1 do
        --fill(255/100*(100-i),255)
        fill(255*1/((i/20)^2),255)
        ellipse(250,250,i*2)
    end
    
    ballSize = 25
    
    ballMesh = mesh()
    ballMesh.shader  = shader(aballShader.vertexShader, aballShader.fragmentShader)
    --ballMesh.shader = shader("Documents:layershade")
    ballMesh:addRect(0, 0, ballSize, ballSize)
    ballMesh:setRectTex(1, 0,0,1,1)
    ballMesh.texture = img
    --ballMesh:setRect(rIdx, 0, 0, ballSize, ballSize)
    firstPass = image(WIDTH,HEIGHT)
    secondPass = mesh()
    secondPass:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    secondPass:setRectTex(1,0,0,1,1)
    secondPass.texture = firstPass
    secondPass.shader = shader(thresholdShader.vertexShader, thresholdShader.fragmentShader)
end

function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
    else
        --if touches[touch.id] == nil then
        touches[touch.id] = touch
        --end
    end    
end    

function touchActions()
    for k,v in pairs(touches) do 
        if CurrentTouch.state == ENDED then
            --if there are no current touches then we kill all current touches to avoid bugged ball producers
            touches[k] = nil
        else

            --add a new ball at the touch location
            size = math.random(1,20)
            tspot = physics.body(CIRCLE, size)
            tspot.position = vec2(v.x+math.random(-1,1), v.y+math.random(-1,1))
            tspot.restitution = 0.95

            balls[nextball] = { tspot = tspot, size = size * 2, r = math.random(30,255), g = math.random(30,255), b = math.random(30,255) }

            nextball = nextball + 1
        end    
    end
end

function draw()

    if thresholded then
        setContext(firstPass) 
    end
    background(0)
    for k,v in pairs(balls) do
        if v.tspot.x < -20 or v.tspot.x > WIDTH + 20 or v.tspot.y < -20 then
            balls[k].tspot:destroy()
            balls[k] = nil  
        else
            resetMatrix()
            translate(v.tspot.x,v.tspot.y)
            scale(v.size)

            --[[
            ballMesh.shader.mModel = modelMatrix()
            ballMesh.shader.vEyePosition = vec4(v.tspot.x,v.tspot.y,250,0)
            ballMesh.shader.vLightPosition = vec4(v.tspot.x,v.tspot.y,250,0)
            ]]
            ballMesh:setColors(color(v.r,v.g,v.b,255))

            ballMesh:draw()
            --fill(v.r, v.g, v.b, 255)
            --ellipse(v.tspot.x, v.tspot.y, v.size)
        end
    end
    if thresholded then
        resetMatrix()
        setContext()
        background(0)
        secondPass.shader.threshold = threshold
        secondPass:draw()
    end
    touchActions()
end

aballShader = {
vertexShader = [[
//
// 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 vec2 texCoord;

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

void main()
{
    //Pass the mesh color to the fragment shader
    vTexCoord = texCoord;
    
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
#extension GL_EXT_shader_framebuffer_fetch : require
//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = gl_LastFragData[0];
    
    col.xyz += texture2D( texture, vTexCoord ).xyz;
    col.a = 1.0;
    //Set the output color to the texture color
    gl_FragColor = col;
}
]] }

thresholdShader = {
vertexShader = [[
//
// 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 vec2 texCoord;

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

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

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform lowp float threshold;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = texture2D( texture, vTexCoord );
    
    if (col.r < threshold) {
        col = vec4(0.0,0.0,0.0,0.0);
    }
    else {
        col = vec4(1.0,1.0,1.0,1.0);
    }
    
    //Set the output color to the texture color
    gl_FragColor = col;
}
]] }

I only see a black screen running in an iPad2 with iOS 5.1 and Codea 1.5.4(2).
Error:
GL_EXT_shader_framebuffer_fetch not supported