Help converting function to shader

I wrote a colorizer function which changes a grayscale image to color and I would like to put it in a shader.

The colorizer function takes a array colors (of any length) and divides those over the 256 shades.
I would like the shader to work in a similar way if possible, so that I can simply pass in any nr of colors. I’m not sure how best to pass the colors and then check the nr of colors passed, which I need for dividing them evenly.

Test code below, I want to put the colorizer functionality into a shader.

-- Colorising

-- Use this function to perform your initial setup
function setup()
    cols = {color(255, 255, 255, 255), color(255, 179, 0, 255), color(255, 22, 0, 255)}
    originalImg = gradient(WIDTH*.5,HEIGHT*.5)
    coloredImg = colorize(originalImg, cols)
end

function colorize(img, colors)
    local nImg = image(img.width, img.height)
    local dStep = 255/(#colors-1)
    
    for i=1, img.width do
        for j=1, img.height do
            local pix = img:get(i,j)        
            local cr = pix % dStep / dStep           -- color mix ratio
            local ci = #colors - pix // dStep -1     -- color index
            local r = pix / 255                      -- transparency ratio
            local newc = mix(colors[ci], colors[ci+1], 1-cr)
            
            nImg:set(i,j, color(newc.r, newc.g, newc.b, r*255))
        end
    end
    return nImg
end

function mix(v1, v2, r)
    return (1-r) * v1 + r*v2
end

function gradient(w,h)
    local img = image(w,h)
    setContext(img)
    fill(0,0)
    strokeWidth(1)
    for i=0, img.width do
        local r = i/img.width
        stroke(255, r*255)
        ellipse(img.width/2, img.height/2, (1-r)*img.width)
    end
    setContext()
    return img
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(0, 0, 0, 255)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    sprite(originalImg, WIDTH/2, originalImg.height/2)
    sprite(coloredImg, WIDTH/2, HEIGHT-coloredImg.height/2)
end

After quite a bit of reading and fiddling I got most of the shader working, but I’m having trouble getting the colors in an array for easy retrieval.

Right now I pass three seperate colors to the shader, “shader.color1=color” etc, and I can acces them in the shader, however I need to get those colors in an array within the shader.

If I had the colors in an array it should be trivial to get the correct color, but I’m having trouble creating a color array within the fragment shader.

I tried passing the colors to a vec4 array and I tried creating the array in the shader. I also tried to use an array with floats for the rgb cols but no luck.

TLDR: I have the seperate colors in the fragment shader (as color1-color3). How do
I put these in an array? Or can I get them directly by variable name as they are numbered (“color”+1-3)?
I need to pick the color dynamicaly so I can’t hard code it.

@Kirl Here’s an example of passing 8 colors in a table to the shader. Slide the parameter to change the color.

EDIT: Just remember that arrays in Codea start at 1 and arrays in shaders start at 0.

function setup()
    parameter.integer("pos",1,8)
    m = mesh()
    m:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    m:setColors(255,255,255)
    m.shader = shader(MyShader.vsh,MyShader.fsh)
    m.shader.colr = {vec3(255,255,255),vec3(255,255,0),vec3(255,0,255),
    vec3(255,0,0),vec3(0,255,255),vec3(0,255,0),vec3(0,0,255),vec3(0,0,0)}
end

function draw()
    background(0)
    m.shader.pos = pos-1
    m:draw()
end

MyShader = { 
    vsh = [[
    uniform mat4 modelViewProjection;
    attribute vec4 position; 
    void main()
    {   gl_Position = modelViewProjection * position;
    }
    ]], 

    fsh = [[
    uniform mediump vec3 colr[8];
    mediump vec4 col;  
    uniform int pos;  
    void main(void) 
    {   col=vec4(colr[pos][0]/255.,colr[pos][1]/255.,colr[pos][2]/255.,1);
        gl_FragColor=col;
    }
]]}

I wouldn’t pass the colours as an array. I would pass them as a colourmap texture.

Without codea in front of me, it would be something like:

uniform lowp sampler2D texture;
uniform lowp sampler2D colourmap;

varying highp vec2 vTexCoord;
void main()
{
    lowp vec4 colm = texture2D( texture, vTexCoord );
    lowp vec4 col = texture2D(colourmap, vec2(colm.r,.5);
    gl_FragColor = col;
}

This is a simplified version of a colour-mapping shader that I have on github.

Thanks @dave1707 , I’ll try it out tomorrow. Not sure what I was doing wrong by looking at it. Maybe because I was using vec4?

[edit] Ah I see, probably the way I tried calling the colors…
[edit2] Ah, and the way I defined the colors…

That sounds interesting @LoopSpace, can you explain the technique? An online search wasn’t as illuminating as I hoped.

The code seems pretty straightforward, but how would I make this colormap texture? Am I correctly assuming this is a 1 x 256 pix coloured strip?

The code that I actually use is slightly more intricate as it uses several strips. I create the colour map with this code:

   img = image(WIDTH,HEIGHT)
   local sh,c,k
   local collist = {
       Colour.svg.Red,
       Colour.svg.Green,
       Colour.svg.Blue,
       Colour.svg.Yellow,
       Colour.svg.Magenta,
       Colour.svg.Cyan,
       Colour.svg.White
   }
   local colours = image(256,28)
   for k,v in ipairs(collist) do
       for i=1,256 do
           --sh = 100*(1 - math.abs(256-i)/256)^.3
           sh = 100*(1 - math.abs(256-i)/320)
           for j = 1,4 do
               colours:set(i,4*(k-1)+j,Colour.shade(v,sh))
           end
       end
   end

Not all of that is standard Codea, but hopefully clear enough to see what’s going on.

Thanks @LoopSpace and @dave1707 , I have my shader working and it works a treat for a beautifull and blazingly fast fire effect! I’ll do the colormap aproach next.

https://m.youtube.com/watch?v=UdOkTxCsWLA

I’m not sure where the artifacts at the bottom come from (~0:32), it’s not transparency depth as I draw each flame directly to the image. Any ideas?

I have a problem with the colormap aproach, it seems the alpha channel of the colormap is ignored by the shader. I used the simple aproach as suggested above by @LoopSpace.

Example img below, bottom-left is the original img and bottom-right is the shaded img with the used colormap displayed above. As you can see it doesn’t fade out. The original/texture img as well as the colormap img fade to transparent.

I googled a bit and fumbled a bit but no dice.

@Kirl Can you post the code? It ought to pick up the alpha from the colour map. Or did you want it to pick up the alpha from the original image?

I’ve reproduced the issue. It’s to do with how the mesh is blended onto the background. The alpha is being set, but Codea is treating the mesh as if the alpha is pre-multiplied. A possible fix is to set premultiplied to false on the texture image. That worked for me in my test case.

-- Colourmap

function setup()
    m = mesh()
    m:addRect(0,0,200,200)
    m.shader = getshader() 
    fill(255, 255, 255, 255)
    img = image(200,200)
    setContext(img)
    background(0,0,255,0)
    pushMatrix()
    scale(20)
    ellipse(5,5,10)
    popMatrix()
    setContext()
    img.premultiplied = false -- <-- Key line here
    m.texture = img
    cimg = image(255,1)
    for i=1,255 do
        cimg:set(i,1,255,0,0,i)
    end
    m.shader.colourmap = cimg
    m.shader.colour = color(8, 255, 0, 255)
    nimg = image(200,200)
    setContext(nimg)
    pushMatrix()
    translate(100,100)
    m:draw()
    popMatrix()
    setContext()
end

function draw()
    background(40,40,50)
    translate(WIDTH/2,HEIGHT/2)
    stroke(255, 229, 0, 255)
    strokeWidth(10)
    line(0,-HEIGHT/2,0,HEIGHT/2)
    blendMode(NORMAL)
    m:draw()
    sprite(cimg,0,-110)
    sprite(img,0,-220)
    sprite(nimg,0,220)
end

function getshader()
    return shader([[
//
// 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;
}

    ]],[[
    
//
// A basic fragment shader
//

//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform lowp sampler2D colourmap;
//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 colm = texture2D( texture, vTexCoord );
    lowp vec4 col;
    col = texture2D( colourmap, 
            vec2(colm.r,.5) );
    //Set the output color to the texture color
    gl_FragColor = col;
}

    ]])
end

@LoopSpace @Kirl I modified the above code and came up with this. A colormap can still be passed, but I thought I’d change the colors with the sliders.

function setup()
    parameter.number("red",0,1,.3)
    parameter.number("green",0,1,.5)
    parameter.number("blue",0,1,.7)
    
    img=image(200,200)
    setContext(img)
    background(0,0,0,0)
    fill(255)
    scale(20)
    ellipse(5,5,5)
    ellipse(7,7,5)
    ellipse(3,3,5)
    ellipse(3,7,5)
    ellipse(7,3,5)
    setContext()
    
    m = mesh()
    m:addRect(0,0,200,200)
    m.shader = getshader() 
    m.texture = img
end

function draw()
    background(0)
    stroke(255, 229, 0, 255)
    strokeWidth(10)
    line(WIDTH/2,0,WIDTH/2,HEIGHT)
    sprite(img,WIDTH/2,HEIGHT*.65)
    translate(WIDTH/2,HEIGHT*.35)
    m.shader.red=red
    m.shader.green=green
    m.shader.blue=blue
    m:draw()
end

function getshader()
    return shader([[
    
    // A basic vertex shader
    
    uniform mat4 modelViewProjection;
    attribute vec4 position;
    attribute vec4 color;
    attribute vec2 texCoord;
    varying lowp vec4 vColor;
    varying highp vec2 vTexCoord;

void main()
{   vColor = color;
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}
    ]],[[
    
    // A basic fragment shader
    
    uniform lowp sampler2D texture;
    varying highp vec2 vTexCoord;
    uniform highp float red;
    uniform highp float green;
    uniform highp float blue;

void main()
{   lowp vec4 colm = texture2D( texture, vTexCoord );
    gl_FragColor = vec4(colm.r*red,colm.g*green,colm.b*blue,colm.a);
}
    ]])
end

Thanks guys! =)

I got some funky results when I forgot to pass a colormap!