Howdy, Stranger!

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

Help converting function to shader

edited November 19 in Questions Posts: 147

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

Comments

  • edited November 21 Posts: 147

    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.

  • dave1707dave1707 Mod
    edited November 21 Posts: 6,392

    @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;
        }
    ]]}
    
  • Posts: 310

    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.

  • edited November 22 Posts: 147

    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?

  • Posts: 310

    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.

  • edited November 24 Posts: 147

    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.

    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?

  • edited December 6 Posts: 147

    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.

  • Posts: 310

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

  • Posts: 310

    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
    
  • dave1707dave1707 Mod
    edited December 7 Posts: 6,392

    @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
    
  • Posts: 147

    Thanks guys! =)

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

Sign In or Register to comment.