I was trying to understand how Shadow Mapping works on its own and describes this Python program to test Shadow Mapping, and this is as far as possible.
I searched for shadow mapping problems, but I haven't found what has already happened for something like this, so how do I color the normal rotating cube in front of the light and how do I correct the position of the shadow so that it appears and place it exactly on the surface ? I was just modifying the shader.
import pyglet, math, pyrr, ctypes
import numpy as np
from OpenGL.GL import *
from OpenGL.GL.shaders import *
app = pyglet.window.Window()
v = """
in layout(location=0) vec3 posicao;
uniform mat4 view;
uniform vec3 translate;
uniform float rot;
uniform float rot2;
uniform vec3 t2;
out vec3 outpos;
void main(){
vec3 p = posicao;
p = vec3(sin(rot)*p.x+cos(rot)*p.z,p.y,-sin(rot)*p.z+cos(rot)*p.x);
p = translate+p+t2;
p = vec3(p.x,sin(rot2)*p.z+cos(rot2)*p.y,-sin(rot2)*p.y+cos(rot2)*p.z);
outpos = p;
gl_Position = view*vec4(p,1);
}
"""
f = """
uniform vec3 cor;
uniform int modo;
uniform sampler2D shadow;
in vec3 outpos;
void main(){
if(modo==0){
float pi = 3.141592653589793;
vec3 o = outpos+vec3(0,-5,0);
o = vec3(o.x,sin(-30*pi/180)*o.z+cos(-30*pi/180)*o.y,-sin(-30*pi/180)*o.y+cos(-30*pi/180)*o.z);
o.z+=0.05;
float d = texture(shadow,vec2(.5)+o.xy/20).z;
float i = 1;
if(d<-o.z/20){
i = 0.5;
}
gl_FragColor = vec4(vec3(cor),1)*i;
//gl_FragColor = texture(shadow,outpos.xy);
}else{
gl_FragColor = vec4(vec3(-outpos.z/20),1);
}
}
"""
shader = compileProgram(compileShader(v,GL_VERTEX_SHADER),compileShader(f,GL_FRAGMENT_SHADER))
glUseProgram(shader)
tudo = [-200,-4,200,
200,-4,200,
200,-4,-200,
-200,-4,-200,
-2,-2,2,
-2,-2,-2,
2,-2,-2,
2,-2,2,
2,2,2,
2,2,-2,
-2,2,-2,
-2,2,2,
-2,2,2,
-2,2,-2,
-2,-2,-2,
-2,-2,2,
2,-2,2,
2,-2,-2,
2,2,-2,
2,2,2,
-2,2,-2,
2,2,-2,
2,-2,-2,
-2,-2,-2,
-2,-2,2,
2,-2,2,
2,2,2,
-2,2,2]
tudo = np.array(tudo, dtype=np.float32)
CUBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, CUBO)
glBufferData(GL_ARRAY_BUFFER, len(tudo)*4, tudo, GL_STREAM_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, ctypes.c_void_p(0))
glEnableVertexAttribArray(0)
view = pyrr.matrix44.create_perspective_projection_matrix(60, app.width/app.height, .1, 10000)
p = glGetUniformLocation(shader, "view")
glUniformMatrix4fv(p, 1, GL_FALSE, view)
glEnable(GL_DEPTH_TEST)
# Tentativa de Shadow Mapping
glEnable(GL_TEXTURE_2D)
tf = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, tf)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, app.width, app.height, 0, GL_RGB, GL_UNSIGNED_BYTE, None)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
f = glGenFramebuffers(1)
r = glGenRenderbuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, f)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tf, 0)
glBindRenderbuffer(GL_RENDERBUFFER, r)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, app.width, app.height)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, r)
glBindRenderbuffer(GL_RENDERBUFFER, 0)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
pos = [0,-10,0]
comando = {"a":0,"w":0,"s":0,"d":0,"q":0,"e":0}
girar = -1
@app.event
def on_draw():
global CUBO, shader, pos, comando, tudo, girar, f
glBindFramebuffer(GL_FRAMEBUFFER, f)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# Z-Buffer
p = glGetUniformLocation(shader, "modo")
glUniform1i(p, 1)
p = glGetUniformLocation(shader, "rot2")
glUniform1f(p, -30*math.pi/180)
p = glGetUniformLocation(shader, "t2")
glUniform3f(p, 0, -5, 0)
# Movimento
v = .04
if comando["a"] == 1:
pos[0]-=v
if comando["d"] == 1:
pos[0]+=v
if comando["w"] == 1:
pos[1]-=v
if comando["s"] == 1:
pos[1]+=v
if comando["q"] == 1:
pos[2]-=v
if comando["e"] == 1:
pos[2]+=v
# fundo vermelho
p = glGetUniformLocation(shader, "translate")
glUniform3f(p, 0, 0, 0)
p = glGetUniformLocation(shader, "rot")
glUniform1f(p, 0)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 1, 0, 0)
glDrawArrays(GL_QUADS, 0, 4)
# objeto no meio
p = glGetUniformLocation(shader, "translate")
glUniform3f(p, -1, -5, -10)
p = glGetUniformLocation(shader, "rot")
glUniform1f(p, 0)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 0, 0, 1)
glDrawArrays(GL_QUADS, 8, 4)
# objeto azul
p = glGetUniformLocation(shader, "translate")
glUniform3f(p, pos[0], pos[2], pos[1])
p = glGetUniformLocation(shader, "rot")
girar+=.4
glUniform1f(p, girar*math.pi/180)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 0, 0, 1)
glDrawArrays(GL_QUADS, 4, 8)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 0, 1, 0)
glDrawArrays(GL_QUADS, 12, 8)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 1, 1, 0)
glDrawArrays(GL_QUADS, 20, 8)
# gravar o Framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0)
# reset
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# Z-Buffer tirando
p = glGetUniformLocation(shader, "modo")
glUniform1i(p, 0)
p = glGetUniformLocation(shader, "rot2")
glUniform1f(p, 0*math.pi/180)
p = glGetUniformLocation(shader, "t2")
glUniform3f(p, 0, 0, 0)
# fundo vermelho
p = glGetUniformLocation(shader, "translate")
glUniform3f(p, 0, 0, 0)
p = glGetUniformLocation(shader, "rot")
glUniform1f(p, 0)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 1, 0, 0)
glDrawArrays(GL_QUADS, 0, 4)
# objeto no meio
p = glGetUniformLocation(shader, "translate")
glUniform3f(p, -1, -5, -10)
p = glGetUniformLocation(shader, "rot")
glUniform1f(p, 0)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 0, 0, 1)
glDrawArrays(GL_QUADS, 8, 4)
# objeto azul
p = glGetUniformLocation(shader, "translate")
glUniform3f(p, pos[0], pos[2], pos[1])
p = glGetUniformLocation(shader, "rot")
glUniform1f(p, girar*math.pi/180)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 0, 0, 1)
glDrawArrays(GL_QUADS, 4, 8)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 0, 1, 0)
glDrawArrays(GL_QUADS, 12, 8)
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 1, 1, 0)
glDrawArrays(GL_QUADS, 20, 8)
@app.event
def on_key_press(k,m):
global comando, grandeG
if k == pyglet.window.key.A:
comando["a"] = 1
if k == pyglet.window.key.W:
comando["w"] = 1
if k == pyglet.window.key.S:
comando["s"] = 1
if k == pyglet.window.key.D:
comando["d"] = 1
if k == pyglet.window.key.Q:
comando["q"] = 1
if k == pyglet.window.key.E:
comando["e"] = 1
@app.event
def on_key_release(k,m):
global comando
if k == pyglet.window.key.A:
comando["a"] = 0
if k == pyglet.window.key.W:
comando["w"] = 0
if k == pyglet.window.key.S:
comando["s"] = 0
if k == pyglet.window.key.D:
comando["d"] = 0
if k == pyglet.window.key.Q:
comando["q"] = 0
if k == pyglet.window.key.E:
comando["e"] = 0
def SRO(dt):
on_draw()
pyglet.clock.schedule(SRO)
pyglet.app.run()
The issue is caused by the limited accuracy of the shadow depth buffer. Since only 1 channel per fragment needs to be stored in the shadow buffer, I recommend to use the floating point internal texture format GL_R16F
(or GL_R32F
):
glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, app.width, app.height, 0, GL_RED, GL_UNSIGNED_BYTE, None)
Note the (shadow) depth is now stored in the red color channel, thus you have to read the value from the red (x
) channel rather than the blue (z
) channel in the fragment shader:
floaz d = texture(shadow,vec2(.5)+o.xy/20).z;
float d = texture(shadow,vec2(.5)+o.xy/20).x;