Howdy, Stranger!

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

Multiple step colour gradient shader

edited August 2015 in Code Sharing Posts: 2,020

I'm working on a platform game with large, vertically scrolling levels and wanted a simple parallax sky effect in the background. Using a large texture would be impractical as the levels are going to be considerably taller than 2048 pixels. So I wrote a shader for shading between multiple colours. You set 5 colours, the percentage points (from scale 0.0 to 1.0) at which you'd like one to fade in the next, the centre point at which the gradient starts, and the shape of the gradient (circle, elliptical, or linear gradients are possible). I thought I'd share it here in case someone else has a use for it. This could be adapted for all sorts of things, eg to make colourful text etc.

The image below is my attempt at a sunset sky. It would look pretty good (in a cartoony way) with some clouds in front of it.

gradient


--# Main -- MultiStop Colour Gradients function setup() print ("Drag your finger to set the gradient centre") print ("Pull down this output window to access more settings") Gradient.setup() parameter.number("GradientAspect", 0, 2, 0.5) parameter.number("Step1", 0, 1, 0.9) parameter.number("Step2", 0, 1, 0.3) parameter.number("Step3", 0, 1, 0.2) parameter.number("Step4", 0, 1, 0.1) parameter.number("Step5", 0, 1, 0.05) parameter.color("Color1", color(31, 27, 53, 255)) parameter.color("Color2", color(0, 198, 255, 255)) parameter.color("Color3", color(228, 207, 225, 255)) parameter.color("Color4", color(254, 0, 0, 255)) parameter.color("Color5", color(255, 255, 1, 255)) Centre = vec2(0,-0.4) end function draw() background(40, 40, 50) Gradient.mesh.shader.centre = Centre Gradient.mesh.shader.aspect = vec2(GradientAspect, 1) Gradient.mesh.shader.colors = {Color1, Color2, Color3, Color4, Color5} Gradient.mesh.shader.step = {Step1, Step2, Step3, Step4, Step5} Gradient.mesh:draw() end function touched(t) Centre = vec2(-0.5 + t.x/ WIDTH , -0.5 + t.y/HEIGHT) end --# Gradient Gradient = {} function Gradient.setup() Gradient.mesh = mesh() Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) Gradient.mesh.shader = shader( [[ uniform mat4 modelViewProjection; uniform lowp vec2 aspect; // set to (1,1) for circular gradient, (0,1) or (1,0) for linear, or fractional, eg (1, 0.5) for oval attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; // varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { // vColor = color; vTexCoord = (texCoord - vec2(0.5,0.5)) * aspect; gl_Position = modelViewProjection * position; } ]], [[ precision highp float; const int no = 5; //the number of gradation points you want to have // uniform lowp sampler2D texture; uniform lowp vec2 centre; // centre of gradient uniform float step[no]; // transition points between colours, 0.0 - 1.0 uniform lowp vec4 colors[no]; // colours to grade between //varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying float vDistance; void main() { float dist = distance( vTexCoord, centre); lowp vec4 col = colors[0]; for (int i=1; i<no; ++i) { col = mix(col, colors[i], smoothstep(step[i-1], step[i], dist)); } gl_FragColor = col; //texture2D( texture, vTexCoord ) * col; } ]] ) end

Comments

  • Posts: 2,020

    Here's a variant for shading text:

    text

    -- MultiStop Colour Gradients
    
    displayMode(FULLSCREEN)
    supportedOrientations(LANDSCAPE_ANY)
    
    function setup()
        font("DINAlternate-Bold")
        fontSize(70)
        textAlign(CENTER)
        textMode(CORNER)
        textWrapWidth(WIDTH*0.8)
        fill(255)
    
        Gradient.setup("Does anyone remember the Commodore Amiga? It had a chip called the COPPER. This could change the colour value each line of the display. I still think of colour gradients like these as “COPPER bars” \u{1f61d}")
    end
    
    function draw()
        background(0)
        Gradient.mesh:draw()
    end
    
    Gradient = {}
    
    function Gradient.setup(txt)
    
        local w,h = textSize(txt)
        local img = image(w,h)
        setContext(img)
        text(txt)
        setContext()
        Gradient.mesh = mesh()
        Gradient.mesh.texture = img
        Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, w,h)
        Gradient.mesh.shader = 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; //- vec2(0.5,0.5);
            gl_Position = modelViewProjection * position;
        }
        ]],
        [[
        precision highp float;
    
        const int no = 6; //the number of gradation points you want to have
    
        uniform lowp sampler2D texture;
        uniform float step[no];  // transition points between colours, 0.0 - 1.0
        uniform lowp vec4 colors[no]; // colours to grade between
        uniform float repeats;
    
        //varying lowp vec4 vColor;
        varying highp vec2 vTexCoord;
        varying float vDistance;
    
        void main()
        {
            lowp vec4 col = colors[0];
            for (int i=1; i<no; ++i) {
                col = mix(col, colors[i], smoothstep(step[i-1], step[i], fract(vTexCoord.y*repeats)));
            }
            gl_FragColor = texture2D( texture, vTexCoord ) * col;
        }
        ]]
        )
        Gradient.mesh.shader.colors = {
        color(27, 29, 50, 255),
        color(41, 164, 218, 255),
        color(222, 231, 255, 255),
        color(79, 39, 31, 255),
        color(188, 53, 53, 255),
        color(255, 255, 100, 255)}
        Gradient.mesh.shader.step = {0.8, 0.5, 0.45, 0.43, 0.35, 0.09}
        Gradient.mesh.shader.repeats = 8 --how many times the texture repeats. set this to the number of lines
    end
    
  • IgnatzIgnatz Mod
    Posts: 5,396

    Ah, the wonderful days of the 80's. Personally, I think that gradient text is hard to read.

    Please just don't write code for flashing text. [-O<

  • Posts: 2,020

    Yes, I wouldn't typeset a novel like this

    ( instead I'd use this: http://codea.io/talk/discussion/6710/markdown-codea-rich-text-formatting#latest )

    This is for attention-grabbing. Hi-score tables and the like.

  • Posts: 289

    beautiful Colour Gradients text

  • Posts: 708

    @yojimbo2000 been messing about with your first gradient shader. Looking to see if I can go for some varying backdrops for my ghost slicing game. Here's a graveyard scene


    --# Main -- MultiStop Colour Gradients function setup() print("Drag down for more options") Gradient.setup() parameter.number("GradientAspect", 0, 2, 1) parameter.number("Step1", 0, 1, 0.6) parameter.number("Step2", 0, 1, 0) parameter.integer("MoonMove",0,1,1) parameter.color("Color1", color(31, 27, 53, 255)) parameter.color("Color2", color(0, 198, 255, 255)) Centre = vec2(0,0) moon=vec2(WIDTH/2,HEIGHT/2) bgobj={} numobj=15 for i=1,numobj do local lev=1 if i>numobj*0.8 then lev=3 elseif i>numobj*0.5 then lev=2 end table.insert(bgobj,{x=math.random(WIDTH),y=75,level=lev,angle=-8+math.random(15),type=math.random(2)}) end cross=image(100,200) setContext(cross) fill(255) rect(40,0,20,200) rect(0,140,100,20) setContext() gravestone=image(100,200) setContext(gravestone) fill(255) rect(10,0,80,120) ellipse(50,100,80) setContext() rail=image(60,200) setContext(rail) fill(255) rect(25,0,10,140) rect(-10,110,80,5) rect(-10,100,80,5) ellipse(30,145,20) setContext() img={} table.insert(img,{img=cross,w=100,h=200}) table.insert(img,{img=gravestone,w=100,h=200}) end function draw() if MoonMove==1 then Centre.y=math.sin(ElapsedTime*0.5)/2 Centre.x=-math.cos(ElapsedTime*0.5)/3 end moon.x=(Centre.x+0.5)*WIDTH moon.y=(Centre.y+0.5)*HEIGHT --shimmer on the moon -- Step1=0.3+math.random(100)/10000 background(40, 40, 50) Gradient.mesh.shader.centre = Centre Gradient.mesh.shader.aspect = vec2(GradientAspect, 1) Gradient.mesh.shader.colors = {Color1, Color2} Gradient.mesh.shader.step = {Step1, Step2} Gradient.mesh:draw() fill(255) ellipse(moon.x,moon.y,80) for i,b in pairs(bgobj) do if b.level==3 then tint(0,255) elseif b.level==2 then local a=0.4 tint(Color1.r*a,Color1.g*a,Color1.b*a,255) else local fade=math.max(255-(moon.y/HEIGHT)*255,100) tint(0,fade) end pushMatrix() translate(b.x,b.y*b.level/3) rotate(b.angle) sprite(img[b.type].img,0,0,img[b.type].w*b.level/3,img[b.type].h*b.level/3) popMatrix() noTint() end tint(0,255) for i=0,WIDTH,60 do sprite(rail,i,100) end noTint() end function touched(t) Centre = vec2(-0.5 + t.x/ WIDTH , -0.5 + t.y/HEIGHT) end --# Gradient Gradient = {} function Gradient.setup() Gradient.mesh = mesh() Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) Gradient.mesh.shader = shader( [[ uniform mat4 modelViewProjection; uniform lowp vec2 aspect; // set to (1,1) for circular gradient, (0,1) or (1,0) for linear, or fractional, eg (1, 0.5) for oval attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; // varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { // vColor = color; vTexCoord = (texCoord - vec2(0.5,0.5)) * aspect; gl_Position = modelViewProjection * position; } ]], [[ precision highp float; const int no = 2; //the number of gradation points you want to have // uniform lowp sampler2D texture; uniform lowp vec2 centre; // centre of gradient uniform float step[no]; // transition points between colours, 0.0 - 1.0 uniform lowp vec4 colors[no]; // colours to grade between //varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying float vDistance; void main() { float dist = distance( vTexCoord, centre); lowp vec4 col = colors[0]; for (int i=1; i<no; ++i) { col = mix(col, colors[i], smoothstep(step[i-1], step[i], dist)); } gl_FragColor = col; //texture2D( texture, vTexCoord ) * col; } ]] ) end
  • Posts: 2,020

    That's awesome! Love how it's all procedural. There's some very atmospheric games around which are just silhouettes over a gradient. One on iOS called Volt that I like.

  • Posts: 2,020

    Although, there's no need to draw the moon with an ellipse, do it with the shader. Try this:


    --# Main -- MultiStop Colour Gradients function setup() print("Drag down for more options") Gradient.setup() parameter.number("GradientAspect", 0, 2, WIDTH/HEIGHT) parameter.number("Step1", 0, 1, 0.6) parameter.number("Step2", 0, 1, 0.06) parameter.number("Step3", 0, 1, 0.05) parameter.integer("MoonMove",0,1,1) parameter.color("Color1", color(31, 27, 53, 255)) parameter.color("Color2", color(0, 198, 255, 255)) parameter.color("Color3", color(255, 255, 255, 255)) Centre = vec2(0,0) moon=vec2(WIDTH/2,HEIGHT/2) bgobj={} numobj=15 for i=1,numobj do local lev=1 if i>numobj*0.8 then lev=3 elseif i>numobj*0.5 then lev=2 end table.insert(bgobj,{x=math.random(WIDTH),y=75,level=lev,angle=-8+math.random(15),type=math.random(2)}) end cross=image(100,200) setContext(cross) fill(255) rect(40,0,20,200) rect(0,140,100,20) setContext() gravestone=image(100,200) setContext(gravestone) fill(255) rect(10,0,80,120) ellipse(50,100,80) setContext() rail=image(60,200) setContext(rail) fill(255) rect(25,0,10,140) rect(-10,110,80,5) rect(-10,100,80,5) ellipse(30,145,20) setContext() img={} table.insert(img,{img=cross,w=100,h=200}) table.insert(img,{img=gravestone,w=100,h=200}) end function draw() if MoonMove==1 then Centre.y=math.sin(ElapsedTime*0.5)/2 Centre.x=-math.cos(ElapsedTime*0.5)/3 end moon.x=(Centre.x+0.5)*WIDTH moon.y=(Centre.y+0.5)*HEIGHT --shimmer on the moon -- Step1=0.3+math.random(100)/10000 background(40, 40, 50) Gradient.mesh.shader.centre = Centre Gradient.mesh.shader.aspect = vec2(GradientAspect, 1) Gradient.mesh.shader.colors = {Color1, Color2, Color3} Gradient.mesh.shader.step = {Step1, Step2, Step3} Gradient.mesh:draw() fill(255) -- ellipse(moon.x,moon.y,80) for i,b in pairs(bgobj) do if b.level==3 then tint(0,255) elseif b.level==2 then local a=0.4 tint(Color1.r*a,Color1.g*a,Color1.b*a,255) else local fade=math.max(255-(moon.y/HEIGHT)*255,100) tint(0,fade) end pushMatrix() translate(b.x,b.y*b.level/3) rotate(b.angle) sprite(img[b.type].img,0,0,img[b.type].w*b.level/3,img[b.type].h*b.level/3) popMatrix() noTint() end tint(0,255) for i=0,WIDTH,60 do sprite(rail,i,100) end noTint() end function touched(t) Centre = vec2(-0.5 + t.x/ WIDTH , -0.5 + t.y/HEIGHT) end --# Gradient Gradient = {} function Gradient.setup() Gradient.mesh = mesh() Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) Gradient.mesh.shader = shader( [[ uniform mat4 modelViewProjection; uniform lowp vec2 aspect; // set to (1,1) for circular gradient, (0,1) or (1,0) for linear, or fractional, eg (1, 0.5) for oval attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; // varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { // vColor = color; vTexCoord = (texCoord - vec2(0.5,0.5)) * aspect; gl_Position = modelViewProjection * position; } ]], [[ precision highp float; const int no = 3; //the number of gradation points you want to have // uniform lowp sampler2D texture; uniform lowp vec2 centre; // centre of gradient uniform float step[no]; // transition points between colours, 0.0 - 1.0 uniform lowp vec4 colors[no]; // colours to grade between //varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying float vDistance; void main() { float dist = distance( vTexCoord, centre); lowp vec4 col = colors[0]; for (int i=1; i<no; ++i) { col = mix(col, colors[i], smoothstep(step[i-1], step[i], dist)); } gl_FragColor = col; //texture2D( texture, vTexCoord ) * col; } ]] ) end
  • Posts: 708

    Of course - didn't think of that!

  • IgnatzIgnatz Mod
    edited August 2015 Posts: 5,396

    Replace draw with the two functions below for a nice outline effect on the graveyard, which follows the direction of the moon

    (This is a bit like Photoshop tennis, where you take a drawing and modify it, in turns).

    function draw()
        if MoonMove==1 then
            Centre.y=math.sin(ElapsedTime*0.5)/2
            Centre.x=-math.cos(ElapsedTime*0.5)/3
        end
        moon.x=(Centre.x+0.5)*WIDTH
        moon.y=(Centre.y+0.5)*HEIGHT
        --shimmer on the moon
        --  Step1=0.3+math.random(100)/10000
        background(40, 40, 50)
        Gradient.mesh.shader.centre = Centre
        Gradient.mesh.shader.aspect = vec2(GradientAspect, 1)
        Gradient.mesh.shader.colors = {Color1, Color2, Color3}
        Gradient.mesh.shader.step = {Step1, Step2, Step3}
        Gradient.mesh:draw()
    --get direction to moon, multiply by 3
        v=(moon-vec2(WIDTH/2,150)):normalize()*3
    --draw white reflection offset by this vector
        DrawGraveyard(color(150),v.x,v.y)
    --draw graveyard
        DrawGraveyard(color(0),0,0)
    end
    
    function DrawGraveyard(c,x,y)
        pushMatrix()
        translate(x,y)
        tint(c)
        for i,b in pairs(bgobj) do
            pushMatrix()
            translate(b.x,b.y*b.level/3)
            rotate(b.angle)
            sprite(img[b.type].img,0,0,img[b.type].w*b.level/3,img[b.type].h*b.level/3)
            popMatrix()
        end
        for i=0,WIDTH,60 do
            sprite(rail,i,100)
        end
        popMatrix()
        noTint()
    end
    
  • Posts: 708

    Nice effect, though it cancels out the subtle fade out of the objects which are further in the distance.

  • IgnatzIgnatz Mod
    Posts: 5,396

    In an action game, I'm not sure subtlety is a factor! :))

  • Posts: 289

    does it need the newest Codea version and ios or global replace?

  • Posts: 289

    it shows:shader compile error,attempt to redeclare 'step' as variable,attempt to use 'step' as variable... how to make it?

  • IgnatzIgnatz Mod
    Posts: 5,396

    @firewolf - there are four versions of the code. Which one?

  • Posts: 289

    i tried again,the same errors in four versions

  • Posts: 2,020

    Which version of iOS and Codea are you using? Did you make any changes to the code before you saw that error?

    I wonder whether older versions of open gl had a step function (in 2.0 it's edge and smoothstep), which would make step not available as a variable name (similar to the issue where the reflect function was added).

  • Posts: 289

    thanks

  • Posts: 708

    Now with optional mist and rain


    --# Main -- MultiStop Colour Gradients function setup() print("Drag down for more options") Gradient.setup() parameter.number("GradientAspect", 0, 2, WIDTH/HEIGHT) parameter.number("Step1", 0, 1, 0.6) parameter.number("Step2", 0, 1, 0.0501) parameter.number("Step3", 0, 1, 0.050) parameter.integer("MoonMove",0,1,1) parameter.number("rainangle",-30,30,-20) parameter.number("rainspeed",1.5,5,2) parameter.integer("MistOn",0,1,1) parameter.integer("RainOn",0,1,1) parameter.color("Color1", color(31, 27, 53, 255)) parameter.color("Color2", color(0, 198, 255, 255)) parameter.color("Color3", color(255, 255, 255, 255)) Centre = vec2(0,0) moon=vec2(WIDTH/2,HEIGHT/2) bgobj={} numobj=15 for i=1,numobj do local lev=1 if i>numobj*0.8 then lev=3 elseif i>numobj*0.5 then lev=2 end table.insert(bgobj,{x=math.random(WIDTH),y=75,level=lev,angle=-8+math.random(15),type=math.random(2)}) end cross=image(100,200) setContext(cross) fill(255) rect(40,0,20,200) rect(0,140,100,20) setContext() gravestone=image(100,200) setContext(gravestone) fill(255) rect(10,0,80,120) ellipse(50,100,80) setContext() rail=image(60,200) setContext(rail) fill(255) rect(25,0,10,140) rect(-10,110,80,5) rect(-10,100,80,5) ellipse(30,145,20) setContext() mist=image(200,30) setContext(mist) fill(255) polygon({vec2(10,5), vec2(100,25), vec2(190,20), vec2(110, 10)}) --must be in clockwise or anti-clockwise order setContext() rain=image(10,10) setContext(rain) fill(255) strokeWidth(4) stroke(255) line(5,1,5,9) setContext() img={} table.insert(img,{img=cross,w=100,h=200}) table.insert(img,{img=gravestone,w=100,h=200}) fog={} for i=1,30 do local fx=1 if math.random(2)==1 then fx=-1 end local fy=1 if math.random(2)==1 then fy=-1 end table.insert(fog,{x=math.random(WIDTH),y=math.random(HEIGHT/2),level=math.random(3),scalex=1+math.random(40)/10,fx=fx,fy=fy,spd=-3+math.random(60)/10}) end raindrop={} for i=1,300 do table.insert(raindrop,{x=-300+math.random(WIDTH+600),y=math.random(HEIGHT),level=math.random(3)}) end end function draw() if MoonMove==1 then Centre.y=math.sin(ElapsedTime*0.5)/2 Centre.x=-math.cos(ElapsedTime*0.5)/3 end moon.x=(Centre.x+0.5)*WIDTH moon.y=(Centre.y+0.5)*HEIGHT --shimmer on the moon -- Step1=0.3+math.random(100)/10000 background(40, 40, 50) Gradient.mesh.shader.centre = Centre Gradient.mesh.shader.aspect = vec2(GradientAspect, 1) Gradient.mesh.shader.colors = {Color1, Color2, Color3} Gradient.mesh.shader.step = {Step1, Step2, Step3} Gradient.mesh:draw() fill(255) for i,b in pairs(bgobj) do if b.level==3 then tint(0,255) elseif b.level==2 then local a=0.4 tint(Color1.r*a,Color1.g*a,Color1.b*a,255) else local fade=math.max(255-(moon.y/HEIGHT)*255,100) tint(0,fade) end pushMatrix() translate(b.x,b.y*b.level/3) rotate(b.angle) sprite(img[b.type].img,0,0,img[b.type].w*b.level/3,img[b.type].h*b.level/3) popMatrix() noTint() end --fog if MistOn==1 then for i,m in pairs(fog) do tint(255,(1-(m.y/(HEIGHT/2)))*40) sprite(mist,m.x,m.y,m.fx*200*m.scalex,m.fy*50) m.x = m.x + m.spd if m.x<-600 or m.x>WIDTH+600 then m.spd = m.spd * -1 end if m.spd==0 then m.spd=1 end noTint() end end if RainOn==1 then --lightning bolt if math.random(400)==1 then background(255) end for i,r in pairs(raindrop) do tint(0,70) pushMatrix() translate(r.x,r.y) rotate(rainangle) sprite(rain,0,0,10*r.level/2) popMatrix() r.x=r.x+rainspeed*r.level*math.sin(math.rad(rainangle)) r.y=r.y-rainspeed*r.level*math.cos(math.rad(rainangle)) if r.y<0 then r.y=HEIGHT+20 r.x=-300+math.random(WIDTH+600) end end end tint(0,255) for i=0,WIDTH,60 do sprite(rail,i,100) end noTint() end function touched(t) Centre = vec2(-0.5 + t.x/ WIDTH , -0.5 + t.y/HEIGHT) end --# Gradient Gradient = {} function Gradient.setup() Gradient.mesh = mesh() Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) Gradient.mesh.shader = shader( [[ uniform mat4 modelViewProjection; uniform lowp vec2 aspect; // set to (1,1) for circular gradient, (0,1) or (1,0) for linear, or fractional, eg (1, 0.5) for oval attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; // varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { // vColor = color; vTexCoord = (texCoord - vec2(0.5,0.5)) * aspect; gl_Position = modelViewProjection * position; } ]], [[ precision highp float; const int no = 3; //the number of gradation points you want to have // uniform lowp sampler2D texture; uniform lowp vec2 centre; // centre of gradient uniform float step[no]; // transition points between colours, 0.0 - 1.0 uniform lowp vec4 colors[no]; // colours to grade between //varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying float vDistance; void main() { float dist = distance( vTexCoord, centre); lowp vec4 col = colors[0]; for (int i=1; i<no; ++i) { col = mix(col, colors[i], smoothstep(step[i-1], step[i], dist)); } gl_FragColor = col; //texture2D( texture, vTexCoord ) * col; } ]] ) end --adapted from yojimbo2000 function polygon(points) if #points<3 then return end local verts=triangulate(points) local cols={} for a=1, #verts do cols[a]=color(fill()) end local poly=mesh() poly.vertices=verts poly.colors=cols poly:draw() if strokeWidth()>0 then local a,b for a=1, #points do if a==#points then b=1 else b=a+1 end line(points[a].x,points[a].y,points[b].x,points[b].y) end end end
  • Posts: 2,020

    Love it!

  • IgnatzIgnatz Mod
    Posts: 5,396

    Nice! I would halve the size of the rain sprites, though, they look a bit blocky.

  • Posts: 708

    Yes @Ignatz I agree they do look a bit chunky.

    Here's a spooky forest using the same shader. There is a small bug where the top of a tree sometimes appears thicker and some work could be done to unify the palettes - it'll look a bit odd if you change the sky colour - the trees should be tinted from the same base, but I'm tired and off to bed!


    --# Main -- MultiStop Colour Gradients function setup() print("Drag down for more options") Gradient.setup() parameter.number("GradientAspect", 0, 2, WIDTH/HEIGHT) parameter.number("Step1", 0, 1, 0.6) parameter.number("Step2", 0, 1, 0.0501) parameter.number("Step3", 0, 1, 0.050) parameter.integer("MoonMove",0,0,1) parameter.color("Color1", color(86,11,55, 255)) parameter.color("Color2", color(207,53,85, 255)) parameter.color("Color3", color(255, 255, 255, 255)) Centre = vec2(-0.22,0.2) moon=vec2(WIDTH/4,HEIGHT/3) -- c={color(115,20,34,255),color(65,15,36),color(0,0,0)} c={color(88,7,20,100),color(65,15,36),color(0,0,0)} foffset={0,WIDTH/20,-WIDTH/20} forest={} for f=1,3 do treelayer={} numtrees=7 for i=1,numtrees do local xoff=-20+math.random(39) table.insert(treelayer,{im=generateTree(c[f]),x=(i-1)*WIDTH/(numtrees-1)+xoff+foffset[f],y=HEIGHT/2}) end table.insert(forest,treelayer) end end function draw() if MoonMove==1 then Centre.y=math.sin(ElapsedTime*0.5)/2 Centre.x=-math.cos(ElapsedTime*0.5)/3 end moon.x=(Centre.x+0.5)*WIDTH moon.y=(Centre.y+0.5)*HEIGHT background(86,11,55, 255) Gradient.mesh.shader.centre = Centre Gradient.mesh.shader.aspect = vec2(GradientAspect, 1) Gradient.mesh.shader.colors = {Color1, Color2, Color3} Gradient.mesh.shader.step = {Step1, Step2, Step3} Gradient.mesh:draw() fill(255) -- This sets the line thickness strokeWidth(5) for j,f in pairs(forest) do for i,t in pairs(f) do sprite(t.im,t.x,t.y,40+j*50,HEIGHT) end end end function touched(t) Centre = vec2(-0.5 + t.x/ WIDTH , -0.5 + t.y/HEIGHT) end function generateTree(col) local treepoints={} treemesh=mesh() treemesh:clear() local branchdensity=8 for i=1,branchdensity do local h=((i-1)/(branchdensity-1))*HEIGHT local adj=math.random(15)-8 table.insert(treepoints,i,vec2(100+adj,h)) table.insert(treepoints,i+1,vec2(150+adj,h)) end --add in branches --start from right hand side bottom and work round counter clockwise local s=#treepoints for i=s-1,2,-1 do if i~=10 and i~=11 and math.random(3)==1 then if i>10 then table.insert(treepoints,i+1,vec2(treepoints[i].x-1,treepoints[i].y-10-math.random(10))) table.insert(treepoints,i+1,vec2(treepoints[i].x+50+math.random(70),treepoints[i].y+25+math.random(10))) else table.insert(treepoints,i+1,vec2(treepoints[i].x+(1),treepoints[i].y+10+math.random(10))) table.insert(treepoints,i+1,vec2(treepoints[i].x-50-math.random(70),treepoints[i].y+25+math.random(10))) end end end treemesh.vertices=triangulate(treepoints) treemesh:setColors(col) local img=image(250,HEIGHT) setContext(img) treemesh:draw() setContext() return(img) end --# Gradient Gradient = {} function Gradient.setup() Gradient.mesh = mesh() Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) Gradient.mesh.shader = shader( [[ uniform mat4 modelViewProjection; uniform lowp vec2 aspect; // set to (1,1) for circular gradient, (0,1) or (1,0) for linear, or fractional, eg (1, 0.5) for oval attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; // varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { // vColor = color; vTexCoord = (texCoord - vec2(0.5,0.5)) * aspect; gl_Position = modelViewProjection * position; } ]], [[ precision highp float; const int no = 3; //the number of gradation points you want to have // uniform lowp sampler2D texture; uniform lowp vec2 centre; // centre of gradient uniform float step[no]; // transition points between colours, 0.0 - 1.0 uniform lowp vec4 colors[no]; // colours to grade between //varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying float vDistance; void main() { float dist = distance( vTexCoord, centre); lowp vec4 col = colors[0]; for (int i=1; i<no; ++i) { col = mix(col, colors[i], smoothstep(step[i-1], step[i], dist)); } gl_FragColor = col; //texture2D( texture, vTexCoord ) * col; } ]] ) end
  • Posts: 2,020

    These are very cool. You're the master of 2D procedural art! One thing though, I think the moon has too hard an edge with step2 set at 0.0501, I can see aliasing. I prefer giving it a slightly softer edge, around 0.052 looks good to me (ie an approximately 2-pixel fall-off at the edge of the moon).

  • Posts: 708

    Thanks - with the vector based art I like the sharp edges of the moon - but with your approach it allows it to be easily changed

  • Posts: 289

    i wanna make the rain drop effect like 3d matrixworld screensaver, how to make the gradient color when text() chars vertically on screen?

    str:gsub(".", function(c)
            print(c)
            text(c ,w/2,h)
            local _,hh=textSize(c)
            h = h + hh*0.7
        end)
    
  • edited October 2015 Posts: 2,020

    Here's a version that does vertical text, right-to-left. Note that using gsub(".") only works with single-byte characters (The smiley at the end won't display). If you want to parse multi-byte text, you should use Lua 5.3's UTF8 library.

    EDIT: changed the name of the shader variable from step to gradStep

    -- MultiStop Colour Gradients
    
    displayMode(FULLSCREEN)
    supportedOrientations(LANDSCAPE_ANY)
    
    function setup()
        font("DINAlternate-Bold")
        fontSize(70)
        textAlign(CENTER)
        textMode(CORNER)
        textWrapWidth(WIDTH*0.8)
        fill(255)
    
        Gradient.setup("Does anyone remember the Commodore Amiga? It had a chip called the COPPER. This could change the colour value each line of the display. I still think of colour gradients like these as “COPPER bars” \u{1f61d}")
    end
    
    function draw()
        background(37, 23, 38, 255)
        Gradient.mesh:draw()
    end
    
    Gradient = {}
    
    function Gradient.setup(txt)
    
        local w,h = textSize(txt)
        local img = image(w,h)
        setContext(img)
        local charW, charH = textSize("W")
        charH = charH * 0.8
        local i, curW, curH = 0
        local lineH = h//charH
        print("lineh", lineH)
        print(txt)
        txt:gsub(".", function(c)
            print(c)
            curW = w - charW * ((i//lineH)+1)
            curH = h - charH * ((i%lineH)+1)
            i = i + 1
            text(c, curW, curH)
        end)
        setContext()
        Gradient.mesh = mesh()
        Gradient.mesh.texture = img
        Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, w,h)
        Gradient.mesh.shader = 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; //- vec2(0.5,0.5);
            gl_Position = modelViewProjection * position;
        }
        ]],
        [[
        precision highp float;
    
        const int no = 6; //the number of gradation points you want to have
    
        uniform lowp sampler2D texture;
        uniform float gradStep[no];  // transition points between colours, 0.0 - 1.0
        uniform lowp vec4 colors[no]; // colours to grade between
        uniform float repeats;
    
        //varying lowp vec4 vColor;
        varying highp vec2 vTexCoord;
        varying float vDistance;
    
        void main()
        {
            lowp vec4 col = colors[0];
            for (int i=1; i<no; ++i) {
                col = mix(col, colors[i], smoothstep(gradStep[i-1], gradStep[i], fract(vTexCoord.y*repeats)));
            }
            gl_FragColor = texture2D( texture, vTexCoord ) * col;
        }
        ]]
        )
        Gradient.mesh.shader.colors = {
        color(27, 29, 50, 255),
        color(41, 164, 218, 255),
        color(222, 231, 255, 255),
        color(79, 39, 31, 255),
        color(188, 53, 53, 255),
        color(255, 255, 100, 255)}
        Gradient.mesh.shader.gradStep = {0.8, 0.5, 0.45, 0.43, 0.35, 0.09}
        Gradient.mesh.shader.repeats = h/charH --lineH  --how many times the texture repeats. set this to the number of lines
    end
    
  • Jmv38Jmv38 Mod
    Posts: 3,278

    i get this error

    ERROR: 0:6: Attempt to redeclare 'step' as a variable
    ERROR: 0:18: Attempt to use 'step' as a variable
    ERROR: 0:18: Attempt to use 'step' as a variable
    
  • Posts: 2,020

    That's interesting. GLES does have a built in function called step I just realised, so it's probably a reserved word. But it's interesting that it works on my setup and not yours (different versions of GLES between iOS versions? Are you iOS 7 still?)

  • Posts: 2,020

    @Jmv38 in the above code, I changed the name of the variable from step to gradStep, could you see if you still get the error with this version?

  • Jmv38Jmv38 Mod
    Posts: 3,278

    ios7. Now it runs. but the text is completely changed to a text that is meaningless. Strange...

  • Posts: 2,020

    It's vertical, and right-to-left

  • Jmv38Jmv38 Mod
    Posts: 3,278

    here is what i get ???

  • Posts: 2,020

    That's what it's meant to do. I know, not very useful.

  • @yojimbo2000, @West--

    I overall just want to say that these rock. Like layer tennis, as @Ignatz said.

    In playing with it I think I've found an error and I wonder if you can tell me how to correct it.

    Setting the aspect parameter of the shader to account for the screen dimensions seems to make the circle become drawn off-center.

    If you set the aspect to 1, 1, and the centre parameter to 0.5, 0.5, the ellipse is drawn with its center right in the corner, as it should be.

    But with the same centre setting, if you set the aspect to (WIDTH/HEIGHT, 1), which is what I think you do in this code, the ellipse is no longer centered in the corner. It seems to get drawn somewhat to the right of where it should get drawn. In effect, the centre parameter no longer determines the actual center.

    Can either of you guys advise me how to adjust it so it centers around the centre? I don't understand from looking at the shader code what exactly is going on.

  • Posts: 708

    @UberGoober I had a quick look at it - it seems that the issue is to do with the way Codea handles screen width at setup.

    I suspect that it all works ok if you have the sidebar present but when you move to displayMode(FULLSCREEN) you get the issue you describe.

    If you set the aspect ratio to be (749/HEIGHT,1) then this should resolve the problem (assuming a normal fullscreen width of 1024). Horribly clunky but will work.

    749 is the width returned (in my iPad) when I first start Codea - it is also the width of the drawing area with the sidebar present.

    I suspect that when the shader is set up it is reading in the width of the texture area to be the fullscreen width minus the sidebar (in vTexCoord).

    However, my shader knowledge is extremely limited and I looked at this a fair while ago

    @Simeon @John Can you shed any light on what's going on? Feels like a bug

    Here is a slightly modified version of the original - swap in and out the displayMode and gradientAspect values.


    --# Main -- MultiStop Colour Gradients w=WIDTH displayMode(FULLSCREEN) function setup() print ("Drag your finger to set the gradient centre") print ("Pull down this output window to access more settings") Gradient.setup() parameter.number("GradientAspect", 0, 2, 0.5) parameter.number("Step1", 0, 1, 0.9) parameter.number("Step2", 0, 1, 0.3) parameter.number("Step3", 0, 1, 0.2) parameter.number("Step4", 0, 1, 0.1) parameter.number("Step5", 0, 1, 0.05) parameter.color("Color1", color(31, 27, 53, 255)) parameter.color("Color2", color(0, 198, 255, 255)) parameter.color("Color3", color(228, 207, 225, 255)) parameter.color("Color4", color(254, 0, 0, 255)) parameter.color("Color5", color(255, 255, 1, 255)) Centre = vec2(0.5,0.5) end function draw() GradientAspect=WIDTH/HEIGHT -- GradientAspect=1024/768 -- GradientAspect=749/768 background(40, 40, 50) Gradient.mesh.shader.aspect = vec2(GradientAspect, 1) Gradient.mesh.shader.centre = Centre Gradient.mesh.shader.colors = {Color1, Color2, Color3, Color4, Color5} Gradient.mesh.shader.step = {Step1, Step2, Step3, Step4, Step5} Gradient.mesh:draw() text(w,WIDTH/2,HEIGHT/2) end function touched(t) Centre = vec2(-0.5 + t.x/ WIDTH , -0.5 + t.y/HEIGHT) end --# Gradient Gradient = {} function Gradient.setup() Gradient.mesh = mesh() Gradient.mesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) Gradient.mesh.shader = shader( [[ uniform mat4 modelViewProjection; uniform lowp vec2 aspect; // set to (1,1) for circular gradient, (0,1) or (1,0) for linear, or fractional, eg (1, 0.5) for oval attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; // varying lowp vec4 vColor; varying highp vec2 vTexCoord; void main() { // vColor = color; vTexCoord = (texCoord - vec2(0.5,0.5)) * aspect; gl_Position = modelViewProjection * position; } ]], [[ precision highp float; const int no = 5; //the number of gradation points you want to have // uniform lowp sampler2D texture; uniform lowp vec2 centre; // centre of gradient uniform float step[no]; // transition points between colours, 0.0 - 1.0 uniform lowp vec4 colors[no]; // colours to grade between //varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying float vDistance; void main() { float dist = distance( vTexCoord, centre); lowp vec4 col = colors[0]; for (int i=1; i<no; ++i) { col = mix(col, colors[i], smoothstep(step[i-1], step[i], dist)); } gl_FragColor = col; //texture2D( texture, vTexCoord ) * col; } ]] ) end
  • dave1707dave1707 Mod
    edited October 2017 Posts: 7,057

    Change the displayMode to OVERLAY and comment out the GradientAspect in draw and the slider for GradientAspect should work after you center it image.

  • You guys seem to have hit it exactly, in my testing.

    @yojimbo2000, @West:

    Below is my attempt to generalize the code to make it an all-purpose ellipse drawer, please give it a run and let me know what you think.

    • you'll notice I did a lot of decomposition and renaming, which I always need to do to understand things. Do forgive me if there is any sacrilege committed!
    • When I saw that the shader parameters were tables, I thought that meant you could create many more rings just by adding more colors & steps. That causes an error though.
    • That shouldn't be so hard a change to make thought, should it? I can sort of see where you'd do it in the code but I don't know how. Maybe could one of you take a look and see? I imagine it's a matter of generalizing those for loops...
    --# Main
    
    --from Yojimbo3000 ellipse and gradient drawer and West graveyard
    
    function setup()
        --graveyard and moon stuff
        print("Drag down for more options")
        displayMode(STANDARD)
        Gradient.setup()
        setupParameters()
        generateGraveyard()
    
        --as shaderEllipse called with no parameters creates a default in the center of the screen
        defaultEllipse = shaderEllipse()
    
        --a customized shaderEllipse
        customCentre = vec2(0.05, 0.19) 
        customColors = {
            color(51, 23, 53, 46),
            color(0, 198, 255, 85), 
            color(220, 116, 117, 76)}
        customSizes = {0.3, 0.29, 0.28}
        customEllipse = shaderEllipse(customCentre, customColors, customSizes)
    end
    
    function draw()
        background(40, 40, 50)
        positionMoon()
        drawEllipsii()
        drawGraveyard()
    end
    
    function setupParameters()
        parameter.number("GradientAspect", 0, 2, WIDTH/HEIGHT)
        parameter.number("Step1", 0, 1, 0.6)
        parameter.number("Step2", 0, 1, 0.06)
        parameter.number("Step3", 0, 1, 0.05)
        parameter.integer("MoonMove",0,1,1)
        parameter.color("Color1",
        color(31, 27, 53, 255))
        parameter.color("Color2",
        color(0, 198, 255, 255))
        parameter.color("Color3",
        color(255, 255, 255, 255))
        parameter.watch("Gradient.mesh.shader.centre")
    end
    
    function generateGraveyard()
        moonReferencedByGraveyard=vec2(WIDTH/2,HEIGHT/2)
        bgobj={}
        local numobj=15
        for i=1,numobj do
            local lev=1
            if i>numobj*0.8 then
                lev=3
            elseif i>numobj*0.5 then
                lev=2
            end
            table.insert(bgobj,{x=math.random(WIDTH),y=75,level=lev,angle=-8+math.random(15),type=math.random(2)})
        end
    
        cross=image(100,200)
        setContext(cross)
        fill(255)
        rect(40,0,20,200)
        rect(0,140,100,20)
        setContext()
    
        gravestone=image(100,200)
        setContext(gravestone)
        fill(255)
        rect(10,0,80,120)
        ellipse(50,100,80)
        setContext()
    
        rail=image(60,200)
        setContext(rail)
        fill(255)
        rect(25,0,10,140)
        rect(-10,110,80,5)
        rect(-10,100,80,5)
        ellipse(30,145,20)
        setContext()
    
        img={}
        table.insert(img,{img=cross,w=100,h=200})
        table.insert(img,{img=gravestone,w=100,h=200})
    end
    
    function positionMoon()
        --if true then return end
        local newPosition = vec2(0,0)
        if MoonMove==1 then
            newPosition.x = math.sin(ElapsedTime*0.5)/2
            newPosition.y = math.cos(ElapsedTime*0.5)/3
            Gradient.mesh.shader.centre = newPosition
        end
        moonReferencedByGraveyard.x=(newPosition.x+0.5)*WIDTH
        moonReferencedByGraveyard.y=(newPosition.y+0.5)*HEIGHT
    end
    
    function drawEllipsii()
        --moon stuff
        Gradient.mesh.shader.aspect = vec2(GradientAspect, 1)
        Gradient.mesh.shader.colors = {Color1, Color2, Color3}
        Gradient.mesh.shader.step = {Step1, Step2, Step3}
        Gradient.mesh:draw()
        --shaderEllipse stuff
        customEllipse:draw()
        defaultEllipse:draw()
    end
    
    function drawGraveyard()
        for i,b in pairs(bgobj) do
            if b.level==3 then
                tint(0,255)
            elseif b.level==2 then
                local a=0.4
                tint(Color1.r*a,Color1.g*a,Color1.b*a,255)
            else
                local fade=math.max(255-(moonReferencedByGraveyard.y/HEIGHT)*255,100)
                tint(0,fade)
            end
            pushMatrix()
            translate(b.x,b.y*b.level/3)
            rotate(b.angle)
            sprite(img[b.type].img,0,0,img[b.type].w*b.level/3,img[b.type].h*b.level/3)
            popMatrix()
            noTint()
        end
        tint(0,255)
        for i=0,WIDTH,60 do
            sprite(rail,i,100)
        end
        noTint()
    end
    
    
    
    --# Gradient
    Gradient = {}
    
    function Gradient.setup()
        local center = vec2(0,0)
        local colors = {
            color(31, 27, 53, 255),
            color(0, 198, 255, 255),
            color(255, 255, 255, 255)}
        local sizes = {0.6, 0.06, 0.05}
        Gradient.mesh = shaderEllipse(center, colors, sizes)
    end
    
    --# ShaderEllipse
    function shaderEllipse(centreVec2, colorTable, sizeTable)
        local newMesh = mesh()
        --a screen sized rect to draw the ellipse on:
        newMesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
        --the shader used to draw the ellipse on that mesh:
        newMesh.shader = shader(
        [[
        uniform mat4 modelViewProjection;
        uniform lowp vec2 aspect; // set to (1,1) for circular gradient, (0,1) or (1,0) for linear, or fractional, eg (1, 0.5) for oval
    
        attribute vec4 position;
        attribute vec4 color;
        attribute vec2 texCoord;
    
        // varying lowp vec4 vColor;
        varying highp vec2 vTexCoord;
    
        void main()
        {
        // vColor = color;
        vTexCoord = (texCoord - vec2(0.5,0.5)) * aspect;
        gl_Position = modelViewProjection * position;
        }
        ]],
        [[
        precision highp float;
    
        const int no = 3; //the number of gradation points you want to have
    
        // uniform lowp sampler2D texture;
        uniform lowp vec2 centre; // centre of gradient
        uniform float step[no];  // transition points between colours, 0.0 - 1.0
        uniform lowp vec4 colors[no]; // colours to grade between
    
        //varying lowp vec4 vColor;
        varying highp vec2 vTexCoord;
        varying float vDistance;
    
        void main()
        {
        float dist = distance( vTexCoord, centre);
        lowp vec4 col = colors[0];
        for (int i=1; i<no; ++i) {
        col = mix(col, colors[i], smoothstep(step[i-1], step[i], dist));
        }
        gl_FragColor = col; //texture2D( texture, vTexCoord ) * col;
        }
        ]]
        )
        --default for the shader aspect ratio (changing this defines an oval)
        newMesh.shader.aspect = vec2(WIDTH/HEIGHT, 1)
        --if no valid parameters are given a default ellipse is supplied
        if centreVec2 == nil or colorTable == nil or sizeTable == nil then
            print("error: invalid parameters for shaderEllipse, using defaults")
            --default center of ellipse is middle of mesh
            newMesh.shader.centre = vec2(0,0)
            --the size and colors of a default ellipse(goes from outside in)
            newMesh.shader.colors = {
                color(30, 62, 81, 0),
                color(29, 45, 60, 255),
                color(145, 207, 223, 255)
            }
            newMesh.shader.step = { 0.05, 0.045, 0.042}
        else
            newMesh.shader.centre = centreVec2
            newMesh.shader.colors = colorTable
            newMesh.shader.step = sizeTable
        end
        return newMesh
    end
    
  • For a little while now, I've been trying to do it myself, but I'm coming up against the whole thing where shaders are in a different language.

    When I inspect the shader code, it seems like all you'd have to do is take the no which is defined as const int no= 3 and make it a different number. It seems like it should all work if you could manually change that number.

    Trying that gets me some the error messages, and trying to adjust for those messages gets me some other error messages, from all of which I've learned these two things:

    • to make no changeable, it can't be declared const anymore
    • to make no function as a parameter in an array declaration, it must be declared const.

    ...so solving the catch-22 there is beyond me. This seems like it must be possible to do, but I'm stymied. Any help?

Sign In or Register to comment.