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]:
[ ]: