Pointlight shadows in screen space

I have been working on a project where one could calculate shadows for small pointlights. This project shows the result with a bigger pointlight in the scene. I hope you like it!


--# Main
function setup()
    pi,ti,sin,cos,rad=math.pi,table.insert,math.sin,math.cos,math.rad
    scene={Create:Box(vec3(0,-5,0),vec3(100,5,100)),
    Create:Box(vec3(10,0,40),vec3(15,15,15)),
    Create:Sphere(vec3(60,8,30),8,25),
    Create:Cyl(vec3(20,0,20),7,20,40,0)
    }
    for i=1,#scene do scene[i].shader=shader(BufferVertex,BufferFrag) scene[i].shader.lr=300 end
    --Screen
    M=mesh()
    M.vertices={vec2(0,0),vec2(WIDTH,0),vec2(WIDTH,HEIGHT),vec2(WIDTH,HEIGHT),vec2(0,HEIGHT),vec2(0,0)}
    M.texCoords={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)}
    M.shader=shader(LightVertex,LightFrag)
    M.shader.lr=300
    M.shader.FA=vec2(math.tan(math.rad(25)),WIDTH/HEIGHT)
    --Variabler
    parameter.watch("1/DeltaTime")
    parameter.number("lx",0,100,85)
    parameter.number("px",-100,200,-50)
    parameter.number("pz",-100,200,-50)
    Pos=vec3(-50,75,-50)
    Eye=vec3(25,0,25)
    Ljus=vec3(0,25,30)
    Depth1=image(WIDTH,HEIGHT)
    Depth2=image(WIDTH,HEIGHT)
    Normals=image(WIDTH,HEIGHT)
end

function draw()
    Ljus.x=lx
    Pos.x=px
    Pos.z=pz
for j=1,3 do
    if j==1 then setContext(Depth1,true) elseif j==2 then setContext(Depth2,true) else setContext(Normals,true) end
    background(255, 255, 255, 255)
    perspective(50,WIDTH/HEIGHT,1,300)
    camera(Pos.x,Pos.y,Pos.z,Eye.x,Eye.y,Eye.z)
    shV=viewMatrix()
    shP=projectionMatrix()
    for i=1,#scene do
        scene[i].shader.loop=j
        scene[i].shader.View=viewMatrix()
        scene[i].shader.imodmat=modelMatrix():inverse()
        scene[i]:draw()
    end
    setContext()
end
    ortho()
    viewMatrix(matrix())
    --Screen
    background(0, 255, 255, 255)
    M.shader.Depth1=Depth1
    M.shader.Depth2=Depth2
    M.shader.Normals=Normals
    M.shader.lp=Ljus
    M.shader.iView=shV:inverse()
    M.shader.ViewProj=shV*shP
    M:draw()
end

function normals(m)
    local normal={}
    local v=m.vertices
    for i=1,#v,3 do
        local n=(v[i+1]-v[i]):cross(v[i+2]-v[i]):normalize()
        normal[i]=n normal[i+1]=n normal[i+2]=n
    end
    return normal
end

BufferVertex=[[
uniform highp mat4 modelViewProjection;
attribute vec4 position;
attribute vec3 normal;
varying vec4 Pos;
varying vec3 Norm;
uniform mat4 imodmat;
uniform mat4 View;
void main() {
    Pos=View*position;
    Norm=(imodmat*vec4(normal,1.)).xyz;
    gl_Position=modelViewProjection*position;
    
}
]]

BufferFrag=[[
precision highp float;
varying highp vec4 Pos;
varying vec3 Norm;
uniform float lr;
uniform int loop;
    
vec3 encodeDepth(float d) {
    vec3 enc=vec3(1.,255.,65025.)*d;
    enc=fract(enc);
    enc-=vec3(enc.y,enc.z,enc.z)*vec3(1./255.,1./255.,1./255.);
    return enc;
}

void main() {
    if (loop==1) {
        if (!gl_FrontFacing) discard;
        highp vec3 co=encodeDepth(-Pos.z/Pos.w/lr);
        gl_FragColor=vec4(co,1.);
    } else if (loop==2) {
        if (gl_FrontFacing) discard;
        highp vec3 co=encodeDepth(-Pos.z/Pos.w/lr);
        gl_FragColor=vec4(co,1.);
    } else {
        vec3 N=((Norm+1.)*0.5);
        gl_FragColor=vec4(N,1.);
    }
}
]]

LightVertex=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec2 vT;
void main() {
    vT=texCoord;
    gl_Position=modelViewProjection*position;
}
]]

LightFrag=[[
precision highp float;
uniform sampler2D Depth1;
uniform sampler2D Depth2;
uniform sampler2D Normals;
uniform float lr;
uniform vec3 lp;
uniform vec2 FA;
uniform mat4 iView;
uniform mat4 ViewProj;
varying highp vec2 vT;
const highp vec2 Values=vec2(1./255.,1./65025.);
//This is the number of steps where we check for shadows
#define Steps 25.

float decodeDepth(vec3 c) { return dot(c,vec3(1.,Values.x,Values.y)); }

vec3 ToPos(vec2 vt,float f) {
    vec2 uv=vt*2.-1.;
    highp float Z=f*lr;
    highp float X=uv.x*FA.x*FA.y*Z;
    highp float Y=uv.y*FA.x*Z;
    highp vec4 fPos=iView*vec4(X,Y,-Z,1.);
    return fPos.xyz/fPos.w;
}

void main() {
    highp vec3 pd=texture2D(Depth1,vT).xyz;
    float D=decodeDepth(pd);
    if (D>0.99) discard;
    //Var
    vec4 Col=vec4(0.3,0.,0.,1.);
    vec3 Normal=texture2D(Normals,vT).xyz; Normal=Normal*2.-1.;
    vec3 Pos=ToPos(vT,D);
    //Light
    vec3 ltp=lp-Pos;
    float ll=max(1.-length(ltp)/60.,0.);
    float Ljus=max(0.,dot(normalize(ltp),Normal)*ll);
if (Ljus!=0.) {
    Col+=vec4(Ljus);
    vec4 lpos=ViewProj*vec4(lp.xyz,1.); lpos.xy=lpos.xy/lpos.w;
    float dz=(lpos.z/lr-D)/Steps; vec2 dir=((lpos.xy*0.5+0.5)-vT)/Steps;
    float cz=D; vec2 cpos=vT;
    for (int i=0;i<int(Steps);i++) {
            cz=cz+dz; cpos=cpos+dir;
if (decodeDepth(texture2D(Depth1,cpos).xyz)<cz && decodeDepth(texture2D(Depth2,cpos).xyz)>cz)
        { Col=vec4(0.3,0.,0.,1.); break; }   
    }
}
    gl_FragColor=vec4(Col.xyz,1.);
}
]]
--# Create
Create=class()

function Create:Box(p,pp)
    v={
    vec3(p.x,p.y+pp.y,p.z),vec3(p.x,p.y+pp.y,p.z+pp.z),vec3(p.x+pp.x,p.y+pp.y,p.z+pp.z),
    vec3(p.x+pp.x,p.y+pp.y,p.z+pp.z),vec3(p.x+pp.x,p.y+pp.y,p.z),vec3(p.x,p.y+pp.y,p.z),
    
    vec3(p.x,p.y,p.z),vec3(p.x+pp.x,p.y,p.z+pp.z),vec3(p.x,p.y,p.z+pp.z),
    vec3(p.x+pp.x,p.y,p.z+pp.z),vec3(p.x,p.y,p.z),vec3(p.x+pp.x,p.y,p.z),
    
    vec3(p.x,p.y,p.z),vec3(p.x,p.y+pp.y,p.z),vec3(p.x+pp.x,p.y+pp.y,p.z),
    vec3(p.x+pp.x,p.y+pp.y,p.z),vec3(p.x+pp.x,p.y,p.z),vec3(p.x,p.y,p.z),
    
    vec3(p.x,p.y,p.z+pp.z),vec3(p.x+pp.x,p.y,p.z+pp.z),vec3(p.x+pp.x,p.y+pp.y,p.z+pp.z),
    vec3(p.x+pp.x,p.y+pp.y,p.z+pp.z),vec3(p.x,p.y+pp.y,p.z+pp.z),vec3(p.x,p.y,p.z+pp.z),
    
    vec3(p.x,p.y,p.z),vec3(p.x,p.y,p.z+pp.z),vec3(p.x,p.y+pp.y,p.z+pp.z),
    vec3(p.x,p.y+pp.y,p.z+pp.z),vec3(p.x,p.y+pp.y,p.z),vec3(p.x,p.y,p.z),
    
    vec3(p.x+pp.x,p.y,p.z),vec3(p.x+pp.x,p.y+pp.y,p.z),vec3(p.x+pp.x,p.y+pp.y,p.z+pp.z),
    vec3(p.x+pp.x,p.y+pp.y,p.z+pp.z),vec3(p.x+pp.x,p.y,p.z+pp.z),vec3(p.x+pp.x,p.y,p.z)}
    m=mesh()
    m.vertices=v
    m.normals=normals(m)
    return m
end

function Create:Cyl(pos,propx,propy,poly,open)
    local cyl=mesh()
    local fc={}
    local ac={}
    local cs=cs or 360
    local angle=cs/poly
for i=1,poly+1 do
    ti(fc,vec3(pos.x+propx*cos(rad(angle*i)),pos.y,pos.z+propx*sin(rad(angle*i))))
    ti(ac,vec3(pos.x+open*(propx*cos(rad(angle*(i)))),pos.y+propy,pos.z+open*(propx*sin(rad(angle*(i))))))
end
    local v={}
    for k=1,poly do
        ti(v,fc[k]) ti(v,ac[k]) ti(v,ac[k+1])
        ti(v,ac[k+1]) ti(v,fc[k+1]) ti(v,fc[k])
    end
    cyl.vertices=v
    cyl.normals=normals(cyl)
    return cyl
end

function Create:Sphere(pos,r,N)
    local tab={}
    local snorm={}
    for n=0,N-1 do
        for m=0,N-1 do
            x=pos.x+r*sin(pi*m/N)*cos(2*pi*n/N)
            y=pos.y+r*sin(pi*m/N)*sin(2*pi*n/N)
            z=pos.z+r*cos(pi*m/N)
            x1=pos.x+r*sin(pi*m/N)*cos(2*pi*(n+1)/N)
            y1=pos.y+r*sin(pi*m/N)*sin(2*pi*(n+1)/N)
            z1=pos.z+r*cos(pi*m/N)
            x2=pos.x+r*sin(pi*(m+1)/N)*cos(2*pi*(n+1)/N)
            y2=pos.y+r*sin(pi*(m+1)/N)*sin(2*pi*(n+1)/N)
            z2=pos.z+r*cos(pi*(m+1)/N)
            x3=pos.x+r*sin(pi*(m+1)/N)*cos(2*pi*n/N)
            y3=pos.y+r*sin(pi*(m+1)/N)*sin(2*pi*n/N)
            z3=pos.z+r*cos(pi*(m+1)/N)
            ti(tab,vec3(x1,y1,z1)) ti(tab,vec3(x,y,z)) ti(tab,vec3(x2,y2,z2))
            ti(tab,vec3(x2,y2,z2)) ti(tab,vec3(x,y,z)) ti(tab,vec3(x3,y3,z3))
            
            ti(snorm,(vec3(x1,y1,z1)-pos):normalize()) ti(snorm,(vec3(x,y,z)-pos):normalize())
            ti(snorm,(vec3(x2,y2,z2)-pos):normalize()) ti(snorm,(vec3(x2,y2,z2)-pos):normalize())
            ti(snorm,(vec3(x,y,z)-pos):normalize()) ti(snorm,(vec3(x3,y3,z3)-pos):normalize())
        end
    end
    local sph=mesh()
    sph.vertices=tab
    sph.normals=snorm
    return sph
end

@MMGames Nice job. Something to look at and learn.

Gorgeous, great work. :-bd

I noticed the cube’s shadow seems a little stepped, though, why is that?