Howdy, Stranger!

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

In this Discussion

Tagged

Meta balls shader
  • juaxixjuaxix
    Posts: 435

    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 ;-)

  • 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; }
  • juaxixjuaxix
    Posts: 435

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

    .@andrew_stacey ok, I understand now thanks

  • juaxixjuaxix
    Posts: 435

    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:

  • juaxixjuaxix
    Posts: 435

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

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

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

  • Jmv38Jmv38
    Posts: 2,118

    That's fantastic!

  • juaxixjuaxix
    Posts: 435

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

  • spacemonkeyspacemonkey
    Posts: 352

    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 ;-)

  • juaxixjuaxix
    Posts: 435

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

    Shaders explained here in this video at 1:35

  • IgnatzIgnatz
    Posts: 2,279

    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.

    NB the original looks better than the video

  • juaxixjuaxix
    Posts: 435

    @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 - GLSL program: http://forum.bonzaisoftware.com/viewthread.php?tid=10

    to Codea + GLSL Shaders?

    or this one!:

  • IgnatzIgnatz
    Posts: 2,279

    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.

  • juaxixjuaxix
    Posts: 435

    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:

  • spacemonkeyspacemonkey
    Posts: 352

    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;
    }
    ]] }
    
  • ZoytZoyt
    Posts: 2,609

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

  • IgnatzIgnatz
    Posts: 2,279

    @spacemonkey - what does this do?

    extension GL_EXT_shader_framebuffer_fetch : require

  • spacemonkeyspacemonkey
    Posts: 352

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

  • spacemonkeyspacemonkey
    Posts: 352

    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; } ]] }
  • juaxixjuaxix
    Posts: 435

    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

  • spacemonkeyspacemonkey
    Posts: 352

    Interesting. It's definitely supported hardware wise on iPad2 cos that's what I have, I guess it's iOS 5, I am running 6.

  • juaxixjuaxix
    Posts: 435

    Yes @spacemonkey ,that extension was added with iOS6 beta :)

  • SkyTheCoderSkyTheCoder
    Posts: 1,146

    After about two hours of coding, my first Metaball shader:

    -- Metaballs-4
    
    --blendMode(ADDITIVE)
    
    -- Use this function to perform your initial setup
    function setup()
        print("Hello World!")
    
        m = {}
    
        parameter.boolean("Use_Shader", true, function()
            if Use_Shader then
                m.shader = shader(vS, fS)
            else
                m.shader = nil
            end
        end)
    
        parameter.boolean("Animated", false)
    
        parameter.boolean("Show_Preview", true)
    
        parameter.integer("Number_of_Balls", 1, 1000, 25, function()
            balls = {}
            ballData = {}
            for i=1,Number_of_Balls do
                table.insert(balls, vec2(math.random(0, WIDTH), math.random(0, HEIGHT)))
                table.insert(ballData, vec2(math.random(-1, 1), math.random(-1, 1)))
            end
        end)
    
        parameter.integer("Metaball_Size", 1, 200, 100)
    
        parameter.integer("Metaball_Resolution", 1, 255, 255)
    
        parameter.action("Register Resolution (May lag!)", function()
            GENERATE_METABALL()
        end)
    
        parameter.integer("FPS_Smoothing", 1, 100, 50)
    
        parameter.action("Restart", function()
            balls = {}
            setContext(img)
            background(0)
            setContext()
            restart()
        end)
    
        function GENERATE_METABALL()
    
            local mr = Metaball_Resolution
            local ms = Metaball_Size
    
        blendMode(NORMAL)
    
        ballTex = image(200, 200)
        setContext(ballTex)
        pushStyle()
        noStroke()
        for i = 255, 0, -(256 - mr) do
        --[[
        fill(128)
        ellipse(100, 100, 100, 100)
        fill(191)
        ellipse(100, 100, 75, 75)
        fill(255)
        ellipse(100, 100, 25, 25)
        popStyle()
        --]]
    
        fill(255 - i, 255 - i)
        local K = 1000
        ellipse(100, 100, (i / 255) * ms, (i / 255) * ms)
    
        --print(i)
        --print(255 - i, 255 - i)
        --print((i / 255) * 100)
    
        --setContext()
        end
    
        setContext()
    
        end
    
        GENERATE_METABALL()
    
        --blendMode(ADDITIVE)
    
        balls = {}
        ballData = {}
        for i=1,Number_of_Balls do
            table.insert(balls, vec2(math.random(0, WIDTH), math.random(0, HEIGHT)))
            table.insert(ballData, vec2(math.random(-1, 1), math.random(-1, 1)))
        end
    
        img = image(WIDTH, HEIGHT)
    
        m = mesh()
        r = m:addRect(WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)
        m:setRectTex(r, 0, 0, 1, 1)
        m.texture = img
        if Use_Shader then
            m.shader = shader(vS, fS)
        end
    
        FPSHistory = {}
    end
    
    -- This function gets called once every frame
    function draw()
        blendMode(ADDITIVE)
    
        output.clear()
        table.insert(FPSHistory, 1 / DeltaTime)
    
        while #FPSHistory > FPS_Smoothing do
            table.remove(FPSHistory, 1)
        end
    
        local result = 0
    
        for k,v in ipairs(FPSHistory) do
            result = result + v
        end
    
        print(result / #FPSHistory)
    
        -- This sets a dark background color 
        background(0)
    
        -- This sets the line thickness
        strokeWidth(5)
    
        -- Do your drawing here
        if Animated then
            for i = 1, Number_of_Balls do
                local b = balls[i]
                local bd = ballData[i]
    
                if b.x > WIDTH then
                    bd.x = -math.abs(bd.x)
                end
    
                if b.x < 0 then
                    bd.x = math.abs(bd.x)
                end
    
                if b.y > HEIGHT then
                    bd.y = -math.abs(bd.y)
                end
    
                if b.y < 0 then
                    bd.y = math.abs(bd.y)
                end
    
                b.x = b.x + bd.x
                b.y = b.y + bd.y
            end
        end
    
        setContext(img)
        background(0)
        for k,v in ipairs(balls) do
            sprite(ballTex, v.x, v.y)
        end
        setContext()
        m.texture = img
        m:draw()
    
        if Show_Preview then
            blendMode(NORMAL)
    
            fill(0)
    
            stroke(127)
    
            strokeWidth(5)
    
            rect(0, 0, 200, 200)
    
            noStroke()
    
            sprite(ballTex, 100, 100)
    
            fill(255)
            font("Inconsolata")
            fontSize(12)
            textMode(CENTER)
            text("Metaball Compution Preview:", 100, 150)
        end
    end
    
    function touched(touch)
        if touch.state == BEGAN or touch.state == MOVING then
            if balls[Number_of_Balls + 1] == nil then
                table.insert(balls, vec2(touch.x, touch.y))
            else
                balls[Number_of_Balls + 1] = vec2(touch.x, touch.y)
            end
        end
    end
    
    vS = [[
    //
    // 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;
    }
    ]]
    
    fS = [[
    //
    // A basic fragment shader
    //
    
    //Default precision qualifier
    precision highp float;
    
    
    //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;
    
    void main()
    {
        //Sample the texture at the interpolated coordinate
        lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;
    
        //Set the output color to the texture color
        if (max(col.r, max(col.g, col.b)) > 0.75)
        {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
        else
        {
            gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
        }
    }
    ]]
    

    It only gets about 10 FPS... At a thousand Metaballs. :P

    Mine uses the simplest math to calculate bridges...none. It just draws a bunch of shaded balls on the screen with the ADDITIVE blend mode. Then it has a shader check if a pixel is bright enough, if it is it renders it white, if it isn't it renders it transparent. Turn off the shader using the parameter to see what I mean. The shader just filters out the gradients.

    Sorry for lots of commented-out lines and messy code. I'm too lazy to clean it up. :P

    Steady 50 FPS at a hundred Metaballs. ;)

  • juaxixjuaxix
    Posts: 435

    I really like this piece of code, since there is a generated texture you could add a noise to give more "water" appearance

  • SkyTheCoderSkyTheCoder
    Posts: 1,146

    Yep. I'm also gonna work on a shader for the screen, that replaces the white with a specific texture.

  • spacemonkeyspacemonkey
    Posts: 352

    Here's one where I encode color across the falloff (dark blue middle, light blue ring, then yellow falloff). Somethings not right that I haven't debugged, you might notice some strange internal artifacts, but it pretty much works. It's slower though... Probably would be better to colorise on a final pass perhaps.

    Code tweaked because I realised colorising the source falloff texture was unecessary and caused more calculation


    displayMode(STANDARD) function setup() displayMode(FULLSCREEN) balls = {} touches = {} nextball = 1 base = physics.body(EDGE, vec2(100,0), vec2(WIDTH-100,0)) --create a texture for the falloff curve size = 500 adjustment = 5 img = image(size,size) setContext(img) for i=size/2+adjustment,adjustment+1,-1 do fill(255*1/((i/15)^2),255) ellipse(size/2,size/2,(i-adjustment)*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) 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() background(0) --sprite(img,WIDTH/2,HEIGHT/2,1000,1000) 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 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; float numberize(lowp vec3 col) { float value; if (col.z == 0.0) { //this is level 1 value = col.x; } else if (col.x > 0.5) { //this is level 2 value = 1.0 + col.z; } else { //this is level 3 value = 2.0 + (1.0-(col.z - 0.5)*2.0); } return value; } lowp vec4 colorize(float value) { lowp vec4 col; if (value <= 1.0) { //level 1 col = vec4(value,value,0.0,1.0); } else if (value <= 2.0) { //level 2 col = vec4(1.0 - (value - 1.0)/2.0, 1.0 - (value - 1.0)/2.0, value - 1.0, 1.0); } else if (value <= 3.0) { col = vec4(0.49, 0.49, 1.0 - (value - 2.0)/2.0, 1.0); } else { col = vec4(0.49,0.49,0.49, 1.0); } return col; } void main() { //Sample the texture at the interpolated coordinate float curVal = numberize(gl_LastFragData[0].xyz); //curVal = curVal + numberize(texture2D( texture, vTexCoord ).xyz); curVal = curVal + texture2D( texture, vTexCoord ).x * 3.0; lowp vec4 col = colorize(curVal); //vec4(0.0,0.0,curVal,1.0); //col.xyz += texture2D( texture, vTexCoord ).xyz; //col.a = 1.0; //Set the output color to the texture color gl_FragColor = col; } ]] }
  • juaxixjuaxix
    Posts: 435

    .@spacemonkey I have exported the code to a project and then run it in an iPhone4 with iOS6.1.1 and it appears the next warning:

    Shader compile log:
    WARNING: 0:58: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
    

    But it worked!, so weird when the balls are created :D

    I cant understand how are you coloring the balls, where are you defining the "power of yellow"? I would like to change it and try with another layer in multiply mode...

    Here it is a video:

  • spacemonkeyspacemonkey
    Posts: 352

    @jauxix I think iPhone doesn't support high precision, change the shader line: precision highp float; to precision mediump float; and it might work.

    For the coloring it's the colorize and numberize that do the work. Effectively I treat the strength in a pixel as a number from 0.0 - 3.0. It encodes 0.0 - 1.0 as black through to bright yellow, then 1.0 - 2.0 as the yellow declining to half strength while blue comes to full strength, and finally 2.0 to 3.0 as the blue declining to half strength (ie ending in mid grey).

    Colorize builds the right color for this in a fairly clear way. The numberize is doing the reverse, but it looks at the various values of the color to decode it. So if the blue is 0.0 then I know it must be in the 0.0 - 1.0 range as the other 2 ranges include blue the strength within that range (decimal part) is then just the red or green value. If the blue has some value, and the red/green is still strong than 0.5 then it must be in 1.0 - 2.0 as that finishes with the red/green just below 0.5 the decimal part is then just blue. Finally if red/green is below 0.5 it must be in the 3rd range 2.0 - 3.0 and the strength will be where the blue is in it's change from full strength to half strength.

  • juaxixjuaxix
    Posts: 435

    I dont understand...so, if we want to turn it like the @Zoyt version here: http://twolivesleft.com/Codea/Talk/discussion/3200/realistic-2d-water-effect we need to change the values to encode blue in stead of yellow, in the numberize function or the colorize? >:D<

  • spacemonkeyspacemonkey
    Posts: 352

    Hmmm... I described my algorithm, I haven't looked at the other code (and I don't have my ipad here today), but you need to conceptually come up with a colour pattern you like and then think how you can translate from that color pattern to a strength and back. Then those algorithms for translation get put into numberize (color in, strength out) and colorize (strength in, color out).

    If you do an image of the kind of color gradient you want to use I'd happily look at it and see if I can implement it.

  • juaxixjuaxix
    Posts: 435

    Hey, @SkyTheCoder, here it is a mod of your code with some color and box2d physics. Thanks for the code, im using it in 6Dimensions game!


    displayMode(FULLSCREEN) -- Use this function to perform your initial setup function setup()     parameter.watch("#balls")     parameter.watch("math.floor(1/DeltaTime)")          ground = physics.body(EDGE, vec2(0,0), vec2(WIDTH,0))     ground.type = STATIC     ramp = physics.body(EDGE,          vec2(WIDTH/2-333, HEIGHT/2-100),          vec2(WIDTH/2-66, HEIGHT/2+100))        ramp2 = physics.body(EDGE,          vec2(WIDTH/2+66, HEIGHT/2+100),          vec2(WIDTH/2+333, HEIGHT/2-100))     m = {}     m.shader = shader(vS, fS)     Number_of_Balls = 333     Metaball_Size = 79     parameter.integer("Metaball_Resolution", 1, 255, 255)     parameter.action("Register Resolution (May lag!)", function()         GENERATE_METABALL()     end)     function GENERATE_METABALL()         local mr = Metaball_Resolution         local ms = Metaball_Size         blendMode(NORMAL)         ballTex = image(200, 200)         setContext(ballTex)         pushStyle()         noStroke()         for i = 255, 0, -(256 - mr) do             fill(255 - i, 255 - i)             ellipse(100, 100, (i / 255) * ms, (i / 255) * ms)         end         setContext()     end     GENERATE_METABALL()     balls = {}     for i=1,Number_of_Balls do        local ball = createDrop( math.random(0, WIDTH), math.random(HEIGHT/2+130, HEIGHT))        table.insert(balls, ball)     end     img = image(WIDTH, HEIGHT)     m = mesh()     r = m:addRect(WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)     m:setRectTex(r, 0, 0, 1, 1)     m.texture = img     m.shader = shader(vS, fS)  end function createDrop(x,y)     local ball = physics.body(CIRCLE, 12)     ball.x = x     ball.y = y     ball.restitution = .1     ball.linearVelocity = vec2(0,0)     ball.friction = 0.1     ball.mass = 10     ball.angularVelocity = 0.0     ball.bullet = false     ball.linearDamping = 0.1     return ball end -- This function gets called once every frame function draw()     -- remove invisible balls... here to avoid flickering     for k,b in ipairs(balls) do         if b.x+Metaball_Size> WIDTH +Metaball_Size *2 or b.x<-Metaball_Size then             table.remove(balls, k)             b:destroy()         end     end     blendMode(NORMAL)     background(0)          sprite("SpaceCute:Background",WIDTH/2,HEIGHT/2, WIDTH,HEIGHT)          rectMode(CENTER)     stroke(255, 0, 0,255)          strokeWidth(3)     line(WIDTH/2-333, HEIGHT/2-100,           WIDTH/2-66, HEIGHT/2+100)     line(WIDTH/2+66, HEIGHT/2+100,           WIDTH/2+333, HEIGHT/2-100)     setContext(img)         background(0) -- clear buffer         --tint(139, 145, 157, 255)         blendMode(ADDITIVE)         for k,b in ipairs(balls) do             sprite(ballTex, b.x, b.y)         end         --noTint()    setContext()    m.texture = img    blendMode(MULTIPLY)    m:draw() end function touched(touch)     if touch.state == BEGAN or touch.state == MOVING then         if balls[Number_of_Balls + 1] == nil then             local ball = createDrop(touch.x,touch.y)             table.insert(balls, ball)         else             balls[Number_of_Balls + 1].x = touch.x             balls[Number_of_Balls + 1].y = touch.y         end     end end vS = [[ // // 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; } ]] fS = [[ // // A basic fragment shader // //Default precision qualifier precision highp float; //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; void main() {     //Sample the texture at the interpolated coordinate     lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;     //Set the output color to the texture color     if (max(col.r, max(col.g, col.b)) > 0.75)     {         gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);     }     else     {         gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);     } } ]]
  • Jmv38Jmv38
    Posts: 2,118

    Very Nice @juaxix however, individual drops have weird behavior when they merge/separate...

  • juaxixjuaxix
    Posts: 435

    Yes, we can try with joints each two balls, but it could be weird too, maybe a little noise for every single drop alone :D what do you think @Jmv38 ?

  • Jmv38Jmv38
    Posts: 2,118

    Well, must admit i havent understood how your code works. I have seen that when 2 unit balls touch each other they are transformed in a single ball too much bigger. The ideal would be to keep the surface constant (ball1+ball2+junction surface = constant), or better the volume constant, but this is easier to specify it than to code it!

  • juaxixjuaxix
    Posts: 435

    The code is easy to understand, look, the balls are just physic bodies generated with parameters to give them the behaviour of water drops, it is: restitution, friction, damping, linear velocity, ok, to draw these balls we are using a technique with a shader and a texture, you need a mesh to apply the shader, just like the ripple or other samples, we are setting the width and height of the area of the mesh to the whole screen:

    m:addRect(WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)
    

    this way we can use the (x,y) position of each ball to paint in the virtual space of the texture (with setContext) of the rect attached to the mesh, so, each ball is represented in the texture painted with the image generated of the ball (200*200) -ballTex -

    for k,b in ipairs(balls) do
     sprite(ballTex, b.x, b.y)
    end
    

    then , you have to use the ADDITIVE blend mode to add all the painted balls to the texture and mix with a background.

    Correct me if I'm wrong @SkyTheCoder.

    We can improve the separated drops with the method from @Zoyt and @spacemonkey shaders, where they numberize and colorize each fragment and use a constant to smooth the draws.

  • SkyTheCoderSkyTheCoder
    Posts: 1,146

    @juaxix Yep, that's how it works.

    Shortened/simplified version:

    In setup you generate a texture, a circle with a gradient, starting at invisible and working its way up to white in the center.

    In setup you also create a mesh, the size of the screen, and a separate texture for it. Maybe named "screen."

    In setup, one more thing, set the blend mode to ADDITIVE. You have to do this AFTER generating the gradient texture, though.

    Set the context (setContext(screen)) to the image you created above. You can then use a mesh or sprite() to draw the above texture at the position of every ball.

    Set the rendering back to the screen (setContext()), and set the screen-sized mesh's texture to the one we created for it, named screen. The mesh just uses a shader to filter out the gradients. If the brightness is greater than half way, it renders it white. If it isn't, it's transparent.

    Haha, that's not too much shorter...

  • SkyTheCoderSkyTheCoder
    Posts: 1,146

    One thing I forgot to mention: The reason it acts as Metaballs is because, with the blend mode ADDITIVE, when two gradient circles merge, the darker parts blend together to qualify as light enough to render white. And the reason why it goes from transparent to white instead of black to white is because if it was black to white I think with the blend mode ADDITIVE it would actually darken the brighter parts on the other gradient circle.

  • Jmv38Jmv38
    Posts: 2,118

    Thank you for your explanations @juaxix and @skythecoder now i understand it. So here is a very little tweek that will make your balls fusion / separation more reallistic, with no extra cost:

            -- replace lines 36-39 by this:
            -- with this gaussian shape of the ball texture, the merge will look more reallistic
            -- also replace the threshold of 0.75 in the shader by 0.5: more physical look.
            local a,d2,ref2
            ref2 = ms*ms/15
            for i = 1,200 do for j =1,200 do
                d2 = (i-100)*(i-100) + (j-100)*(j-100)
                a = math.exp(-d2/ref2)*255
                ballTex:set(i,j,color(a,a,a,255))
            end end
    
  • juaxixjuaxix
    Posts: 435

    .@Jmv38 this water Looks awesome Smooth :D thanks

  • Jmv38Jmv38
    Posts: 2,118

    I tried to add at the end of setup:

       physics.gravity(Gravity)
    

    but this slows down the fps by x5! And it doesnt seem to work. Any idea of what is wrong?

  • juaxixjuaxix
    Posts: 435

    That's weird...you have to set physics.gravity(Gravity) in the draw() function, because it is updated each frame ;) I'm getting the same FPS value...it might not change anything...

  • Jmv38Jmv38
    Posts: 2,118

    @juaxix i've put it in the draw loop and now direction is ok when i tilt the ipad (of course, silly me!) and the FPS is ok too. => works fine, you are correct.

  • spacemonkeyspacemonkey
    Posts: 352

    Very nice. I didn't get time to look back at this, but I think the 2 pass method is much better for real world applications, especially as then you can just worry about coloring once in the final pass. The whole get the graphics processor to do it visually rather than mathematically is sooo nice.

    Pity that won't work for 3d metaballs ;-)

  • juaxixjuaxix
    Posts: 435

    Dont be bad @spacemonkey :D You are right, we need another 3d metaball shader...i am studying a new way of doing this in 3D. And to improve the method I'm using with physics...

    This could be interesting to implement with mesh API: http://http.developer.nvidia.com/GPUGems3/gpugems3_ch07.html

    but the real thing would be to code a Runge Kutta 4 (RK2+RK2): http://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html

    I'm mixing some terrain generation over a sphere with 3d... from this: https://pbs.twimg.com/media/BSsGJUyCEAEE9JY.jpg:large ,to this: https://pbs.twimg.com/media/BSsGPW5CcAI-Xnb.jpg:large

    but if I use normals ( http://www.blackpawn.com/texts/metanormals/ ) it does not work...I would like to mix with metaballs so you can have terrains and 3d water mixed.

    Ah, I have found the @SkyTheCoder method in this forum: https://love2d.org/forums/viewtopic.php?f=5&t=9061 so, no copyright for him as it is copied ;) hehe

  • quezadavquezadav
    Posts: 155

    Ah! Runge-Kutta 4th order is music to my ears... :-B

    @juaxix, I'm glad you are moving in the direction I'd like to be going myself too: towards simulation of real physical phenomena, like fluids flow, with games' physics engines... 8-> pero me subí tarde al tren (I don't know how to translate this) :((

    I look forward to seeing where you get to. Good luck!