GLSL Shadowmap


Tweet opengl shaders code-archives
(Posted 2 years ago) RonTek

Kronos' GLSL Shadow Mapping Example in BlitzMax

Latest Changes:

  • Updated to work with BlitzMax NG
  • Added PCF Shadows (Toggle)

Reference: Shadowmapping with GLSL

BlitzMax GLSL Shadows

PCF Shadows
BlitzMax GLSL PCF Shadows

Shadowmap.bmx

Import brl.Graphics

GLGraphics 800, 600, 0, 60, GRAPHICS_BACKBUFFER | GRAPHICS_DEPTHBUFFER
glewInit()

' Toggle PCF Soft Shadows
Const EnablePCF:Int = True

'' Expressed as Float so gluPerspective division returns a Float And Not 0 (640/480 != 640.0/480.0).
Const RENDER_WIDTH:Float = 800.0
Const RENDER_HEIGHT:Float = 600.0
Const SHADOW_MAP_RATIO:Float = 2.0

Global secondelapsed:Float
''Camera position
Global p_camera:Float[] = [32.0, 20.0, 0.0]

''Camera lookAt
Global l_camera:Float[] = [2.0, 0.0, -10.0]

''Light position
Global p_light:Float[] = [3.0, 20.0, 0.0]

''Light lookAt
Global l_light:Float[] = [0.0, 0.0, -5.0]


''Light movement circle radius
Global light_mvnt:Float = 30.0

'' Hold id of the framebuffer For light POV rendering
'GLuint fboId
Global fboID:Int

'' Z values will be rendered To this texture when using fboId framebuffer
'GLuint depthTextureId
Global depthTextureId:Int

Global shadowMapUniform:Int

Local VertShader:String = "shadowmap.vert.glsl"
Local FragShader:String = "shadowmap.frag.glsl"

If EnablePCF Then FragShader = "shadowmap_pcf.frag.glsl"

Global lightshader:TShader = TShader.Load(VertShader, FragShader)
shadowMapUniform = glGetUniformLocationARB(lightshader.progobj, "ShadowMap")

Global m_quadric:Byte Ptr
m_quadric = gluNewQuadric()
'gluQuadricNormals(m_quadric, GLU_SMOOTH) 'not working on my version of bmx-ng 

generateShadowFBO() ;

glEnable(GL_DEPTH_TEST)
glClearColor(0, 0, 0, 1.0)
glEnable(GL_CULL_FACE)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

Global ShowShadowMap:Int = -1

Local secondselapsed:Int

While Not KeyDown(KEY_ESCAPE) And Not AppTerminate()

    If KeyHit(KEY_M) Then ShowShadowMap:*- 1
    secondselapsed:+1.0

    If KeyDown(KEY_UP) Then p_camera[0]:+1
    If KeyDown(KEY_DOWN) Then p_camera[0]:-1
    If KeyDown(KEY_LEFT) Then p_camera[2]:+1
    If KeyDown(KEY_RIGHT) Then p_camera[2]:-1

    renderScene(secondselapsed)

    Flip(True)

Wend

Function generateShadowFBO()

    Local shadowMapWidth:Int = RENDER_WIDTH * SHADOW_MAP_RATIO
    Local shadowMapHeight:Int = RENDER_HEIGHT * SHADOW_MAP_RATIO

    ''GLfloat borderColor[4] = {0,0,0,0};

    'GLenum FBOstatus;
    Local FBOstatus:Int

    '' Try To use a texture depth component
    glGenTextures(1, Varptr depthTextureId)
    glBindTexture(GL_TEXTURE_2D, depthTextureId)

    '' GL_LINEAR does Not make sense For depth texture. However, Next tutorial shows usage of GL_LINEAR And PCF
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

    '' Remove artefact on the edges of the shadowmap
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)

    If EnablePCF Then
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
        glTexParameteri(GL_TEXTURE_2D , GL_DEPTH_TEXTURE_MODE , GL_INTENSITY) ;     
    End If  


    ''glTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor );


'   ' No need To force GL_DEPTH_COMPONENT24, drivers usually give you the Max precision If available 
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, Null) '****had to make this null to work***
    glBindTexture(GL_TEXTURE_2D, Null)

'   ' Create a framebuffer Object
    glGenFramebuffersEXT(1, Varptr fboId)
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId)

    '' Instruct openGL that we won't bind a color texture with the currently binded FBO
    glDrawBuffer(GL_NONE)
    glReadBuffer(GL_NONE)

    '' attach the texture To FBO depth attachment point
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthTextureId, Null)

    ' check FBO status
    FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)
    If(FBOstatus <> GL_FRAMEBUFFER_COMPLETE_EXT) Then DebugLog("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n")

    ' switch back To window-system-provided framebuffer
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Null)'*****should this be part of if statement above?
End Function

Function setupMatrices(position_x:Float, position_y:Float, position_z:Float, lookAt_x:Float, lookAt_y:Float, lookAt_z:Float)

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45,RENDER_WIDTH/RENDER_HEIGHT,10,40000);
    glMatrixMode(GL_MODELVIEW) ;
    glLoadIdentity();
    gluLookAt(position_x,position_y,position_z,lookAt_x,lookAt_y,lookAt_z,0,1,0);
End Function


' This update only change the position of the light.
'Int elapsedTimeCounter = 0;
Function update(SecondsElapsed:Float)


    p_light[0] = light_mvnt * Cos(SecondsElapsed) ;
    p_light[2] = light_mvnt * Sin(SecondsElapsed) ;

    'p_light[0] = light_mvnt * Cos(3652/1000.0);
    'p_light[2] = light_mvnt * Sin(3652/1000.0);
End Function


Function setTextureMatrix()

    Global modelView:Double[16]
    Global Projection:Double[16]

    ' This is matrix transform every coordinate x,y,z
    ' x = x* 0.5 + 0.5 
    ' y = y* 0.5 + 0.5 
    ' z = z* 0.5 + 0.5 
    ' Moving from unit cube [-1,1] To [0,1]  
    Global bias:Double[] = [Double 0.5, Double 0.0, Double 0.0, Double 0.0, Double 0.0, Double 0.5, Double 0.0, Double 0.0, Double 0.0, Double 0.0, Double 0.5, Double 0.0, Double 0.5, Double 0.5, Double 0.5, Double 1.0]

    ' Grab modelview And transformation matrices
    glGetDoublev(GL_MODELVIEW_MATRIX, modelView) ;
    glGetDoublev(GL_PROJECTION_MATRIX, Projection) ;


    glMatrixMode(GL_TEXTURE);
    glActiveTextureARB(GL_TEXTURE7);

    glLoadIdentity();   
    glLoadMatrixd(bias) ;

    ' concatenating all matrice into one.
    glMultMatrixd (Projection) ;
    glMultMatrixd (modelView) ;

    ' Go back To normal matrix mode
    glMatrixMode(GL_MODELVIEW);
End Function

' During translation, we also have To maintain the GL_TEXTURE8, used in the shadow shader
' To determine If a vertex is in the shadow.
Function startTranslate(x:Float, y:Float, z:Float)

    glPushMatrix();
    glTranslatef(x,y,z);

    glMatrixMode(GL_TEXTURE);
    glActiveTextureARB(GL_TEXTURE7);
    glPushMatrix();
    glTranslatef(x,y,z);
End Function

Function endTranslate()

    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
End Function

Function drawObjects(shadowpass:Int)

    ' Ground
    glColor4f(0.3, 0.3, 0.3, 1) ;
    glBegin(GL_QUADS);
    glVertex3f(-3500,2,-3500);
    glVertex3f(-3500,2, 1500);
    glVertex3f( 1500,2, 1500);
    glVertex3f( 1500,2,-3500);
    glEnd();

    glColor4f(0.9, 0.9, 0.9, 1) ;

    If shadowpass = True Then
    ' Instead of calling glTranslatef, we need a custom Function that also maintain the light matrix
    startTranslate(0,4,-16);
    gluSphere(m_quadric, 4, 20, 20)

    endTranslate();

    startTranslate(0,4,-5);
    gluSphere(m_quadric, 4, 20, 20)
    endTranslate();
    EndIf

End Function



Function renderScene(SecondsElapsed:Float)

    update(SecondsElapsed) ;

    'First Step: Render from the light POV To a FBO, story depth values only
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fboId); 'Rendering offscreen

    'Using the fixed pipeline To render To the depthbuffer
    lightshader.Disable()

    ' In the Case we render the shadowmap To a higher resolution, the viewport must be modified accordingly.
    glViewport(0,0,RENDER_WIDTH * SHADOW_MAP_RATIO,RENDER_HEIGHT* SHADOW_MAP_RATIO);


    ' Clear previous frame values
    glClear( GL_DEPTH_BUFFER_BIT);

    'Disable color rendering, we only want To write To the Z-Buffer
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 

    setupMatrices(p_light[0],p_light[1],p_light[2],l_light[0],l_light[1],l_light[2]);

    ' Culling switching, rendering only backface, this is done To avoid Self-shadowing
    glCullFace(GL_FRONT);
    drawObjects(True) ;

    'Save modelview/projection matrice into texture7, also add a biais
    setTextureMatrix();


    ' Now rendering from the camera POV, using the FBO To generate shadows
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Null) ;

    glViewport(0,0,RENDER_WIDTH,RENDER_HEIGHT);

    'Enabling color write (previously disabled For light POV z-buffer rendering)
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 

    ' Clear previous frame values
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    'Using the shadow shader
    lightshader.use()
    glUniform1iARB(shadowMapUniform,7);
    glActiveTextureARB(GL_TEXTURE7);
    glBindTexture(GL_TEXTURE_2D,depthTextureId);

    setupMatrices(p_camera[0], p_camera[1], p_camera[2], l_camera[0], l_camera[1], l_camera[2]) ;

    glCullFace(GL_BACK);
    drawObjects(True) ;

    ' DEBUG only. this piece of code draw the depth buffer onscreen
    If ShowShadowMap = -1 Then
     lightshader.Disable()
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     glOrtho(-RENDER_WIDTH/2,RENDER_WIDTH/2,-RENDER_HEIGHT/2,RENDER_HEIGHT/2,1,20);
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     glColor4f(1,1,1,1);
     glActiveTextureARB(GL_TEXTURE0);
     glBindTexture(GL_TEXTURE_2D,depthTextureId);
     glEnable(GL_TEXTURE_2D);
     glTranslated(0,0,-1);
     glBegin(GL_QUADS);
     glTexCoord2d(0,0);glVertex3f(0,0,0);
     glTexCoord2d(1,0);glVertex3f(RENDER_WIDTH/2,0,0);
     glTexCoord2d(1,1);glVertex3f(RENDER_WIDTH/2,RENDER_HEIGHT/2,0);
     glTexCoord2d(0,1);glVertex3f(0,RENDER_HEIGHT/2,0);
     glEnd();
     glDisable(GL_TEXTURE_2D);
     EndIf


End Function


Global CurrentProgramObject:Int = 0
Global TShader_List:TList = Null
Type TShader
    Field Name:String
    Field VSObj:Int
    Field FSObj:Int
    Field ProgObj:Int

    Field Link:TLink

    Function Load:TShader(vertexshaderfile:String, fragmentshaderfile:String, instancename:String = Null)
        If Not TShader_List Then TShader_List = CreateList()
        Local Tmp:TShader = New TShader
        Tmp.Link = ListAddFirst(TShader_List, Tmp)

        If instancename = Null Then
            Tmp.Name = vertexshaderfile
        Else
            Tmp.Name = instancename
        End If

        Local file:String = vertexshaderfile
        Local file2:String = fragmentshaderfile

        Tmp.ProgObj = glCreateProgramObjectARB()
        Tmp.VSObj = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB)
        Tmp.FSObj = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB)

        Local fileStream:TStream = OpenFile(file)
        Local fileStream2:TStream = OpenFile(file2)

        Local shaderSrcVertex:Byte[FileSize(file) + 1]
        Local i:Int = 0
        While Not Eof(fileStream)
           shaderSrcVertex[i] = ReadByte(fileStream)
           i :+ 1
           If i > FileSize(file) Then i = FileSize(file)
        Wend
        CloseFile(fileStream)

        Local pointer:Byte Ptr = Varptr shadersrcVertex[0]
        Local laenge:Int = FileSize(file)

        glShaderSourceARB(Tmp.VSObj, 1, Varptr pointer, Varptr laenge)
        glCompileShaderARB(Tmp.VSObj)

        debugShader(Tmp.VSObj)

        Local shaderSrcFragment:Byte[FileSize(file2) + 1]
        i = 0
        While Not Eof(fileStream2)
           shaderSrcFragment[i] = ReadByte(fileStream2)
           i :+ 1
           If i > FileSize(file2) Then i = FileSize(file2)
        Wend
        CloseFile(fileStream2)

        pointer = Varptr shadersrcFragment[0]
        laenge = FileSize(file2)

        glShaderSourceARB(Tmp.FSObj, 1, Varptr pointer, Varptr laenge)
        glCompileShaderARB(Tmp.FSObj)

        debugShader(Tmp.FSObj)

        glAttachObjectARB(Tmp.ProgObj, Tmp.VSObj)
        glAttachObjectARB(Tmp.ProgObj, Tmp.FSObj)

        glDeleteObjectARB(Tmp.VSObj)
        glDeleteObjectARB(Tmp.FSObj)

        glLinkProgramARB(Tmp.ProgObj)

        Return Tmp
    End Function

    Function debugShader(shaderObject:Int)
        Local laengedeslogs:Float
        glGetObjectParameterfvARB(shaderObject,GL_OBJECT_INFO_LOG_LENGTH_ARB, Varptr laengedeslogs)

        Local bytearray:Byte[] = New Byte[laengedeslogs]
        Local laenge23:Int = 0
        glGetInfoLogARB(shaderObject, laengedeslogs, Varptr(laenge23), Varptr(bytearray[0]));
        Local msg:String = ""
        For Local j:Int = 0 To laengedeslogs - 1
           msg = msg + Chr(bytearray[j])
        Next
        DebugLog  "ShaderLog: "+msg
    End Function

    Method Use()
        glUseProgramObjectARB(Self.ProgObj)
    End Method

    Function Disable()
        glUseProgramObjectARB(Null)
    End Function

    Function DestroyAll()
        If TShader_List <> Null Then
            For Local Tmp:TShader = EachIn TShader_List
                Tmp.Destroy()
                Tmp = Null
            Next
            TShader_List.Clear()
            TShader_List = Null
        End If
    End Function

    Method Destroy()
        glDetachObjectARB(Self.ProgObj, Self.VSObj)
        glDeleteObjectARB(Self.VSObj)
        Self.VSObj = Null
        glDetachObjectARB(Self.ProgObj, Self.FSObj)
        glDeleteObjectARB(Self.FSObj)
        Self.FSObj = Null
        glDeleteObjectARB(Self.ProgObj)
        Self.ProgObj = Null
        Self.Name = Null
        RemoveLink(Self.Link)
    End Method
End Type

shadowmap.vert.glsl

// Used for shadow lookup
varying vec4 ShadowCoord;

void main()
{
    ShadowCoord= gl_TextureMatrix[7] * gl_Vertex; 
    gl_Position = ftransform();
    gl_FrontColor = gl_Color;
}

shadowmap.frag.glsl

uniform sampler2D ShadowMap;

varying vec4 ShadowCoord;

void main()
{   
    vec4 shadowCoordinateWdivide = ShadowCoord / ShadowCoord.w ;

    // Used to lower moiré pattern and self-shadowing   
    shadowCoordinateWdivide.z += 0.0005;

    float distanceFromLight = texture2D(ShadowMap,shadowCoordinateWdivide.st).z;

    float shadow = 1.0;
    if (ShadowCoord.w > 0.0)
        shadow = distanceFromLight < shadowCoordinateWdivide.z ? 0.5 : 1.0 ;

    gl_FragColor =   shadow * gl_Color; 
}

shadowmap_pcf.frag.glsl


uniform sampler2DShadow ShadowMap;

varying vec4 ShadowCoord;

// This define the value to move one pixel left or right
float xPixelOffset = 0.001;

// This define the value to move one pixel up or down
float yPixelOffset = 0.001;

float lookup( vec2 offSet)
{
    // Values are multiplied by ShadowCoord.w because shadow2DProj does a W division for us.
    return shadow2DProj(ShadowMap, ShadowCoord + 
                                   vec4(offSet.x * xPixelOffset * ShadowCoord.w, 
                                        offSet.y * yPixelOffset * ShadowCoord.w, 
                                        0.05, 
                                        0.0) ).w;
}

void main()
{       

    float shadow ;

    // Avoid counter shadow
    if (ShadowCoord.w > 1.0)
    {
        float x,y;
        for (y = -1.5 ; y <=1.5 ; y+=1.0)
            for (x = -1.5 ; x <=1.5 ; x+=1.0)
                shadow += lookup(vec2(x,y));

        shadow /= 16.0 ;

    }
    gl_FragColor = (shadow+0.2) * gl_Color;

}

Reply To Topic (minimum 10 characters)

Please log in to reply