Skip to content

Instantly share code, notes, and snippets.

@8Observer8
Last active August 16, 2024 12:11
Show Gist options
  • Save 8Observer8/0ec34fac62770e30fc9fd38d8b342b00 to your computer and use it in GitHub Desktop.
Save 8Observer8/0ec34fac62770e30fc9fd38d8b342b00 to your computer and use it in GitHub Desktop.
Shadow mapping using Pygame, PyOpenGL, and PyGLM
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)
# 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()
@8Observer8
Copy link
Author

pip install Pygame PyOpenGL PyGLM numpy
python main.py

cuboids-with-simple-shadows-opengles20-pygame-python-camtasia

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment