Howdy, Stranger!

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

Exploding mesh with shaders

edited February 2013 in Code Sharing Posts: 2,161

Here's another bit of fun with shaders. In this one all the interesting stuff happens in the vertex shader. The mesh is divided into squares which "explode": they zoom off in different directions slowing rotating as they do. The catch is that you have to program in the full path of each square so you can't do a "step by step" solution of the ODE. This means that it is only practical to use this for ODEs that can be solved with closed form.

It also demonstrates how buffers work.

Second time through in the video then I didn't call the background function. Rather firework-y in effect!

displayMode(FULLSCREEN)
function setup()
    backingMode(RETAINED)
    explosion = mesh()
    local s = shader()
    s.vertexProgram, s.fragmentProgram = expshader()
    explosion.shader = s
    explosion.texture = "Cargo Bot:Codea Icon"
    explosion.shader.friction = .1
    explosion.shader.factor = 10
    local vels = explosion:buffer("velocity")
    local origin = explosion:buffer("origin")
    local angv = explosion:buffer("angvel")
    local m,n = 20,20
    vels:resize(m*n*6)
    origin:resize(m*n*6)
    angv:resize(m*n*6)

    local w,h = 20,20
    local xx,y = (WIDTH - n*w)/2, (HEIGHT - h*m)/2
    explosion.shader.size = vec2(n*w,m*h)
    explosion.shader.lr = vec2(xx - w/2,y - h/2)
    local c = vec2(xx+n*w/2,y+m*h/2)
    local cl = vec2(n*w,m*h):len()/2
    local r,th,sf,x,df
    sf = .3
    df = math.random()
    for i=1,m do
        x = xx
        for j = 1,n do
            r = explosion:addRect(x,y,w,h)
            explosion:setRectTex(r,(j-1)/n,(i-1)/m,1/n,1/m)
            th = 2*noise(i*sf+df,j*sf+df)*math.pi
            for k=1,6 do
                vels[6*r-k+1] = 20*(2-(c:dist(vec2(x,y))/cl))^2
                        *vec4(math.cos(th),math.sin(th),0,0)
                origin[6*r-k+1] = vec2(x,y)
                angv[6*r-k+1] = vec2(th,0)
            end
            x = x + w
        end
        y = y + h
    end
    --startRecording()
end

function draw()
    if ElapsedTime < 10 then
        background(91, 90, 71, 255)
        explosion.shader.time = math.max(0,ElapsedTime-2)
    elseif ElapsedTime < 11 then
        background(0, 0, 0, 255)
        explosion.shader.time = 0
    else
        explosion.shader.time = math.max(0,ElapsedTime-12)
    end
    explosion:draw()
end

function touched(touch)
end

function expshader()
    return [[
//
// The explosion vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform float time;
uniform vec2 size;
uniform vec2 lr;
uniform float friction;
uniform float factor;
lowp vec4 gravity = vec4(0.,-1.,0.,0.);
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
// These are vertex buffers: initial velocity of the square,
// angular velocity,
// centre of square
attribute vec4 velocity;
attribute vec2 angvel;
attribute vec2 origin;

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

// ODE: x'' = -friction x' + gravity
// Solution: A exp(- friction * time) + B + time*gravity/friction
// Initial conditions:
// A = gravity/(friction*friction) - x'(0)/friction
// B = x(0) -A

void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vTexCoord = texCoord;
    lowp vec4 pos;
    lowp float angle = time*angvel.x;
    highp vec4 A = gravity/(friction*friction) - velocity/friction;
    highp vec4 B = vec4(origin,0.,0.) - A;
    lowp mat2 rot = mat2(cos(angle), sin(angle), -sin(angle), cos(angle));

    pos = (position - vec4(origin,0.,0.));
    pos.xy = rot * pos.xy;
    pos += exp(-factor*time*friction)*A + B + factor*time * gravity/friction;
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * pos;
}
]],[[
//
// A basic 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;

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 = col;
}
]]
end

Comments

  • What means this "ODE"?

  • edited February 2013 Posts: 489

    Hello @Marksolberg. ODE is ordinary differential equation. See this Wikipedia article for more information.

  • Posts: 437

    Wow ... Would you like to do a script for meta balls in 3D?

  • edited February 2013 Posts: 4

    Awesome work! had to implement it right away in my WIP "snake on steroids" project:

    EDIT: just saw that the video makes it all look awful... ^^

    The "sparks" are also made with it.
    Thanks for making it easy to put it in the shader lab for easy access. :)

    Just one thing I haven't really managed to do is change the "coloring" of the mesh textures (in my case to make it transparent over time).
    Does not work the way I usually do it with textured meshes and I am absolutely not sure if has to do with the shader code...

    If you would be so kind to help me out, I'd really appreciate it.

  • edited February 2013 Posts: 176

    First, I have to apologize for the following.

    Ode to an ODE. (Well actually a limerick.)

    There once was a female mathematician

    Who had a sexual aberration

    "It's not what you think"

    She said with a wink

    "He's no Ordinary Differential Equation!"

  • edited February 2013 Posts: 2,161

    .@VanDread Here's an example with "trails":

    I've now written it as a class to make it easy to use with different projects. It's part of my library: http://www.math.ntnu.no/~stacey/code/CodeLibrary. It has no dependencies so you can just grab the Explosion.lua file (remove the import.library bit at the start and the last end) and use as-is.

  • Posts: 666

    Update your link, I think. it goes to your home page, but not to where your code is stored.

  • edited March 2014 Posts: 342

    @Andrew_Stacey - Could you please tell me how to make an explosion? I have the class copied into a project, but I don't understand how to use it [and I can't see the videos :'(

  • Posts: 2,161

    Here's an example that I have:

    In setup:

    explosion = Explosion({
        image = "Cargo Bot:Codea Icon", -- could be an actual Codea image
        trails = true,
        centre = vec2(WIDTH/2,HEIGHT/2)
    })
    explosion:activate(1.5) -- how long in the future to start
    

    Then in draw, I just have explosion:draw(), and that's it!

    How I use it in a more complicated project is as follows:

    1. When I want to have an explosion, I draw whatever it is that should be exploded on to an image (using setContext).
    2. Then I set up a new explosion using syntax like the above.
    3. Lastly, whatever object it was that wants to have the explosion also has to ensure that it calls the draw method during its own draw method.

    For example, in my newer version of the Anagrams program, then when the word is guessed, the following code runs:

    local img = image(self.cw,self.lh) -- image the size of the current word
    setContext(img)
    resetMatrix()
    -- draw word on to image
    setContext()
    self.explosion = Explosion({
        image = img,
        centre = -- some complicated vector,
        trails = true
    })
    self.explosion:activate(1.5)
    
  • edited March 2014 Posts: 342

    Thank you as always @Andrew_Stacey \^-^/ it's a beautiful effect. Is it available to use in any project (or rather, may I have your permission to use it)?
    EDIT: I did all of that and it didn't work (for me at least)

  • Posts: 2,161

    @Monkeyman32123 You'll need to post some code for me to see. It's working for me with a simple example program so to figure out what's not working for you I need something to see.

    As for using it, as far as possible I place all my code in the public domain (the only time I can't is when I've used someone else's code).

  • I tried to place it in my gyroship project (to explode when "youdead == true"... I can't really give you just a snippet as the project is large (but it is on github, and there is a "project beta" discussion about it)

  • Posts: 2,161

    Can you upload your attempt with the explosion to github? I downloaded what you have but there's jo explosion code there.

  • BriarfoxBriarfox Mod
    Posts: 1,542

    @Andrew_Stacey that is awesome thank you very much for sharing it. I'm having an issue if my fireworks project. Occasionally the shader shows active = true yet nothing is draw.

  • IgnatzIgnatz Mod
    Posts: 5,396

    @Briarfox - in a situation where a shader works intermittently, it's most probable that you have a problem with its inputs, because if a shader doesn't like something, it just doesn't work (which is your problem).

    So I would carefully check all the shader parameters and make sure they are always valid each time they are set. The best may be to output them each time you start a firework, and then look at them carefully for any firework that didn't explode (and maybe compare with one that did explode).

  • BriarfoxBriarfox Mod
    Posts: 1,542

    @Ignatz Thanks, I figured it out. math.random(.1,1) was throwing a 0 out for firction which crashed it.

  • @andrew_stacey I just wanted to set up an explosion in "setup" and then, when a certain variable becomes true, I want to begin and draw the explosion in the draw function (in main).

  • Posts: 2,161

    @Monkeyman32123 It is so much easier to debug with some actual code. Could you post your attempt somewhere?

  • edited March 2014 Posts: 342

    In the "drawship" function I did this

    If youdead and explosion == nil then
    local img = image(self.cw,self.lh) -- image the size of the current word
    setContext(img)
    resetMatrix()
    -- draw word on to image
    setContext()
    self.explosion = Explosion({
        image = img,
        centre = shiploc,
        trails = true
    })
    self.explosion:activate(1.5)
    End
    If explosion ~= nil then
        explosion:draw()
    End
    
  • BriarfoxBriarfox Mod
    edited March 2014 Posts: 1,542

    @monkeyman32123 try this:

    -- 0test
     
    -- Use this function to perform your initial setup
    function setup()
        print("Hello World!")
        img = image(500,500)
        setContext(img)
        fill(255, 0, 0, 255)
        fontSize(50)
        text("Hello World",250,250)
        setContext()
     
     
    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)
     
        if explosion then
            explosion:draw()
        end
     
        -- Do your drawing here
     
    end
     
    function touched(t)
        if t.state == BEGAN then
            explosion = Explosion({
        image = img,
        centre = vec2(WIDTH/2,HEIGHT/2),
        trails = false,
        rows = 50,
        cols = 50
        })
            explosion:activate(1)
        end
    end
  • BriarfoxBriarfox Mod
    Posts: 1,542

    Here is a main tab that lets you play with all the explosion shader settings. This is only the main tab, grab the explosion shader from above.

    function setup()
        explode = nil
        displayMode(OVERLAY)
        parameter.number("Friction",0.01,2)
        parameter.integer("Factor",1,100,10)
        parameter.integer("Rows",2,100,20)
        parameter.integer("Cols",2,100,20)
        parameter.boolean("Trails")
        parameter.integer("TrailLength",1,100,16)
        parameter.number("TrailSeparation",0.01,1,0.5)
        parameter.action("Explode",function()
            explode = nil
            print(Trails)
            local img = readImage("Cargo Bot:Codea Icon")
            explode = Explosion({image = img,
                                friction = Friction,
                                factor = Factor,
                                rows = Rows,
                                cols = Cols,
                                trails = Trails,
                                trailLength = TrailLength,
                                trailSeparation = TrailSeparation,
                                centre = vec2(0,0)            
            })
            explode:activate(1)
            end)
     
    end
     
    -- This function gets called once every frame
    function draw()
        background(0, 0, 0, 255)
        if explode then
            translate(WIDTH/2,HEIGHT/2)
            explode:draw()
        end
     
     
    end
  • Posts: 2,161

    @Monkeyman32123 You're missing a couple of selfs in that code.

  • I have it so that it sets up the explosion in setup, then activates it when you die, then draws it in the draw step....and it works...BUT it makes the explosion always in the place I set it to be in setup (obviously)...is there a way for me to keep setting up the explosion in setup but change it's location when you die before it draws? If I try to setup the explosion anywhere but in setup it causes massive lag spikes

  • Posts: 2,161

    Can't you wrap it in a translate?

    translate(100,200)
    explosion:draw()
    
  • Yeah, I literally just figured that out right before you commented XD
    But thank you sir

Sign In or Register to comment.