Skip to content

Instantly share code, notes, and snippets.

@nandor
Last active November 23, 2015 01:15
Show Gist options
  • Save nandor/c5d453d415a7d6a539c9 to your computer and use it in GitHub Desktop.
Save nandor/c5d453d415a7d6a539c9 to your computer and use it in GitHub Desktop.
Screen Space Ambient Occlusion
#!/usr/bin/env python2
import glfw
import math
import numpy as np
import random
import sys
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
def randVec(i, n):
"""Generates a random point in a hemisphere."""
x = random.uniform(-1, 1)
y = random.uniform(-1, 1)
z = random.uniform( 0.5, 1)
l = random.uniform(0.1, 1) / math.sqrt(x * x + y * y + z * z)
scale = (i + 1.0) / n
l *= 0.1 + 0.9 * scale * scale
return (x * l, y * l, z * l)
def randomTexture(w, h):
mat = np.zeros((w, h, 3), dtype=np.float)
for i, j in np.ndindex((w, h)):
mat[i, j, 0] = random.uniform(-1, 1)
mat[i, j, 1] = random.uniform(-1, 1)
mat[i, j, 2] = 0.0
return mat
VECS = [randVec(i, 8) for i in range(8)]
# Geometry shader.
GEOM_VS = '''
#version 410 core
layout(location = 0) in vec3 i_vert;
layout(location = 1) in vec3 i_norm;
layout(location = 2) in vec2 i_uv;
uniform mat4 u_proj;
uniform mat4 u_view;
out vec3 v_norm;
out vec3 v_vert;
out vec2 v_uv;
void main() {
vec4 view_space_vert = u_view * vec4(i_vert, 1.0);
vec4 view_space_norm = u_view * vec4(i_norm, 0.0);
v_vert = view_space_vert.xyz;
v_norm = view_space_norm.xyz;
gl_Position = u_proj * view_space_vert;
}
'''
GEOM_FS = '''
#version 410 core
uniform vec4 u_diffuse;
uniform mat4 u_proj;
layout(location = 0) out vec4 o_albedo;
layout(location = 1) out vec3 o_normal;
layout(location = 2) out vec4 o_vertex;
in vec3 v_norm;
in vec3 v_vert;
in vec3 v_uv;
float linear_depth(float depth) {
const float n = 0.1f;
const float f = 100.0f;
return (2 * n * f) / (f + n - (2.0 * depth - 1.0) * (f - n));
}
void main() {
o_albedo = u_diffuse;
o_normal = normalize(v_norm);
o_vertex = vec4(v_vert, linear_depth(gl_FragCoord.z));
}
'''
# SSAO shader.
SSAO_VS = '''
#version 410 core
layout(location = 0) in vec3 i_vertex;
out vec2 v_uv;
void main() {
v_uv = (i_vertex.xy + 1.0) / 2.0;
gl_Position = vec4(i_vertex, 1.0);
}
'''
SSAO_FS = '''
#version 410 core
const vec3 SAMPLES[] = vec3[](%(samples)s);
const float FOCUS = 0.15f;
const float POWER = 5.0;
uniform mat4 u_proj;
uniform sampler2D u_random;
uniform sampler2D u_normal;
uniform sampler2D u_vertex;
layout(location = 0) out float ao;
in vec2 v_uv;
void main() {
// Read the position, normal and the random vector.
vec2 scale = textureSize(u_normal, 0) / textureSize(u_random, 0);
vec4 vert = texture(u_vertex, v_uv);
vec3 norm = texture(u_normal, v_uv).rgb;
vec3 rvec = texture(u_random, v_uv * scale).rgb;
// Compute the btn matrix to rotate the hemisphere.
vec3 tang = normalize(rvec - norm * dot(rvec, norm));
vec3 btan = cross(norm, tang);
mat3 tbn = mat3(tang, btan, norm);
float occlusion = 0.0;
for (int i = 0; i < %(count)d; ++i) {
// Compute an offset from the center point & project it.
vec3 smpl_view = tbn * SAMPLES[i] * FOCUS + vert.xyz;
vec4 smpl_proj = u_proj * vec4(smpl_view, 1);
smpl_proj.xyz /= smpl_proj.w;
smpl_proj.xy = smpl_proj.xy * 0.5f + 0.5f;
// Sample the depth.
float depth = -texture(u_vertex, smpl_proj.xy).w;
// Check for edges with a steep falloff.
float range = smoothstep(0, 1, FOCUS / abs(vert.w - depth));
// If point occluded, add it to the accumulator.
occlusion += depth > smpl_view.z ? 1 : 0;
}
ao = pow(1.0 - occlusion / %(count)d, POWER);
}
''' % {
'samples': ','.join(['vec3(%1.4f,%1.4f,%1.4f)' % v for v in VECS]),
'count': len(VECS)
}
# Point light shader.
LIGHT_FS = '''
#version 410 core
const vec3 LIGHT_DIR = vec3(0.5, 1, -1);
uniform sampler2D u_albedo;
uniform sampler2D u_normal;
uniform sampler2D u_vertex;
uniform sampler2D u_ao;
layout(location = 0) out vec4 o_colour;
in vec2 v_uv;
void main() {
vec3 albedo = texture(u_albedo, v_uv).rgb;
vec3 normal = normalize(texture(u_normal, v_uv).rgb);
vec3 vertex = texture(u_vertex, v_uv).rgb;
vec2 s = 1.0 / textureSize(u_normal, 0);
// 5x5 box blur.
float ao = 0.0;
ao += texture(u_ao, v_uv + vec2(-2, -2) * s).r;
ao += texture(u_ao, v_uv + vec2(-1, -2) * s).r;
ao += texture(u_ao, v_uv + vec2( 0, -2) * s).r;
ao += texture(u_ao, v_uv + vec2( 1, -2) * s).r;
ao += texture(u_ao, v_uv + vec2( 2, -2) * s).r;
ao += texture(u_ao, v_uv + vec2(-2, -1) * s).r;
ao += texture(u_ao, v_uv + vec2(-1, -1) * s).r;
ao += texture(u_ao, v_uv + vec2( 0, -1) * s).r;
ao += texture(u_ao, v_uv + vec2( 1, -1) * s).r;
ao += texture(u_ao, v_uv + vec2( 2, -1) * s).r;
ao += texture(u_ao, v_uv + vec2(-2, 0) * s).r;
ao += texture(u_ao, v_uv + vec2(-1, 0) * s).r;
ao += texture(u_ao, v_uv + vec2( 0, 0) * s).r;
ao += texture(u_ao, v_uv + vec2( 1, 0) * s).r;
ao += texture(u_ao, v_uv + vec2( 2, 0) * s).r;
ao += texture(u_ao, v_uv + vec2(-2, 1) * s).r;
ao += texture(u_ao, v_uv + vec2(-1, 1) * s).r;
ao += texture(u_ao, v_uv + vec2( 0, 1) * s).r;
ao += texture(u_ao, v_uv + vec2( 1, 1) * s).r;
ao += texture(u_ao, v_uv + vec2( 2, 1) * s).r;
ao += texture(u_ao, v_uv + vec2(-2, 2) * s).r;
ao += texture(u_ao, v_uv + vec2(-1, 2) * s).r;
ao += texture(u_ao, v_uv + vec2( 0, 2) * s).r;
ao += texture(u_ao, v_uv + vec2( 1, 2) * s).r;
ao += texture(u_ao, v_uv + vec2( 2, 2) * s).r;
ao /= 25.0;
float angle = max(dot(normalize(normal), normalize(LIGHT_DIR)), 0.0);
vec3 colour = vec3(0.0);
colour += albedo * ao * 0.3;
colour += albedo * angle;
o_colour = vec4(colour, 1.0);
}
'''
LIGHT_VS = '''
#version 410 core
layout(location = 0) in vec3 i_vertex;
out vec2 v_uv;
void main() {
v_uv = (i_vertex.xy + 1.0) / 2.0;
gl_Position = vec4(i_vertex, 1.0);
}
'''
def frustum(left, right, bottom, top, znear, zfar):
"""Creates a view frustum."""
assert(right != left and bottom != top and znear != zfar)
M = np.zeros((4,4), dtype=np.float32)
M[0,0] = +2.0 * znear / (right - left)
M[2,0] = +(right + left) / (right - left)
M[1,1] = +2.0 * znear / (top - bottom)
M[3,1] = +(top + bottom) / (top - bottom)
M[2,2] = -(zfar + znear) / (zfar - znear)
M[3,2] = -2.0 * znear * zfar / (zfar - znear)
M[2,3] = -1.0
return M
def perspective(fovy, aspect, znear, zfar):
"""Creates a perspective projection matrix."""
assert(znear != zfar)
h = np.tan(fovy / 360.0 * np.pi) * znear
w = h * aspect
return frustum( -w, w, -h, h, znear, zfar )
def translate(M, x, y, z):
"""Translates a matrix along the axis."""
T = [[ 1, 0, 0, x], [ 0, 1, 0, y], [ 0, 0, 1, z], [ 0, 0, 0, 1]]
M[...] = np.dot(M, np.array(T, dtype=np.float32).T)
return M
def scale(M, x, y, z):
"""Scales a matrix."""
T = [[x, 0, 0, 0], [0, y, 0, 0], [0, 0, z, 0], [0, 0, 0, 1]]
M[...] = np.dot(M, np.array(T, dtype=np.float32).T)
return M
def rotate(M, angle, x, y, z):
"""Rotates a matrix around an axis."""
angle = math.pi * angle / 180
c = math.cos(angle)
s = math.sin(angle)
n = math.sqrt(x * x + y * y + z * z)
x /= n
y /= n
z /= n
cx, cy, cz = (1 - c) * x, (1 - c) * y, (1 - c) * z
M[...] = np.dot(M, np.array([
[ cx * x + c, cy * x - z * s, cz * x + y * s, 0],
[ cx * y + z * s, cy * y + c, cz * y - x * s, 0],
[ cx * z - y * s, cy * z + x * s, cz * z + c, 0],
[ 0, 0, 0, 1]
]).T)
return M
class Mesh(object):
"""Wrapper around OpenGL VAO & VBO."""
def __init__(self, mesh):
"""Creates a new mesh out of a numpy buffer."""
self._vao = glGenVertexArrays(1)
self._vbo = glGenBuffers(1)
self._len = mesh.size / 8
glBindVertexArray(self._vao)
glBindBuffer(GL_ARRAY_BUFFER, self._vbo)
glBufferData(GL_ARRAY_BUFFER, self._len * 32, mesh, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glEnableVertexAttribArray(2)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 32, ctypes.c_void_p(0))
glVertexAttribPointer(1, 3, GL_FLOAT, False, 32, ctypes.c_void_p(12))
glVertexAttribPointer(2, 2, GL_FLOAT, False, 32, ctypes.c_void_p(24))
def render(self):
"""Renders the mesh."""
glBindVertexArray(self._vao)
glDrawArrays(GL_TRIANGLES, 0, self._len)
class Cube(Mesh):
"""Simple cube mesh."""
def __init__(self, x, y, z, w, h, d):
"""Createas a cube out of a numpy buffer."""
super(Cube, self).__init__(np.array([
x - w, y + h, z - d, +0.0, +1.0, +0.0, 0.0, 0.0,
x - w, y + h, z + d, +0.0, +1.0, +0.0, 0.0, 0.0,
x + w, y + h, z + d, +0.0, +1.0, +0.0, 0.0, 0.0,
x - w, y + h, z - d, +0.0, +1.0, +0.0, 0.0, 0.0,
x + w, y + h, z + d, +0.0, +1.0, +0.0, 0.0, 0.0,
x + w, y + h, z - d, +0.0, +1.0, +0.0, 0.0, 0.0,
x - w, y - h, z - d, -1.0, +0.0, +0.0, 0.0, 0.0,
x - w, y - h, z + d, -1.0, +0.0, +0.0, 0.0, 0.0,
x - w, y + h, z + d, -1.0, +0.0, +0.0, 0.0, 0.0,
x - w, y - h, z - d, -1.0, +0.0, +0.0, 0.0, 0.0,
x - w, y + h, z + d, -1.0, +0.0, +0.0, 0.0, 0.0,
x - w, y + h, z - d, -1.0, +0.0, +0.0, 0.0, 0.0,
x - w, y - h, z - d, +1.0, +0.0, -1.0, 0.0, 0.0,
x + w, y + h, z - d, +1.0, +0.0, -1.0, 0.0, 0.0,
x + w, y - h, z - d, +1.0, +0.0, -1.0, 0.0, 0.0,
x - w, y - h, z - d, +1.0, +0.0, -1.0, 0.0, 0.0,
x - w, y + h, z - d, +1.0, +0.0, -1.0, 0.0, 0.0,
x + w, y + h, z - d, +1.0, +0.0, -1.0, 0.0, 0.0,
x - w, y - h, z + d, +1.0, +0.0, +1.0, 0.0, 0.0,
x + w, y - h, z + d, +1.0, +0.0, +1.0, 0.0, 0.0,
x + w, y + h, z + d, +1.0, +0.0, +1.0, 0.0, 0.0,
x - w, y - h, z + d, +1.0, +0.0, +1.0, 0.0, 0.0,
x + w, y + h, z + d, +1.0, +0.0, +1.0, 0.0, 0.0,
x - w, y + h, z + d, +1.0, +0.0, +1.0, 0.0, 0.0,
], dtype=np.float32))
class Object(Mesh):
"""Wrapper that loads object files."""
def __init__(self, path):
with open(path) as f:
vert = []
norm = []
data = []
def add(data, v, n):
data += v
data += n
data += [0, 0]
for line in f.readlines():
tokens = line.strip().split(' ')
if tokens[0] == 'v':
[x, y, z] = map(float, tokens[1:])
vert.append([x, y, z])
continue
if tokens[0] == 'vn':
[nx, ny, nz] = map(float, tokens[1:])
norm.append([nx, ny, nz])
continue
if tokens[0] == 'f':
[f0, f1, f2] = map(lambda x: map(int, x.split('//')), tokens[1:])
add(data, vert[f0[0] - 1], norm[f0[1] - 1])
add(data, vert[f1[0] - 1], norm[f1[1] - 1])
add(data, vert[f2[0] - 1], norm[f2[1] - 1])
continue
super(Object, self).__init__(np.array(data, dtype=np.float32))
class Quad(Mesh):
"""Wrapper around an OpenGL quad VAO."""
def __init__(self):
"""Initializes the mesh."""
super(Quad, self).__init__(np.array([
-1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
-1.0, +1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+1.0, +1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+1.0, +1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
], dtype=np.float32))
class Plane(Mesh):
"""Draws a plane parallel to xz."""
def __init__(self):
"""Initializes the mesh."""
super(Plane, self).__init__(np.array([
-10.0, -1.0, -10.0, 0.0, 1.0, 0.0, 0.0, 0.0,
-10.0, -1.0, +10.0, 0.0, 1.0, 0.0, 0.0, 0.0,
+10.0, -1.0, +10.0, 0.0, 1.0, 0.0, 0.0, 0.0,
-10.0, -1.0, -10.0, 0.0, 1.0, 0.0, 0.0, 0.0,
+10.0, -1.0, +10.0, 0.0, 1.0, 0.0, 0.0, 0.0,
+10.0, -1.0, -10.0, 0.0, 1.0, 0.0, 0.0, 0.0,
], dtype=np.float32))
class Program(object):
"""Wrapper around OpenGL programs."""
def __init__(self, vert_source, frag_source):
"""Creates a new program."""
self._vert = self._compile_shader(GL_VERTEX_SHADER, vert_source)
self._frag = self._compile_shader(GL_FRAGMENT_SHADER, frag_source)
self._prog = self._link_shaders([self._vert, self._frag])
def bind(self):
"""Binds the program to the context."""
glUseProgram(self._prog)
def __setitem__(self, key, value):
"""Sets the value of a uniform parameter."""
if not key in self._unifs:
return
unif = self._unifs[key]
if value.__class__ == np.ndarray:
glUniformMatrix4fv(unif, 1, False, value)
return
if value.__class__ == tuple:
if len(value) == 2 and value[0].__class__ == Texture:
tex, loc = value
tex.bind(loc)
glUniform1i(unif, loc)
return
if len(value) == 4:
glUniform4f(unif, value[0], value[1], value[2], value[3])
return
raise TypeError('Invalid uniform type for %s: %s', key, value.__class__)
def _compile_shader(self, type, source):
"""Compiles a single shader of a give type from source."""
shader = glCreateShader(type)
if shader < 0:
raise RuntimeError('Cannot create shader object.')
glShaderSource(shader, source)
glCompileShader(shader)
if not glGetShaderiv(shader, GL_COMPILE_STATUS):
raise ValueError(glGetShaderInfoLog(shader))
return shader
def _link_shaders(self, shaders):
"""Links all shader objects."""
prog = glCreateProgram()
for shader in shaders:
glAttachShader(prog, shader)
# Link the program.
glLinkProgram(prog)
if not glGetProgramiv(prog, GL_LINK_STATUS):
raise ValueError(glGetProgramInfoLog(prog))
# Fetch the location of all uniforms.
self._unifs = {}
for i in range(glGetProgramiv(prog, GL_ACTIVE_UNIFORMS)):
name, size, type = glGetActiveUniform(prog, i)
self._unifs[name] = glGetUniformLocation(prog, name)
return prog
class Texture(object):
"""Wrapper around OpenGL textures."""
def __init__(self, w, h, d, type, data=None):
"""Initializes an empty texture."""
self.w = w
self.h = h
self.d = d
self.type = type
self.tex = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, self.tex)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
fmt, ct, src = {
(1, GL_UNSIGNED_BYTE,): ( GL_LUMINANCE, GLubyte, GL_LUMINANCE),
(2, GL_UNSIGNED_BYTE,): ( GL_LUMINANCE_ALPHA, GLubyte, GL_LUMINANCE_ALPHA),
(3, GL_UNSIGNED_BYTE,): ( GL_RGB, GLubyte, GL_RGB),
(4, GL_UNSIGNED_BYTE,): ( GL_RGBA, GLubyte, GL_RGBA),
(1, GL_FLOAT): ( GL_R32F, GLfloat, GL_LUMINANCE),
(2, GL_FLOAT): ( GL_RG32F, GLfloat, GL_LUMINANCE_ALPHA),
(3, GL_FLOAT): ( GL_RGB32F, GLfloat, GL_RGB),
(4, GL_FLOAT): ( GL_RGBA32F, GLfloat, GL_RGBA),
}[d, type]
if data is None:
glTexImage2D(
GL_TEXTURE_2D,
0,
fmt,
w,
h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
ctypes.c_void_p(None)
)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
else:
glTexImage2D(
GL_TEXTURE_2D,
0,
fmt,
w,
h,
0,
src,
type,
data
)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
def bind(self, loc):
"""Binds a texture to a location."""
glActiveTexture(GL_TEXTURE0 + loc)
glBindTexture(GL_TEXTURE_2D, self.tex)
class FBO(object):
"""Wrapper around OpenGL frame buffer objects."""
def __init__(self, textures):
"""Initializes the FBO."""
self._buffers = [GL_COLOR_ATTACHMENT0 + i for i in range(len(textures))]
if not textures:
self._fbo = 0
return
self._fbo = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, self._fbo)
# Create a renderbuffer for depth.
self._depth = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, self._depth)
glRenderbufferStorage(
GL_RENDERBUFFER,
GL_DEPTH_COMPONENT24,
textures[0].w,
textures[0].h
)
glFramebufferRenderbuffer(
GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER,
self._depth
)
# Attach all textures to color attachments.
for idx, texture in enumerate(textures):
glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + idx,
GL_TEXTURE_2D,
texture.tex,
0
)
if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE:
raise ValueError('Framebuffer not complete.')
def bind(self):
"""Activates the frame buffer object."""
glBindFramebuffer(GL_FRAMEBUFFER, self._fbo)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if self._buffers:
glDrawBuffers(self._buffers)
def main():
"""Entry point of the application."""
if not glfw.init():
return
glfw.window_hint(glfw.RESIZABLE, False)
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(1280, 720, 'SSAO', None, None)
if not window:
glfw.terminate()
return
width, height = glfw.get_framebuffer_size(window)
glfw.make_context_current(window)
glViewport(0, 0, width, height)
glFrontFace(GL_CCW)
glCullFace(GL_BACK)
# Load and create OpenGL resources.
quad = Quad()
plane = Plane()
bunny = Object('bunny.obj')
cubes = [
Cube(1, -1.2, 0.5, 0.5, 0.5, 0.5),
Cube(1.2, -1.4, 0.7, 0.5, 0.5, 0.5)
]
geom = Program(GEOM_VS, GEOM_FS)
ssao = Program(SSAO_VS, SSAO_FS)
light = Program(LIGHT_VS, LIGHT_FS)
albedo = Texture(width, height, 4, GL_UNSIGNED_BYTE)
normal = Texture(width, height, 2, GL_FLOAT)
vertex = Texture(width, height, 4, GL_FLOAT)
ao = Texture(width, height, 1, GL_FLOAT)
gao = Texture(width, height, 1, GL_FLOAT)
rand = Texture(5, 5, 3, GL_FLOAT, randomTexture(5, 5))
geom_fbo = FBO([albedo, normal, vertex])
ao_fbo = FBO([ao])
no_fbo = FBO([])
# Set up the camera.
proj = perspective(45.0, 1.0 * width / height, 0.1, 100.0)
view = np.eye(4, dtype=np.float32)
view = rotate(view, 20, 1, 0, 0)
view = translate(view, 0, 0, -7)
frames, lastTime = 0, glfw.get_time()
while not glfw.window_should_close(window):
# Render the model.
glEnable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE)
geom_fbo.bind()
geom.bind()
geom['u_proj'] = proj
geom['u_view'] = view
geom['u_diffuse'] = (0.9, 0.9, 0.9, 1)
plane.render()
geom['u_diffuse'] = (1, 1, 1, 1)
bunny.render()
for cube in cubes:
cube.render()
glDisable(GL_CULL_FACE)
glDisable(GL_DEPTH_TEST)
# Compute ambient occlusion.
ao_fbo.bind()
ssao.bind()
ssao['u_proj'] = proj
ssao['u_random'] = (rand, 0)
ssao['u_normal'] = (normal, 1)
ssao['u_vertex'] = (vertex, 2)
quad.render()
# Render the model with lighting.
no_fbo.bind()
light.bind()
light['u_albedo'] = (albedo, 0)
light['u_normal'] = (normal, 1)
light['u_ao'] = (ao, 3)
quad.render()
glfw.swap_buffers(window)
glfw.poll_events()
# FPS counter.
time = glfw.get_time()
frames += 1
if time - lastTime > 1.0:
print 'FPS %2f' % frames
frames = 0
lastTime = time
glfw.terminate()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment