Howdy, Stranger!

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

Undulate: an experiment with vertex shaders and buffers (Codea 1.5)

edited October 2017 in Code Sharing Posts: 489

Buffers are a new feature in Codea version 1.5 that allow per-vertex data to be associated with a mesh and passed to a shader as an attribute. Shaders can be used to manipulate the vertices of a mesh, as well as manipulate how pixels are coloured. The code below shows a simple application.

The code uses the '--#' convention that allows it to be pasted into a new project with the Codea tab structure preserved (by a long touch on 'Add New Project' and then 'Paste Into Project').

image

I believe that, in Codea version 1.5, it is not yet possible to pass a Lua number directly to a GLSL ES float attribute using a buffer. It is, however, possible to pass a Codea vec2 userdata value to a GLSL ES vec2 attribute and this code uses the x coordinate of two-dimensional vectors to achieve its objective in passing data.

The vertex and fragment shaders were developed in the new Shader Lab. Here, they are stored as strings in table Undulate to make the code easier to share on the Forum.

--# Main
--
-- Undulate
--

supportedOrientations(LANDSCAPE_ANY)
function setup()
    local default = 40
    local ti = table.insert -- Cache the function for speed
    function reset()
        local halfN = n/2 - 0.5
        m = mesh()
        m.shader = shader(Undulate.vertexShader,
            Undulate.fragmentShader)
        local dBuffer = m:buffer("d")
        dBuffer:resize(n * n * 6)
        local ver = {}
        local col = {}
        local len = math.min(WIDTH, HEIGHT) / n
        p = n / 2 * len
        for j = 0, n - 1 do
            local y1 = j - halfN
            local ySqr = y1 * y1
            for i = 0, n - 1 do
                local x1 = i - halfN
                local d = math.sqrt(x1 * x1 + ySqr) * default / n
                local x = i * len
                local y = j * len
                local c = rCol()
                ti(ver, vec3(x, y, 0))
                ti(ver, vec3(x + len, y, 0))
                ti(ver, vec3(x, y + len, 0))
                ti(ver, vec3(x, y + len, 0))
                ti(ver, vec3(x + len, y, 0))
                ti(ver, vec3(x + len, y + len, 0))
                for k = 1, 6 do
                    ti(col, c)
                    -- Needs to be a vec2 to work in Codea beta 1.5(16)
                    dBuffer[(i + j * n) * 6 + k] = vec2(d, 0)
                end
            end
        end
        m.vertices = ver
        m.colors = col
    end      
    parameter.integer("n", 1, 100, default, reset)
end

function draw()
    background(0)
    perspective() 
    camera(p, -200, 1000, p, p, 0, 0, 1, 0)
    m.shader.t = ElapsedTime * 3
    m:draw()
end

function rCol()
    return color(
    math.random(255),
    math.random(255),
    math.random(255))
end

--# ShaderUndulate
Undulate = {

vertexShader = [[
//
// Vertex shader: Undulate
//

uniform mat4 modelViewProjection;
uniform float t;

attribute vec4 position;
attribute vec4 color;
attribute vec2 d; // Needs to be vec2 to work in Codea 1.5(16)

varying lowp vec4 vColor;

void main() {
    float z = 50.0 * sin(d.x + t);
    vec4 p = vec4(position.x, position.y, z, position.w);
    vColor = color;
    gl_Position = modelViewProjection * p;
}
]],

fragmentShader = [[
//
// Fragment shader: Undulate
//

varying lowp vec4 vColor;

void main() {
    gl_FragColor = vColor;
}
]]
}

Comments

  • Posts: 1,187

    This needed a little tweaking to run in Codea 2021. Here’s a zip if anyone wants to run it. It’s pretty cool tbh!

  • Posts: 1,187

    In trying to understand this code, I went through it and renamed the variables to be descriptive of their purpose and not just single letters.

    I think I got it right, though it’s possible some names are inaccurate due to me misunderstanding the code.

    In case anyone else who wants to understand this shader could learn better from descriptive variable names, I’m pasting a zip of that version (it also includes the original version for reference).

  • dave1707dave1707 Mod
    edited June 24 Posts: 9,287

    Here’s something similar that uses Craft spheres. Alter the values of the sliders.

    viewer.mode=STANDARD
    
    function setup()
        parameter.watch("fps")
        parameter.number("amplitude",1,6,6) 
        parameter.number("speed",1,3,1.5) 
        parameter.number("wave",1,6,3) 
        assert(OrbitViewer, "Please include Cameras as a dependency")        
        scene = craft.scene()
        scene.ambientColor=color(255)
        v=scene.camera:add(OrbitViewer, vec3(0,0,0),130,0,3000)
        v.rx,v.ry=-45,-20
        grid,s,f,col,sph=40,.1,90,{},{}
        for x=-grid,grid do
            sph[x],col[x]={},{}
            for y=-grid,grid do
                sph[x][y]=createSphere(vec3(x,y,0))
                col[x][y]=color(math.random(255),math.random(255),math.random(255))
            end
        end
    end
    
    function draw()
        update(DeltaTime)
        scene:draw()
        fps=1//DeltaTime
    end
    
    function update(dt)
        scene:update(dt)
        f=f+(s*speed)
        if f>180 then
            f=f-180
        end
        for x=-grid,grid do
            for y=-grid,grid  do
                sph[x][y].material.diffuse=col[x][y]
                sph[x][y].position=vec3(sph[x][y].x,sph[x][y].y,math.sin(math.sqrt((x/wave)^2+(y/wave)^2)-f*speed)*amplitude)
            end
        end
    end
    
    function createSphere(p)
        local pt=scene:entity()
        pt.position=p
        pt.model = craft.model.icosphere(.7,1)
        pt.material = craft.material(asset.builtin.Materials.Specular)
        return pt
    end
    
  • Posts: 1,060

    nice! why is it necessary to set the color each time thru update?

  • dave1707dave1707 Mod
    edited June 25 Posts: 9,287

    @RonJeffries It’s not. I was thinking of having colors ripple out with the waves but never got around to it. Here’s the updated code setting the colors once.

    viewer.mode=STANDARD
    
    function setup()
        parameter.watch("fps")
        parameter.number("amplitude",1,6,6) 
        parameter.number("speed",1,3,1.5) 
        parameter.number("wave",1,6,3) 
        assert(OrbitViewer, "Please include Cameras as a dependency")        
        scene = craft.scene()
        scene.ambientColor=color(255)
        v=scene.camera:add(OrbitViewer, vec3(0,0,0),130,0,3000)
        v.rx,v.ry=-45,-20
        grid,s,f,sph=40,.1,90,{}
        for x=-grid,grid do
            sph[x]={}
            for y=-grid,grid do
                sph[x][y]=createSphere(vec3(x,y,0))
            end
        end
    end
    
    function draw()
        update(DeltaTime)
        scene:draw()
        fps=1//DeltaTime
    end
    
    function update(dt)
        scene:update(dt)
        f=f+(s*speed)
        if f>180 then
            f=f-180
        end
        for x=-grid,grid do
            for y=-grid,grid  do
                sph[x][y].position=vec3(sph[x][y].x,sph[x][y].y,math.sin(math.sqrt((x/wave)^2+(y/wave)^2)-f*speed)*amplitude)
            end
        end
    end
    
    function createSphere(p)
        local pt=scene:entity()
        pt.position=p
        pt.model = craft.model.icosphere(.7,1)
        pt.material = craft.material(asset.builtin.Materials.Specular)
        pt.material.diffuse=color(math.random(255),math.random(255),math.random(255))
        return pt
    end
    
  • dave1707dave1707 Mod
    Posts: 9,287

    @RonJeffries Here’s what I was originally going for when I was updating the color each time thru the draw function. I added the ripple and colorize sliders.

    viewer.mode=STANDARD
    
    function setup()
        fill(255)
        parameter.number("amplitude",1,6,6) 
        parameter.number("speed",1,3,1.5) 
        parameter.number("wave",1,6,3) 
        parameter.boolean("ripple",false) 
        parameter.color("colorize",color(0,0,255)) 
        assert(OrbitViewer, "Please include Cameras as a dependency")        
        scene = craft.scene()
        scene.ambientColor=color(255)
        v=scene.camera:add(OrbitViewer, vec3(0,0,0),130,0,3000)
        v.rx,v.ry=-45,-20
        grid,s,f,col,sph=40,.1,90,{},{}
        for x=-grid,grid do
            sph[x],col[x]={},{}
            for y=-grid,grid do
                col[x][y]=color(math.random(255),math.random(255),math.random(255))
                sph[x][y]=createSphere(vec3(x,y,0))
            end
        end
    end
    
    function draw()
        update(DeltaTime)
        scene:draw()
        text("FPS  "..1//DeltaTime,WIDTH/2,HEIGHT-50)
    end
    
    function update(dt)
        scene:update(dt)
        f=f+(s*speed)
        if f>180 then
            f=f-180
        end
        for x=-grid,grid do
            for y=-grid,grid  do
                sph[x][y].material.diffuse=col[x][y]
                if ripple then
                    if math.sin(math.sqrt((x/wave)^2+(y/wave)^2)-f*speed)<-.98 then
                        sph[x][y].material.diffuse=colorize
                    end
                end
                sph[x][y].position=vec3(sph[x][y].x,sph[x][y].y,math.sin(math.sqrt((x/wave)^2+(y/wave)^2)-f*speed)*amplitude)
            end
        end
    end
    
    function createSphere(p)
        local pt=scene:entity()
        pt.position=p
        pt.model = craft.model.icosphere(.7,1)
        pt.material = craft.material(asset.builtin.Materials.Specular)
        return pt
    end
    
  • Posts: 1,060

    yes, but with your previous i tried to init sph color just once, instead if each time thru and just got white. i thought one should be able to save the continual setting of material color, but no. maybe i did it wrong.

  • dave1707dave1707 Mod
    Posts: 9,287

    @RonJeffries Once you set sph[x][y].position or sph[x][y].material.diffuse, those should remain what you set until you change them. On my above code with the ripple color, I have to save my original color in a table to put it back after the ripple color passes. In setup, I’m not setting the color for each sphere, but instead I’m setting the color in a corresponding table so in update I’m either setting the color from the table or the ripple color. If I don’t set the diffuse color in update, all the spheres will be white or eventually the ripple color.

  • Posts: 1,060

    yes i must have done something wrong.

  • Posts: 1,187

    @dave1707 its really cool!

    Would it be hard to have the selected color apply to all the balls, not just the crests, but also apply a gradient effect, so that the balls get darker and darker the closer to the bottom they are?

  • dave1707dave1707 Mod
    Posts: 9,287

    @UberGoober It should be possible to do a lot of modification to the colors based on the position of the sphere in the sine curve. Don’t have time right now to play with it.

  • Posts: 1,187

    @RonJeffries @dave1707 here’s my attempt to apply the OP shader version to a craft shader (includes the OP version for comparison).

    It doesn’t fully work, and the way it fails illustrates the biggest frustration I’ve had with converting mesh shaders to craft shaders.

    The functions called setMeshDistanceBufferFor in the mesh version, and setCraftDistanceBufferFor In the Craft version, isolate the crucial code for making the shaders move each square independently (I think).

    It works in the mesh version but I haven’t found a way to get the craft shader to do it.

Sign In or Register to comment.