Screen Space Ambient Occlusion¶
Using the multiple render targets we will render color, position and normals in textures to be used by the ssao algoritm
[1]:
import ipywebgl
import numpy as np
[2]:
w = ipywebgl.GLViewer()
w.clear_color(.8, .8, .8 ,1)
w.clear()
w.enable(depth_test=True)
w.execute_commands(execute_once=True)
[3]:
# Multi render target setup for the scene
color_geo_buffer = w.create_framebuffer()
w.bind_framebuffer('FRAMEBUFFER', color_geo_buffer)
color_target = w.create_texture()
w.bind_texture('TEXTURE_2D', color_target)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE')
w.tex_storage_2d('TEXTURE_2D', 1, 'RGBA8', w.width, w.height)
w.framebuffer_texture_2d('FRAMEBUFFER', 'COLOR_ATTACHMENT0', 'TEXTURE_2D', color_target, 0)
position_target = w.create_texture()
w.bind_texture('TEXTURE_2D', position_target)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE')
w.tex_storage_2d('TEXTURE_2D', 1, 'RGBA16F', w.width, w.height)
w.framebuffer_texture_2d('FRAMEBUFFER', 'COLOR_ATTACHMENT1', 'TEXTURE_2D', position_target, 0)
normal_target = w.create_texture()
w.bind_texture('TEXTURE_2D', normal_target)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE')
w.tex_storage_2d('TEXTURE_2D', 1, 'RGBA16F', w.width, w.height)
w.framebuffer_texture_2d('FRAMEBUFFER', 'COLOR_ATTACHMENT2', 'TEXTURE_2D', normal_target, 0)
depth_target = w.create_texture()
w.bind_texture('TEXTURE_2D', depth_target)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE')
w.tex_storage_2d('TEXTURE_2D', 1, 'DEPTH_COMPONENT16', w.width, w.height)
w.framebuffer_texture_2d('FRAMEBUFFER', 'DEPTH_ATTACHMENT', 'TEXTURE_2D', depth_target, 0)
w.draw_buffers(['COLOR_ATTACHMENT0', 'COLOR_ATTACHMENT1', 'COLOR_ATTACHMENT2'])
w.bind_framebuffer('FRAMEBUFFER', None)
w.execute_commands(execute_once=True)
[4]:
mrt_prog = w.create_program_ext(
"""#version 300 es
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform mat4 u_world;
in vec3 in_vert;
in vec3 in_normal;
out vec4 v_viewposition;
out vec4 v_viewnormal;
void main() {
vec4 pos = u_world * vec4(in_vert, 1.0);
vec4 normal = u_world * vec4(in_normal, 0.0);
v_viewposition = u_viewMatrix * pos;
v_viewnormal = u_viewMatrix * normal;
gl_Position = u_projectionMatrix * v_viewposition;
}
"""
,
"""#version 300 es
precision highp float;
uniform vec3 u_color;
in vec4 v_viewposition;
in vec4 v_viewnormal;
layout(location=0) out vec4 color;
layout(location=1) out vec4 viewPosition;
layout(location=2) out vec4 viewNormal;
void main() {
color = vec4(u_color, 1);
viewPosition = v_viewposition;
viewNormal = vec4(normalize(v_viewnormal.xyz), 0);
}
""",
{
'in_vert' : 0,
'in_normal' : 1
})
[5]:
show_texture_prog = w.create_program_ext(
"""#version 300 es
in vec2 in_vert;
void main() {
gl_Position = vec4(in_vert, 0, 1);
}
"""
,
"""#version 300 es
precision highp float;
uniform sampler2D u_texture;
out vec4 color;
void main() {
ivec2 size = textureSize(u_texture, 0); //so we can display smaller textures
color = vec4(texelFetch(u_texture, ivec2(gl_FragCoord.xy) % size, 0).rgb, 1.0);
//color = vec4(gl_FragCoord.xyz,1);
}
""",
{
'in_vert' : 0,
})
[6]:
sphere_vbo = w.create_buffer_ext(
src_data=np.array(
[[ 0. , 0. , 1. , 0. , 0. , 1. ],
[-0.72, -0.53, 0.45, -0.72, -0.53, 0.45],
[ 0.28, -0.85, 0.45, 0.28, -0.85, 0.45],
[ 0.89, 0. , 0.45, 0.89, 0. , 0.45],
[ 0.28, 0.85, 0.45, 0.28, 0.85, 0.45],
[-0.72, 0.53, 0.45, -0.72, 0.53, 0.45],
[-0.89, -0. , -0.45, -0.89, -0. , -0.45],
[-0.28, -0.85, -0.45, -0.28, -0.85, -0.45],
[ 0.72, -0.53, -0.45, 0.72, -0.53, -0.45],
[ 0.72, 0.53, -0.45, 0.72, 0.53, -0.45],
[-0.28, 0.85, -0.45, -0.28, 0.85, -0.45],
[-0. , 0. , -1. , -0. , 0. , -1. ],
[ 0.16, -0.5 , 0.85, 0.16, -0.5 , 0.85],
[-0.43, -0.31, 0.85, -0.43, -0.31, 0.85],
[-0.26, -0.81, 0.53, -0.26, -0.81, 0.53],
[ 0.53, -0. , 0.85, 0.53, -0. , 0.85],
[ 0.69, -0.5 , 0.53, 0.69, -0.5 , 0.53],
[ 0.16, 0.5 , 0.85, 0.16, 0.5 , 0.85],
[ 0.69, 0.5 , 0.53, 0.69, 0.5 , 0.53],
[-0.43, 0.31, 0.85, -0.43, 0.31, 0.85],
[-0.26, 0.81, 0.53, -0.26, 0.81, 0.53],
[-0.85, -0. , 0.53, -0.85, -0. , 0.53],
[-0.59, -0.81, -0. , -0.59, -0.81, -0. ],
[-0. , -1. , -0. , -0. , -1. , -0. ],
[ 0.59, -0.81, 0. , 0.59, -0.81, 0. ],
[ 0.95, -0.31, -0. , 0.95, -0.31, -0. ],
[ 0.95, 0.31, -0. , 0.95, 0.31, -0. ],
[ 0.59, 0.81, -0. , 0.59, 0.81, -0. ],
[-0. , 1. , -0. , -0. , 1. , -0. ],
[-0.59, 0.81, -0. , -0.59, 0.81, -0. ],
[-0.95, 0.31, 0. , -0.95, 0.31, 0. ],
[-0.95, -0.31, 0. , -0.95, -0.31, 0. ],
[-0.69, -0.5 , -0.53, -0.69, -0.5 , -0.53],
[ 0.26, -0.81, -0.53, 0.26, -0.81, -0.53],
[ 0.85, 0. , -0.53, 0.85, 0. , -0.53],
[ 0.26, 0.81, -0.53, 0.26, 0.81, -0.53],
[-0.69, 0.5 , -0.53, -0.69, 0.5 , -0.53],
[-0.53, -0. , -0.85, -0.53, -0. , -0.85],
[-0.16, -0.5 , -0.85, -0.16, -0.5 , -0.85],
[ 0.43, -0.31, -0.85, 0.43, -0.31, -0.85],
[ 0.43, 0.31, -0.85, 0.43, 0.31, -0.85],
[-0.16, 0.5 , -0.85, -0.16, 0.5 , -0.85]], dtype=np.float32).flatten()
)
indices = np.array(
[[0, 13, 12], [12, 14, 2], [12, 13, 14], [13, 1, 14], [0, 12, 15], [15, 16, 3], [15, 12, 16], [12, 2, 16],
[0, 15, 17], [17, 18, 4], [17, 15, 18], [15, 3, 18], [0, 17, 19], [19, 20, 5], [19, 17, 20], [17, 4, 20],
[0, 19, 13], [13, 21, 1], [13, 19, 21], [19, 5, 21], [1, 22, 14], [14, 23, 2], [14, 22, 23], [22, 7, 23],
[2, 24, 16], [16, 25, 3], [16, 24, 25], [24, 8, 25], [3, 26, 18], [18, 27, 4], [18, 26, 27], [26, 9, 27],
[4, 28, 20], [20, 29, 5], [20, 28, 29], [28, 10, 29], [5, 30, 21], [21, 31, 1], [21, 30, 31], [30, 6, 31],
[1, 31, 22], [22, 32, 7], [22, 31, 32], [31, 6, 32], [2, 23, 24], [24, 33, 8], [24, 23, 33], [23, 7, 33],
[3, 25, 26], [26, 34, 9], [26, 25, 34], [25, 8, 34], [4, 27, 28], [28, 35, 10], [28, 27, 35], [27, 9, 35],
[5, 29, 30], [30, 36, 6], [30, 29, 36], [29, 10, 36], [6, 37, 32], [32, 38, 7], [32, 37, 38], [37, 11, 38],
[7, 38, 33], [33, 39, 8], [33, 38, 39], [38, 11, 39], [8, 39, 34], [34, 40, 9], [34, 39, 40], [39, 11, 40],
[9, 40, 35], [35, 41, 10], [35, 40, 41], [40, 11, 41], [10, 41, 36], [36, 37, 6], [36, 41, 37], [41, 11, 37]],
dtype=np.uint8).flatten()
sphere_vao = w.create_vertex_array_ext(
mrt_prog,
[
(sphere_vbo, '3f32 3f32', 'in_vert', 'in_normal'),
],
indices
)
plane_vbo = w.create_buffer_ext(
src_data=np.array(
[[10 , 0. , -10. , 0. , 1 , 0. ],
[-10 , 0. , -10. , 0. , 1 , 0. ],
[-10 , 0. , 10. , 0. , 1 , 0. ],
[-10 , 0. , 10. , 0. , 1 , 0. ],
[10 , 0. , 10. , 0. , 1 , 0. ],
[10 , 0. , -10. , 0. , 1 , 0. ]], dtype=np.float32).flatten()
)
plane_vao = w.create_vertex_array_ext(
mrt_prog,
[
(plane_vbo, '3f32 3f32', 'in_vert', 'in_normal'),
]
)
screen_vbo = w.create_buffer_ext(
src_data=np.array(
[-1, 1,
-1, -1,
1, -1,
-1, 1,
1, -1,
1, 1,], dtype=np.float32).flatten()
)
screen_vao = w.create_vertex_array_ext(
show_texture_prog,
[
(screen_vbo, '2f32', 'in_vert'),
]
)
[7]:
# bind the different texture to the different texture units
w.active_texture(0)
w.bind_texture('TEXTURE_2D', color_target)
w.active_texture(1)
w.bind_texture('TEXTURE_2D', position_target)
w.active_texture(2)
w.bind_texture('TEXTURE_2D', normal_target)
w.execute_commands(execute_once=True)
# scene to render
spheres_count = 20
spheres = np.eye(4)[np.newaxis,...].repeat(spheres_count, axis=0)
spheres[:,:3,3] = np.random.random([spheres_count,3]) * 6 - 2.5
spheres[:,1,3] += 3
plane = np.eye(4, dtype=np.float32)
colors = np.random.random([spheres_count,3]).astype(np.float32) * 0.5 + .5;
def render_scene():
# draw the scene
w.bind_vertex_array(sphere_vao)
for i in range(spheres.shape[0]):
w.uniform_matrix('u_world', spheres[i,:,:].T)
w.uniform('u_color', colors[i,:])
w.draw_elements('TRIANGLES', indices.shape[0], 'UNSIGNED_BYTE', 0)
w.bind_vertex_array(plane_vao)
w.uniform_matrix('u_world', plane.T)
w.draw_arrays('TRIANGLES', 0, 6)
def render_scene_mrt():
w.enable(depth_test=True)
w.clear_color(0,0,0,0)
# render in the render targets
w.bind_framebuffer('FRAMEBUFFER', color_geo_buffer)
w.clear()
w.use_program(mrt_prog)
render_scene()
# release the render targets
w.bind_framebuffer('FRAMEBUFFER', None)
[8]:
ssao_prog = w.create_program_ext(
"""#version 300 es
in vec2 in_vert;
void main() {
gl_Position = vec4(in_vert, 0, 1);
}
"""
,
"""#version 300 es
precision highp float;
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform sampler2D u_position;
uniform sampler2D u_normal;
uniform sampler2D u_noise;
uniform vec3 u_kernels[32];
uniform float u_radius;
uniform float u_bias;
out vec4 color;
void main() {
ivec2 noiseSize = textureSize(u_noise, 0);
vec4 posBuffer = texelFetch(u_position, ivec2(gl_FragCoord.xy), 0).rgba;
vec3 fragPos = posBuffer.rgb;
vec3 normal = texelFetch(u_normal, ivec2(gl_FragCoord.xy), 0).rgb;
vec3 randomVec = texelFetch(u_noise, ivec2(gl_FragCoord.xy) % noiseSize, 0).rgb;
vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, bitangent, normal);
float occlusion = 0.0;
for(int i = 0; i < 32; ++i)
{
// get sample position
vec3 samplePos = TBN * u_kernels[i]; // from tangent to view-space
samplePos = fragPos + samplePos * u_radius;
vec4 offset = vec4(samplePos, 1.0);
offset = u_projectionMatrix * offset; // from view to clip-space
offset.xyz /= offset.w; // perspective divide
offset.xyz = offset.xyz * 0.5 + 0.5; // transform to range 0.0 - 1.0
float sampleDepth = texture(u_position, offset.xy).z;
float rangeCheck = smoothstep(0.0, 1.0, u_radius / abs(fragPos.z - sampleDepth));
occlusion += (sampleDepth >= samplePos.z + u_bias ? 1.0 : 0.0) * rangeCheck;
}
occlusion *= posBuffer.a;
color = vec4((1.0 - occlusion/32.0) , 1, 1, 1);
}
""",
{
'in_vert' : 0,
})
[9]:
blur_prog = w.create_program_ext(
"""#version 300 es
in vec2 in_vert;
void main() {
gl_Position = vec4(in_vert, 0, 1);
}
"""
,
"""#version 300 es
precision highp float;
uniform sampler2D u_texture;
out vec4 color;
void main() {
float result = 0.0;
for (int x = -2; x < 3; ++x)
{
for (int y = -2; y < 3; ++y)
{
float src = texelFetch(u_texture, ivec2(gl_FragCoord.xy) + ivec2(x,y), 0).r;
result += src;
}
}
float fragcolor = result / (5.0 * 5.0);
color = vec4(fragcolor,fragcolor,fragcolor, 1);
}
""",
{
'in_vert' : 0,
})
[10]:
kernels_count = 32
kernels = np.random.random([kernels_count,3]).astype(np.float32)
kernels *= np.array([2,2,1], dtype=np.float32)
kernels -= np.array([1,1,0], dtype=np.float32)
kernels /= np.linalg.norm(kernels, axis=1).reshape([kernels_count,1])
kernels *= (np.random.random([kernels_count,1]) *.9 + .1)
kernels
[10]:
array([[ 0.14061414, -0.29395023, 0.26397115],
[-0.25332135, -0.5434412 , 0.32016873],
[ 0.43410292, 0.14364298, 0.130379 ],
[ 0.17611985, 0.10773236, 0.08423054],
[-0.19781537, 0.49038583, 0.45685023],
[ 0.47554928, -0.24079715, 0.41659293],
[-0.66781247, -0.26011485, 0.5997044 ],
[ 0.11844267, -0.27249858, 0.3260924 ],
[ 0.09345651, -0.3128616 , 0.45180222],
[ 0.54156613, -0.22780262, 0.2831563 ],
[ 0.34106463, -0.6077427 , 0.14623228],
[-0.25435647, -0.41474894, 0.36305118],
[-0.31858614, 0.25884432, 0.32779214],
[ 0.31594166, -0.1629581 , 0.72806203],
[ 0.41861174, -0.64451885, 0.54554665],
[ 0.16399065, -0.04651393, 0.18215412],
[-0.42527235, 0.32339367, 0.11478794],
[-0.16720796, 0.01177233, 0.01208589],
[-0.11581887, 0.2289092 , 0.13825648],
[-0.12006161, 0.10574264, 0.490239 ],
[-0.19494195, 0.07700069, 0.16450875],
[-0.09289566, 0.12625498, 0.07474788],
[-0.29865226, -0.23536852, 0.04863004],
[-0.03663306, -0.5277404 , 0.10556357],
[ 0.54410267, -0.4087954 , 0.04883142],
[-0.80641663, -0.37124044, 0.06008283],
[ 0.3984966 , -0.01038375, 0.5113025 ],
[-0.18555135, 0.13276961, 0.0864196 ],
[ 0.18193391, -0.03025563, 0.18538079],
[-0.32308352, -0.03066859, 0.05449117],
[ 0.11514763, 0.4769348 , 0.20023301],
[-0.24256429, -0.02219871, 0.49027848]], dtype=float32)
[11]:
noise = np.zeros([4,4,4], dtype=np.uint8)
noise[:,:,:2] = np.random.random([4,4,2]) * 255 - 128
# create the noise texture to randomly rotate the kernel
# bind it to the 3rd active_texture
noise_tex = w.create_texture()
w.active_texture(3)
w.bind_texture('TEXTURE_2D', noise_tex)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'REPEAT')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'REPEAT')
w.tex_image_2d('TEXTURE_2D', 0, 'RGBA', 4, 4, 0, 'RGBA', 'UNSIGNED_BYTE', noise.flatten())
w.execute_commands(execute_once=True)
[12]:
# ssao render target
occlusion_buffer = w.create_framebuffer()
w.bind_framebuffer('FRAMEBUFFER', occlusion_buffer)
# create the ssao texture and bind it to the texture_4
occlusion_target = w.create_texture()
w.active_texture(4)
w.bind_texture('TEXTURE_2D', occlusion_target)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE')
w.tex_storage_2d('TEXTURE_2D', 1, 'R8', w.width, w.height)
w.framebuffer_texture_2d('FRAMEBUFFER', 'COLOR_ATTACHMENT0', 'TEXTURE_2D', occlusion_target, 0)
w.bind_framebuffer('FRAMEBUFFER', None)
# blured ssao render target
occlusion_buffer_blured = w.create_framebuffer()
w.bind_framebuffer('FRAMEBUFFER', occlusion_buffer_blured)
# create the blured ssao texture and bind it to the texture_5
occlusion_target_blured = w.create_texture()
w.active_texture(5)
w.bind_texture('TEXTURE_2D', occlusion_target_blured)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE')
w.tex_storage_2d('TEXTURE_2D', 1, 'R8', w.width, w.height)
w.framebuffer_texture_2d('FRAMEBUFFER', 'COLOR_ATTACHMENT0', 'TEXTURE_2D', occlusion_target_blured, 0)
w.bind_framebuffer('FRAMEBUFFER', None)
w.execute_commands(execute_once=True)
[13]:
# render the ssao
def render_with_ssao():
# render the scene in the MRT
render_scene_mrt()
# render ssao
w.bind_framebuffer('FRAMEBUFFER', occlusion_buffer)
w.clear()
w.use_program(ssao_prog)
w.uniform('u_kernels[0]', kernels)
w.uniform('u_radius', np.array([0.8], dtype=np.float32))
w.uniform('u_bias', np.array([0.2], dtype=np.float32))
w.uniform('u_position', np.array([1], dtype=np.int32))
w.uniform('u_normal', np.array([2], dtype=np.int32))
w.uniform('u_noise', np.array([3], dtype=np.int32))
w.bind_vertex_array(screen_vao)
w.draw_arrays('TRIANGLES',0, 6)
# blur
w.bind_framebuffer('FRAMEBUFFER', occlusion_buffer_blured)
w.clear()
w.use_program(blur_prog)
w.uniform('u_texture', np.array([4], dtype=np.int32))
w.bind_vertex_array(screen_vao)
w.draw_arrays('TRIANGLES',0, 6)
w.bind_framebuffer('FRAMEBUFFER', None)
[14]:
from ipywidgets import widgets, interact
def _render(texture_id=0):
#render the scene
render_with_ssao()
# display the texture
w.bind_framebuffer('FRAMEBUFFER', None)
w.disable(depth_test=True)
w.clear()
w.use_program(show_texture_prog)
w.bind_vertex_array(screen_vao)
w.uniform('u_texture', np.array([texture_id], dtype=np.int32))
w.draw_arrays('TRIANGLES',0, 6)
w.execute_commands()
interact(
_render,
texture_id=widgets.IntSlider(description='texture id', value=0, min=0, max=5)
)
w
[14]:
[15]:
final_prog = w.create_program_ext(
"""#version 300 es
in vec2 in_vert;
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform vec3 u_lightDir;
out vec3 v_lightDir;
void main() {
gl_Position = vec4(in_vert, 0, 1);
v_lightDir = (u_viewMatrix * vec4(u_lightDir, 0)).xyz;
}
"""
,
"""#version 300 es
precision highp float;
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform sampler2D u_color;
uniform sampler2D u_normal;
uniform sampler2D u_ssao;
in vec3 v_lightDir;
out vec4 color;
void main() {
vec3 albedo = texelFetch(u_color, ivec2(gl_FragCoord.xy), 0).rgb;
vec3 normal = texelFetch(u_normal, ivec2(gl_FragCoord.xy), 0).rgb;
float ssao = texelFetch(u_ssao, ivec2(gl_FragCoord.xy), 0).r;
float ndotl = dot(v_lightDir, normal);
float gradient_dot = (u_cameraMatrix * vec4(normal, 0)).y;
vec3 ambient = mix(vec3(.3,.2,.4) , vec3(.5,.8,.8), gradient_dot) *.5 * ssao * ssao * ssao;
vec3 light = mix(vec3(0,0,0), vec3(.6,.8,.8), ndotl) * .5 * albedo;
color = vec4(light + ambient, 1);
}
""",
{
'in_vert' : 0,
})
[16]:
#render the scene
render_with_ssao()
# display the texture
w.bind_framebuffer('FRAMEBUFFER', None)
w.disable(depth_test=True)
w.clear()
w.use_program(final_prog)
w.bind_vertex_array(screen_vao)
w.uniform('u_color', np.array([0], dtype=np.int32))
w.uniform('u_normal', np.array([2], dtype=np.int32))
w.uniform('u_ssao', np.array([5], dtype=np.int32))
w.uniform('u_lightDir', np.array([0,0.707,0.707], dtype=np.float32))
w.draw_arrays('TRIANGLES',0, 6)
w.execute_commands()
w
[16]:
Mix SSAO with shadow¶
use the same concepts as in the shadow example to add a shadow with the ssao
[17]:
shadow_prog = w.create_program_ext(
"""#version 300 es
uniform mat4 u_lightProjection;
uniform mat4 u_world;
in vec3 in_vert;
void main() {
gl_Position = u_lightProjection * u_world * vec4(in_vert, 1.0);
}
"""
,
"""#version 300 es
precision highp float;
out vec4 f_color;
void main() {
f_color = vec4(1, 0.1, 0.1, 1.0);
}
""",
{'in_vert' : 0})
[18]:
shadow_buffer = w.create_framebuffer()
w.bind_framebuffer('FRAMEBUFFER', shadow_buffer)
shadow_texture = w.create_texture()
w.active_texture(6)
w.bind_texture('TEXTURE_2D', shadow_texture)
w.tex_image_2d('TEXTURE_2D', 0, 'DEPTH_COMPONENT32F', 512, 512, 0, 'DEPTH_COMPONENT', 'FLOAT', None)
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MAG_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_MIN_FILTER', 'NEAREST')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE')
w.tex_parameter('TEXTURE_2D', 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE')
w.framebuffer_texture_2d('FRAMEBUFFER', 'DEPTH_ATTACHMENT', 'TEXTURE_2D', shadow_texture, 0)
w.bind_framebuffer('FRAMEBUFFER', None)
w.execute_commands(execute_once=True)
[19]:
def ortho(width, height, near, far):
A = 1. / width
B = 1. / height
C = -(far + near) / (far - near)
D = -2. / (far - near)
return np.array([
[A, 0, 0, 0],
[0, B, 0, 0],
[0, 0, D, C],
[0, 0, 0, 1]
], dtype=np.float32)
# light matrix on top looking down
light_matrix = np.eye(4, dtype=np.float32)
light_matrix[:3, 3] = np.array([0,20,20])
light_matrix[:3, 1] = np.array([0,0.707,-0.707])
light_matrix[:3, 2] = np.array([0,0.707,0.707])
inverse_light_dir = light_matrix[:3, 2] * -1
light_matrix = np.linalg.inv(light_matrix)
bias = np.array(
[[0.5, 0.0, 0.0, 0.5],
[0.0, 0.5, 0.0, 0.5],
[0.0, 0.0, 0.5, 0.5],
[0.0, 0.0, 0.0, 1.0]], dtype=np.float32)
# orthographic shadow
light_ortho = ortho(8,8, 10.0, 40.0)
light_ortho_projection = np.dot(light_ortho, light_matrix)
light_ortho_reprojection = np.dot(bias, light_ortho_projection)
[20]:
final_shadow_prog = w.create_program_ext(
"""#version 300 es
in vec2 in_vert;
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform vec3 u_lightDir;
out vec3 v_lightDir;
void main() {
gl_Position = vec4(in_vert, 0, 1);
v_lightDir = (u_viewMatrix * vec4(u_lightDir, 0)).xyz;
}
"""
,
"""#version 300 es
precision highp float;
//the ViewBlock that is automatically filled by ipywebgl
layout(std140) uniform ViewBlock
{
mat4 u_cameraMatrix; //the camera matrix in world space
mat4 u_viewMatrix; //the inverse of the camera matrix
mat4 u_projectionMatrix; //the projection matrix
mat4 u_viewProjectionMatrix; //the projection * view matrix
};
uniform sampler2D u_position;
uniform sampler2D u_color;
uniform sampler2D u_normal;
uniform sampler2D u_ssao;
uniform sampler2D u_shadowmap;
uniform mat4 u_lightProjection;
uniform float u_bias;
in vec3 v_lightDir;
out vec4 color;
void main() {
vec3 position = texelFetch(u_position, ivec2(gl_FragCoord.xy), 0).rgb;
vec3 albedo = texelFetch(u_color, ivec2(gl_FragCoord.xy), 0).rgb;
vec3 normal = texelFetch(u_normal, ivec2(gl_FragCoord.xy), 0).rgb;
float ssao = texelFetch(u_ssao, ivec2(gl_FragCoord.xy), 0).r;
vec4 world_pos = u_cameraMatrix * vec4(position,1);
vec4 shadowcoord = u_lightProjection * world_pos;
vec3 shadow = shadowcoord.xyz / shadowcoord.w;
float currentDepth = shadow.z + u_bias;
bool inRange =
shadow.x >= 0.0 &&
shadow.x <= 1.0 &&
shadow.y >= 0.0 &&
shadow.y <= 1.0;
float projectedDepth = texture(u_shadowmap, shadow.xy).r;
float shadowLight = (inRange && projectedDepth < currentDepth) ? 0.0 : 1.0;
float ndotl = clamp( dot(v_lightDir, normal), 0.0, 1.0);
float gradient_dot = (u_cameraMatrix * vec4(normal, 0)).y;
vec3 ambient = mix(vec3(.3,.2,.4) , vec3(.5,.8,.8), gradient_dot) *.5 * ssao * ssao * ssao;
vec3 light = vec3(ndotl,ndotl,ndotl) * .5 *shadowLight ;
color = vec4(albedo * (light + ambient), 1);
}
""",
{
'in_vert' : 0,
})
[21]:
# draw shadow map
w.enable(depth_test=True)
w.bind_framebuffer('FRAMEBUFFER', shadow_buffer)
w.viewport(0,0,512,512)
w.clear()
w.use_program(shadow_prog)
w.uniform_matrix('u_lightProjection', light_ortho_projection.T)
render_scene()
w.viewport(0,0,w.width,w.height)
# draw all buffers
render_with_ssao()
# display the texture
w.bind_framebuffer('FRAMEBUFFER', None)
w.disable(depth_test=True)
w.clear()
w.use_program(final_shadow_prog)
w.bind_vertex_array(screen_vao)
w.uniform('u_color', np.array([0], dtype=np.int32))
w.uniform('u_position', np.array([1], dtype=np.int32))
w.uniform('u_normal', np.array([2], dtype=np.int32))
w.uniform('u_ssao', np.array([5], dtype=np.int32))
w.uniform('u_shadowmap', np.array([6], dtype=np.int32))
w.uniform('u_bias', np.array([-0.02], dtype=np.float32))
w.uniform('u_lightDir', np.array([0,0.707,0.707], dtype=np.float32))
w.uniform_matrix('u_lightProjection', light_ortho_reprojection.T)
w.draw_arrays('TRIANGLES',0, 6)
w.execute_commands()
w
[21]:
[ ]: