Last active
August 16, 2024 12:11
-
-
Save 8Observer8/0ec34fac62770e30fc9fd38d8b342b00 to your computer and use it in GitHub Desktop.
Shadow mapping using Pygame, PyOpenGL, and PyGLM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from OpenGL.GL import * | |
def initFBO(offscreenWidth, offscreenHeight): | |
# Create a texture object and set its size and parameters | |
shadowMapTexture = glGenTextures(1) | |
glBindTexture(GL_TEXTURE_2D, shadowMapTexture) | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, offscreenWidth, offscreenHeight, | |
0, GL_RGBA, GL_UNSIGNED_BYTE, None) | |
# Create a renderbuffer object and Set its size and parameters | |
depthBuffer = glGenRenderbuffers(1) | |
glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer) | |
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, | |
offscreenWidth, offscreenHeight) | |
# Attach the texture and the renderbuffer object to the FBO | |
# Create a frame buffer object (FBO) | |
framebuffer = glGenFramebuffers(1) | |
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer) | |
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
GL_TEXTURE_2D, shadowMapTexture, 0) | |
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, | |
GL_RENDERBUFFER, depthBuffer) | |
e = glCheckFramebufferStatus(GL_FRAMEBUFFER) | |
if e != GL_FRAMEBUFFER_COMPLETE: | |
print("Frame buffer object is incomplete") | |
glBindFramebuffer(GL_FRAMEBUFFER, 0) | |
glBindTexture(GL_TEXTURE_2D, 0) | |
glBindRenderbuffer(GL_RENDERBUFFER, 0) | |
return (shadowMapTexture, framebuffer) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# pip install Pygame PyOpenGL PyGLM numpy | |
# python main.py | |
import ctypes | |
import math | |
import glm | |
import numpy as np | |
import pygame | |
from OpenGL.GL import * | |
from OpenGL.GL.shaders import * | |
from pygame.locals import * | |
from fbo import initFBO | |
vertexShaderSource = """ | |
attribute vec4 aPosition; | |
attribute vec4 aNormal; | |
uniform mat4 uNormalMatrix; | |
uniform mat4 uMvpMatrix; | |
uniform mat4 uMvpMatrixFromLight; | |
const vec3 lightPosition = vec3(-10.0, 50.0, 30.0); | |
const vec3 lightDirection = normalize(lightPosition); | |
const float ambient = 0.3; | |
varying float vDot; | |
varying vec4 vPositionFromLight; | |
void main() | |
{ | |
gl_Position = uMvpMatrix * aPosition; | |
vPositionFromLight = uMvpMatrixFromLight * aPosition; | |
vec4 normal = uNormalMatrix * aNormal; | |
vDot = max(dot(normalize(normal.xyz), lightDirection), ambient); | |
} | |
""" | |
fragmentShaderSource = """ | |
uniform vec3 uColor; | |
uniform sampler2D uShadowMap; | |
uniform bool uReceiveShadow; | |
const vec3 lightColor = vec3(1.0, 1.0, 1.0); | |
varying float vDot; | |
varying vec4 vPositionFromLight; | |
void main() | |
{ | |
vec3 diffuse = lightColor * uColor * vDot; | |
if (uReceiveShadow) | |
{ | |
vec3 shadowCoord = (vPositionFromLight.xyz/vPositionFromLight.w)/2.0 + 0.5; | |
vec4 rgbaDepth = texture2D(uShadowMap, shadowCoord.xy); | |
float depth = rgbaDepth.r; // Retrieve the z-value from R | |
float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0; | |
gl_FragColor = vec4(diffuse * visibility, 1.0); | |
} | |
else | |
{ | |
gl_FragColor = vec4(diffuse, 1.0); | |
} | |
} | |
""" | |
shadowMapVertexShaderSource = """ | |
attribute vec4 aPosition; | |
uniform mat4 uMvpMatrix; | |
void main() | |
{ | |
gl_Position = uMvpMatrix * aPosition; | |
} | |
""" | |
shadowMapFragmentShaderSource = """ | |
void main() | |
{ | |
// Write the z-value in R | |
gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0); | |
} | |
""" | |
def main(): | |
pygame.init() | |
winWidth, winHeight = 400, 300 | |
display = (winWidth, winHeight) | |
pygame.display.set_mode(display, DOUBLEBUF | OPENGL) | |
glEnable(GL_DEPTH_TEST) | |
glClearColor(0.2, 0.2, 0.2, 1) | |
program = compileProgram( | |
compileShader(vertexShaderSource, GL_VERTEX_SHADER), | |
compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER)) | |
glUseProgram(program) | |
# Create a cube | |
# v6----- v5 | |
# /| /| | |
# v1------v0| | |
# | | | | | |
# | |v7---|-|v4 | |
# |/ |/ | |
# v2------v3 | |
vertPositions = np.array([ | |
-0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, # v2-v3-v1 front | |
-0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, # v1-v3-v0 front | |
0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, # v3-v4-v0 right | |
0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, # v0-v4-v5 right | |
-0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, # v1-v0-v6 up | |
-0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, # v6-v0-v5 up | |
-0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, # v7-v2-v6 left | |
-0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, # v6-v2-v1 left | |
-0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, # v7-v4-v2 down | |
-0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, # v2-v4-v3 down | |
0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, # v4-v7-v5 back | |
0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5 # v5-v7-v6 back | |
], dtype=np.float32) | |
vertPosBuffer = glGenBuffers(1) | |
glBindBuffer(GL_ARRAY_BUFFER, vertPosBuffer) | |
glBufferData(GL_ARRAY_BUFFER, len(vertPositions) * 4, | |
vertPositions, GL_STATIC_DRAW) | |
normals = np.array([ | |
0, 0, 1, 0, 0, 1, 0, 0, 1, # v2-v3-v1 front | |
0, 0, 1, 0, 0, 1, 0, 0, 1, # v1-v3-v0 front | |
1, 0, 0, 1, 0, 0, 1, 0, 0, # v3-v4-v0 right | |
1, 0, 0, 1, 0, 0, 1, 0, 0, # v0-v4-v5 right | |
0, 1, 0, 0, 1, 0, 0, 1, 0, # v1-v0-v6 up | |
0, 1, 0, 0, 1, 0, 0, 1, 0, # v6-v0-v5 up | |
-1, 0, 0, -1, 0, 0, -1, 0, 0, # v7-v2-v6 left | |
-1, 0, 0, -1, 0, 0, -1, 0, 0, # v6-v2-v1 left | |
0, -1, 0, 0, -1, 0, 0, -1, 0, # v7-v4-v2 down | |
0, -1, 0, 0, -1, 0, 0, -1, 0, # v2-v4-v3 down | |
0, 0, -1, 0, 0, -1, 0, 0, -1, # v4-v7-v5 back | |
0, 0, -1, 0, 0, -1, 0, 0, -1 # v5-v7-v6 back | |
], dtype=np.float32) | |
normalBuffer = glGenBuffers(1) | |
glBindBuffer(GL_ARRAY_BUFFER, normalBuffer) | |
glBufferData(GL_ARRAY_BUFFER, len(normals) * 4, | |
normals, GL_STATIC_DRAW) | |
aPositionLocation = glGetAttribLocation(program, "aPosition") | |
aNormalLocation = glGetAttribLocation(program, "aNormal") | |
uColorLocation = glGetUniformLocation(program, "uColor") | |
uReceiveShadowLocation = glGetUniformLocation(program, "uReceiveShadow") | |
uNormalMatrixLocation = glGetUniformLocation(program, "uNormalMatrix") | |
uMvpMatrixLocation = glGetUniformLocation(program, "uMvpMatrix") | |
uShadowMapLocation = glGetUniformLocation(program, "uShadowMap") | |
glUniform1i(uShadowMapLocation, 0) | |
uMvpMatrixFromLightLocation = glGetUniformLocation(program, "uMvpMatrixFromLight") | |
shadowMapProgram = compileProgram( | |
compileShader(shadowMapVertexShaderSource, GL_VERTEX_SHADER), | |
compileShader(shadowMapFragmentShaderSource, GL_FRAGMENT_SHADER)) | |
glUseProgram(shadowMapProgram) | |
aPositionLocationForShadow = glGetAttribLocation(shadowMapProgram, "aPosition") | |
uMvpMatrixLocationForShadow = glGetUniformLocation(shadowMapProgram, "uMvpMatrix") | |
projMatrix = glm.perspective(math.radians(45), winWidth/winHeight, 0.1, 1000) | |
viewMatrix = glm.lookAt( | |
glm.vec3(0, 20, 60), # position | |
glm.vec3(0, 0, 0), # target | |
glm.vec3(0, 1, 0)) # up | |
projViewMatrix = projMatrix * viewMatrix | |
offscreenWidth, offscreenHeight = 2048, 2048 | |
shadowMapTexture, framebuffer = initFBO(offscreenWidth, offscreenHeight) | |
projMatrixFromLight = glm.ortho(-200, 200, -200, 200, -50, 200) | |
viewMatrixFromLight = glm.lookAt( | |
glm.vec3(-10, 50, 30), # position | |
glm.vec3(0, 0, 0), # target | |
glm.vec3(0, 1, 0)) # up | |
projViewMatrixFromLight = projMatrixFromLight * viewMatrixFromLight | |
position1 = glm.vec3(7, 5, 0) | |
angle1 = 0 | |
scale1 = glm.vec3(10, 10, 10) | |
color1 = glm.vec3(0.5, 0.5, 1) | |
position2 = glm.vec3(-7, 5, 0) | |
angle2 = 0 | |
scale2 = glm.vec3(8, 10, 10) | |
color2 = glm.vec3(1, 0.5, 0.5) | |
groundPosition = glm.vec3(0, -10, 0) | |
groundScale = glm.vec3(40, 3, 40) | |
groundColor = glm.vec3(0.5, 1, 0.5) | |
while True: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
pygame.quit() | |
quit() | |
# Change the drawing destination to FBO | |
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer) | |
glViewport(0, 0, offscreenWidth, offscreenHeight) | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) | |
glUseProgram(shadowMapProgram) | |
# Cuboids | |
glBindBuffer(GL_ARRAY_BUFFER, vertPosBuffer) | |
glVertexAttribPointer(aPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0)) | |
glEnableVertexAttribArray(aPositionLocation) | |
# Cube | |
# Model matrix | |
modelMatrix = glm.translate(glm.mat4(1), position1) | |
modelMatrix = glm.rotate(modelMatrix, math.radians(angle1), glm.vec3(1, 0, 0)) | |
modelMatrix = glm.scale(modelMatrix, scale1) | |
# MVP-matrix | |
mvpMatrixFromLightForCube = projViewMatrixFromLight * modelMatrix | |
glUniformMatrix4fv(uMvpMatrixLocationForShadow, 1, GL_FALSE, | |
glm.value_ptr(mvpMatrixFromLightForCube)) | |
glDrawArrays(GL_TRIANGLES, 0, 36) | |
# Cuboid | |
# Model matrix | |
modelMatrix = glm.translate(glm.mat4(1), position2) | |
modelMatrix = glm.rotate(modelMatrix, math.radians(angle2), glm.vec3(0, 0, 1)) | |
modelMatrix = glm.scale(modelMatrix, scale2) | |
# MVP-matrix | |
mvpMatrixFromLightForCuboid = projViewMatrixFromLight * modelMatrix | |
glUniformMatrix4fv(uMvpMatrixLocationForShadow, 1, GL_FALSE, | |
glm.value_ptr(mvpMatrixFromLightForCuboid)) | |
glDrawArrays(GL_TRIANGLES, 0, 36) | |
# Ground | |
# Model matrix | |
modelMatrix = glm.translate(glm.mat4(1), groundPosition) | |
modelMatrix = glm.scale(modelMatrix, groundScale) | |
# MVP-matrix | |
mvpMatrixFromLightForGround = projViewMatrixFromLight * modelMatrix | |
glUniformMatrix4fv(uMvpMatrixLocationForShadow, 1, GL_FALSE, | |
glm.value_ptr(mvpMatrixFromLightForGround)) | |
glDrawArrays(GL_TRIANGLES, 0, 36) | |
glBindFramebuffer(GL_FRAMEBUFFER, 0) | |
glUseProgram(program) | |
glViewport(0, 0, winWidth, winHeight) | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) | |
glBindBuffer(GL_ARRAY_BUFFER, vertPosBuffer) | |
glVertexAttribPointer(aPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0)) | |
glEnableVertexAttribArray(aPositionLocation) | |
glBindBuffer(GL_ARRAY_BUFFER, normalBuffer) | |
glVertexAttribPointer(aNormalLocation, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0)) | |
glEnableVertexAttribArray(aNormalLocation) | |
# Cube | |
# Model matrix | |
modelMatrix = glm.translate(glm.mat4(1), position1) | |
modelMatrix = glm.rotate(modelMatrix, math.radians(angle1), glm.vec3(1, 0, 0)) | |
modelMatrix = glm.scale(modelMatrix, scale1) | |
# Normal matrix | |
normalMatrix = glm.inverse(modelMatrix) | |
normalMatrix = glm.transpose(normalMatrix) | |
glUniformMatrix4fv(uNormalMatrixLocation, 1, GL_FALSE, glm.value_ptr(normalMatrix)) | |
# MVP-matrix | |
mvpMatrix = projViewMatrix * modelMatrix | |
glUniformMatrix4fv(uMvpMatrixLocation, 1, GL_FALSE, glm.value_ptr(mvpMatrix)) | |
# Color | |
glUniform3fv(uColorLocation, 1, glm.value_ptr(color1)) | |
glUniform1i(uReceiveShadowLocation, 0) | |
glDrawArrays(GL_TRIANGLES, 0, 36) | |
angle1 += 1 | |
# Cuboid | |
# Model matrix | |
modelMatrix = glm.translate(glm.mat4(1), position2) | |
modelMatrix = glm.rotate(modelMatrix, math.radians(angle2), glm.vec3(0, 0, 1)) | |
modelMatrix = glm.scale(modelMatrix, scale2) | |
# Normal matrix | |
normalMatrix = glm.inverse(modelMatrix) | |
normalMatrix = glm.transpose(normalMatrix) | |
glUniformMatrix4fv(uNormalMatrixLocation, 1, GL_FALSE, glm.value_ptr(normalMatrix)) | |
# MVP-matrix | |
mvpMatrix = projViewMatrix * modelMatrix | |
glUniformMatrix4fv(uMvpMatrixLocation, 1, GL_FALSE, glm.value_ptr(mvpMatrix)) | |
# Color | |
glUniform3fv(uColorLocation, 1, glm.value_ptr(color2)) | |
glUniform1i(uReceiveShadowLocation, 0) | |
glDrawArrays(GL_TRIANGLES, 0, 36) | |
angle2 -= 3 | |
# Ground | |
glBindTexture(GL_TEXTURE_2D, shadowMapTexture) | |
# Model matrix | |
modelMatrix = glm.translate(glm.mat4(1), groundPosition) | |
modelMatrix = glm.scale(modelMatrix, groundScale) | |
# Normal matrix | |
normalMatrix = glm.inverse(modelMatrix) | |
normalMatrix = glm.transpose(normalMatrix) | |
glUniformMatrix4fv(uNormalMatrixLocation, 1, GL_FALSE, glm.value_ptr(normalMatrix)) | |
# MVP-matrix | |
mvpMatrix = projViewMatrix * modelMatrix | |
glUniformMatrix4fv(uMvpMatrixLocation, 1, GL_FALSE, glm.value_ptr(mvpMatrix)) | |
glUniformMatrix4fv(uMvpMatrixFromLightLocation, 1, GL_FALSE, | |
glm.value_ptr(mvpMatrixFromLightForGround)) | |
# Color | |
glUniform3fv(uColorLocation, 1, glm.value_ptr(groundColor)) | |
glUniform1i(uReceiveShadowLocation, 1) | |
glDrawArrays(GL_TRIANGLES, 0, 36) | |
pygame.display.flip() | |
pygame.time.wait(10) | |
main() |
Author
8Observer8
commented
Aug 15, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment