My first shader question

So after skimming @Ignatz’s wonderful shader ebook and looking around the web and forums, I decided to whip up a quick lighting shader, which was easier than I imagined, but then I hit a roadblock. I wanted to make the center of the light have a bright redish tint (as if from a fire), then only a slight tint as you move away from the centre, but due to the alpha approaching zero as you approach the center, when I tried to tint it it ended up being brighter around the edges. I tried to find a workaround, but with no luck, unfortunately. If anyone knows how it can be done, I’d appreciate it greatly. This is my first shader work, and though I made it this far the rest confuses me. Below is my shader code (fully functional, except for my tinting idea)

function setup()
    m = mesh()
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m:setColors(color(255))
    m.shader = shader(lightshader.v,lightshader.f)
    a,b,c = {},{},{}
    for i=1,5 do
        if i==1 then a[1],b[1] = vec3(WIDTH/2,HEIGHT/2,150),true else
            a[i],b[i] = vec3(WIDTH/2,HEIGHT/2,150),false
        end
        c[i]=1
    end
    m.shader.lights = a
    m.shader.on = b
    m.shader.flicker = c
    fps=60
    lightstate(2,true)
end
function draw()
    background(255)
    sprite("Cargo Bot:Codea Icon",WIDTH/2,HEIGHT/2,WIDTH)
    setflicker(1)
    setflicker(2,10)
    m:draw()
    output.clear()
    fps=.9*fps+.1/DeltaTime
    print(fps)
end
function touched(t)
    movelight(1,vec3(t.x,t.y,150))
end
function movelight(index,newval)
    a[index] = newval
    m.shader.lights = a
end
function lightstate(index,newval)
    b[index] = newval
    m.shader.on = b
end
function setflicker(index,factor,flicker)
    factor = factor or 15
    if flicker then c[index] = flicker else
        c[index] = 1+noise(ElapsedTime*(.5+math.random()))/factor
    end
    m.shader.flicker = c
end
lightshader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
varying highp vec4 vPosition;
void main()
{
    vPosition = position;
    gl_Position = modelViewProjection * position;
}]],
f=[[
uniform highp float num;
precision highp float;
uniform lowp sampler2D texture;
uniform highp vec3 lights[5];
uniform bool on[5];
uniform float flicker[5];
varying lowp vec4 vColor;
varying highp vec4 vPosition;
void main()
{
    highp float d = 0.0;
    for (int i=0; i<= 5; i++) {
        if (on[i]==true) {
        highp float f = lights[i].z*flicker[i];
        d=d+clamp(1.0-distance(vPosition,vec4(lights[i].x,lights[i].y,0,1))/f+0.1,0.0,1.0);
        };
        };
    gl_FragColor = vec4(0.,0.,0.,1.0-d);
}]]
}

@Monkeyman31213 - you can’t make the middle red if that is where your shader is drawing nothing at all. You need to draw the other way round, using the shader to actually draw the background image.

so in setup
m.shader.texture="Cargo Bot:Codea Icon"

and in draw, don’t sprite the Cargo Bot icon

and use this shader, which draws the icon and shades it red at the same time - see adjustment for red at the bottom

lightshader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec4 vPosition;
varying highp vec2 vTexCoord;
void main()
{
    vPosition = position;
vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}]],
f=[[
uniform highp float num;
precision highp float;
uniform lowp sampler2D texture;
uniform highp vec3 lights[5];
uniform bool on[5];
uniform float flicker[5];
varying lowp vec4 vColor;
varying highp vec4 vPosition;
varying highp vec2 vTexCoord;
void main()
{
    highp float d = 0.0;
    highp float r=0.0;
    for (int i=0; i<= 5; i++) {
        if (on[i]==true) {
        highp float f = lights[i].z*flicker[i];
        d=d+clamp(1.0-distance(vPosition,vec4(lights[i].x,lights[i].y,0,1))/f+0.1,0.0,1.0);
        };
        };
    lowp vec4 col = texture2D(texture, vTexCoord) ;
    col.a=d;
    col.r=d+0.25; //vary this for red colour
    gl_FragColor = col;
}]]
}

 

@Ignatz Ah, that works beautifully :slight_smile:
Unfortunately, I think that way will eventually cause more lag as I’ll have to draw everything to an image then redraw that image with the shader active.
I got what I was thinking of half-working here… You’ll see why I say half.

lightshader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
varying highp vec4 vPosition;
void main()
{
    vPosition = position;
    gl_Position = modelViewProjection * position;
}]],
f=[[
uniform highp float num;
precision highp float;
uniform lowp sampler2D texture;
uniform highp vec3 lights[5];
uniform bool on[5];
uniform float flicker[5];
varying lowp vec4 vColor;
varying highp vec4 vPosition;
void main()
{
    highp float d = 0.0;
    for (int i=0; i<= 5; i++) {
        if (on[i]==true) {
        highp float f = lights[i].z*flicker[i];
        d=d+clamp(1.0-distance(vPosition,vec4(lights[i].x,lights[i].y,0,1))/f+0.1,0.0,1.0);
        };
        };
    highp float q = (d*d*d*d)/2.5;
    gl_FragColor = vec4(0.+q,0.,0.,1.0-d+q);
}]]
}

@Monkeyman32123 - you could always cheat

  1. Create an image which is shaded red in the middle and fades toward the edges, ie it just does the red colouring you want. You may need a shader to do this nicely, but you only need to do it once, then keep the image in memory. Make it as big as your largest light radius.

  2. Draw everything except the shader stuff

  3. Sprite the red image over your light positions, resized to their radius

  4. Use your original shader

Hmmm, that’s clever, never though of that!
I will try that as soon as I get the chance, thanks for the suggestion :slight_smile:

Since I’m here though, an addition to my question: that version lags heavily when you turn on more than three lights at a time, is there a way to get the same effect with higher speed?

First, test if it is the shader or spriting the red image, that is causing the slowdown (eg by turning the red image off). Then you know where to look for improvement.

It’s the shader. It’s fine with only two lights, but past that it causes tremendous lag.

Unfortunately that is to be expected, when you think about the fact that the fragment shader code gets run for every pixel on the screen, at each frame

There is a possible way to cheat, by not using a shader at all, but creating an image in memory, making it black, setting blendMode to additive, spriting the red shaded images onto it, then spriting that combined image onto the screen.

But don’t bother, I tried it, and it is too slow.

Basically, multiple lights are really slow - unless maybe you buy the newest iPad! :wink:

@Ignatz The original iPad Air is still cool…even though the iPad Air 2 is 40% faster… :((

Another approach which allows you to do the lighting as a final pass. By putting

#extension GL_EXT_shader_framebuffer_fetch : require

in the fragment shader you can get the current color of the pixel back during processing with gl_LastFragData[0] :

function setup()
    m = mesh()
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m:setColors(color(255))
    m.shader = shader(lightshader.v,lightshader.f)
    
    a,b,c = {},{},{}
    for i=1,5 do
        if i==1 then a[1],b[1] = vec3(WIDTH/2,HEIGHT/2,150),true else
            a[i],b[i] = vec3(WIDTH/2,HEIGHT/2,150),false
        end
        c[i]=1
    end
    m.shader.lights = a
    m.shader.on = b
    m.shader.flicker = c
    fps=60
    lightstate(2,true)
end
function draw()
    background(0)
    sprite("Cargo Bot:Codea Icon",WIDTH/2,HEIGHT/2,WIDTH)
    setflicker(1)
    setflicker(2,10)
    m:draw()
    output.clear()
    fps=.9*fps+.1/DeltaTime
    print(fps)
end
function touched(t)
    movelight(1,vec3(t.x,t.y,150))
end
function movelight(index,newval)
    a[index] = newval
    m.shader.lights = a
end
function lightstate(index,newval)
    b[index] = newval
    m.shader.on = b
end
function setflicker(index,factor,flicker)
    factor = factor or 15
    if flicker then c[index] = flicker else
        c[index] = 1+noise(ElapsedTime*(.5+math.random()))/factor
    end
    m.shader.flicker = c
end
lightshader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
varying highp vec4 vPosition;
void main()
{
    vPosition = position;
    gl_Position = modelViewProjection * position;
}]],
f=[[
#extension GL_EXT_shader_framebuffer_fetch : require

uniform highp float num;
precision highp float;
uniform highp vec3 lights[5];
uniform bool on[5];
uniform float flicker[5];
varying lowp vec4 vColor;
varying highp vec4 vPosition;
void main()
{
    highp float d = 0.0;
    highp float r=0.0;
    for (int i=0; i<= 5; i++) {
        if (on[i]==true) {
        highp float f = lights[i].z*flicker[i];
        d=d+clamp(1.0-distance(vPosition,vec4(lights[i].x,lights[i].y,0,1))/f+0.1,0.0,1.0);
        };
        };
    lowp vec4 col = gl_LastFragData[0] ;
    col.rgb = col.rgb * d;
    
    col.r = col.r + d-.25*2.0; //vary this for red colour
    
    col.a = 1.0;
    
    gl_FragColor = col;
}]]
}

@spacemonkey - that is very cool… :-bd

Slightly bored so tweaked a multitouch lights fade away version…

function setup()
    m = mesh()
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m:setColors(color(255))
    m.shader = shader(lightshader.v,lightshader.f)
    --m.shader.texture="Cargo Bot:Codea Icon"
    
    a,b,c = {},{},{}
    touches = {}
    for i=1,5 do
        touches[i] = {touch = nil, lasttouch = 600}
        if i==1 then a[1],b[1] = vec3(WIDTH/2,HEIGHT/2,150),1 else
            a[i],b[i] = vec3(WIDTH/2,HEIGHT/2,150),0
        end
        c[i]=1
    end
    m.shader.lights = a
    m.shader.on = b
    m.shader.flicker = c
    fps=60
    --lightstate(2,true)
    
    nextlight = 1
end
function draw()
    background(0)
    sprite("Cargo Bot:Codea Icon",WIDTH/2,HEIGHT/2,WIDTH)
    setflicker(1)
    setflicker(2,10)
    m:draw()
    output.clear()
    fps=.9*fps+.1/DeltaTime
    print(fps)
    decayLight()
end

function decayLight()
    for i = 1,5 do
        t = ElapsedTime - touches[i].lasttouch
        if t > 1 then
            lightstate(i, 1/t)
        end
        if t > 10 then
            lightstate(i, 0)
            v = nil
        end
    end
end

function touched(t)
    light = 0
    for i = 1,5 do
        if touches[i].touch == t.id then
            light = i
        end
    end
    if light == 0 then
        light = nextlight + 1
        nextlight = (nextlight + 1) % 5
        touches[light] = { touch = t.id }
    end
    touches[light].lasttouch = ElapsedTime
    movelight(light,vec3(t.x,t.y,150))
    lightstate(light,1)
end

function movelight(index,newval)
    a[index] = newval
    m.shader.lights = a
end
function lightstate(index,newval)
    b[index] = newval
    m.shader.on = b
end
function setflicker(index,factor,flicker)
    factor = factor or 15
    if flicker then c[index] = flicker else
        c[index] = 1+noise(ElapsedTime*(.5+math.random()))/factor
    end
    m.shader.flicker = c
end
lightshader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
varying highp vec4 vPosition;
void main()
{
    vPosition = position;
    gl_Position = modelViewProjection * position;
}]],
f=[[
#extension GL_EXT_shader_framebuffer_fetch : require

uniform highp float num;
precision highp float;
uniform highp vec3 lights[5];
uniform float on[5];
uniform float flicker[5];
varying lowp vec4 vColor;
varying highp vec4 vPosition;
void main()
{
    highp float d = 0.0;
    highp float r=0.0;
    for (int i=0; i<= 5; i++) {
        if (on[i] > 0.0) {
        highp float f = lights[i].z*flicker[i];
        d=d+(clamp(1.0-distance(vPosition,vec4(lights[i].x,lights[i].y,0,1))/f+0.1,0.0,1.0))*on[i];
        };
        };
    lowp vec4 col = gl_LastFragData[0] ;
    col.rgb = col.rgb * d;
    
    col.r = col.r + d-.25*2.0; //vary this for red colour
    
    col.a = 1.0;
    
    gl_FragColor = col;
}]]
}

@spacemonkey - have you played with any other extensions?

For example, I wondered if the “instance” extension was possible/useful, but I don’t understand the documentation enough to see if it works

I’ve only played with a couple where I had a need. I might have an explore back through them at some stage and see if anything else piques my interest.

This page is quite handy: https://developer.apple.com/library/ios/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/OpenGLESPlatforms/OpenGLESPlatforms.html It lists opengl ES compatability for devices and the supported extensions from apple side…

Further to that, I haven’t tried any code but the instance thing while very interesting I think needs a different call at the OpenGL (Codea) side, not just the stuff in the shader, it calls “glDrawArraysInstanced” or “glDrawElementsInstanced” and tells it the number of instances. So without the ability to mesh:drawInstanced(numInstances) or some-such in the Codea API I don’t think we’ll be able to play with this one. So to get it it’s probably an @Simeon enhancement.