function LightBuffer(regl, depth) {
this.diffuse = regl.texture({ format: 'rgba', type: 'float', wrap: 'clamp' });
this.specular = regl.texture({ format: 'rgba', type: 'float', wrap: 'clamp' });
this.depth = depth || regl.renderbuffer({ format: 'depth stencil', radius: 1 });
this.destroyDepth = (depth === undefined);
this.framebuffer = regl.framebuffer({
color: [ this.diffuse, this.specular ],
depthStencil: this.depth
});
this.dispose = function() {
this.framebuffer.destroy();
this.diffuse.destroy();
this.specular.destroy();
if (this.destroyDepth) this.depth.destroy();
};
const command = {
framebuffer: this.framebuffer,
cull: {
enable: true,
face: 'back'
},
depth: {
enable: true,
mask: false,
func: 'less'
},
attributes: {
xy: [[-1, -1], [1, 1], [-1, 1], [1, -1]]
},
uniforms: {
'gbuffer.color': regl.prop('gbuffer.color'),
'gbuffer.normal_r': regl.prop('gbuffer.normal'),
'gbuffer.data': regl.prop('gbuffer.data'),
depthbuffer: regl.prop('depthbuffer.color'),
screenToView: (context, props) => { return glm.mat4.invert([], context.projection); },
viewToWorld: (context, props) => { return glm.mat4.invert([], context.view); },
globalIllumination: regl.prop('environment.radiance')
},
primitive: 'triangles',
elements: [[0, 1, 2], [0, 3, 1]],
vert:
`precision mediump float;
attribute vec2 xy;
varying vec2 fragCoords;
varying vec2 fragUV;
void main()
{
fragCoords = xy;
fragUV = (xy + vec2(1.0)) / 2.0;
gl_Position = vec4(xy, 0.0, 1.0);
}`,
frag:
`#extension GL_EXT_draw_buffers : require
precision mediump float;
struct GBuffer
{
sampler2D color;
sampler2D normal_r;
sampler2D data;
};
struct PointLight
{
vec3 position;
vec3 color;
float intensity;
};
const float PI = 3.14159;
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return num / denom;
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float num = NdotV;
float denom = NdotV * (1.0 - k) + k;
return num / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
float fRR(float roughness, vec3 L, vec3 V, vec3 N)
{
float NdotL = max(dot(N, L), 0.0);
float NdotV = max(dot(N, V), 0.0);
float HdotL = max(dot(normalize(L + V), L), 0.0);
float FL = max(pow(1.0 - NdotL, 5.0), 0.0);
float FV = max(pow(1.0 - NdotV, 5.0), 0.0);
float RR = 2.0 * roughness * HdotL * HdotL;
return RR * (FL + FV + FL * FV * (RR - 1.0));
}
vec3 fS(float roughness, vec3 L, vec3 V, vec3 N)
{
vec3 H = normalize(L + V);
vec3 F0 = vec3(0.04);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = numerator / max(denominator, 0.001);
return specular;
}
varying vec2 fragCoords;
varying vec2 fragUV;
uniform GBuffer gbuffer;
uniform sampler2D depthbuffer;
uniform vec3 eye;
uniform mat4 projection, view, screenToView, viewToWorld;
const int numLights = 5;
uniform samplerCube globalIllumination;
uniform PointLight lights[numLights];
uniform samplerCube shadowmaps[numLights];
void main()
{
vec4 color = texture2D(gbuffer.color, fragUV);
vec4 normal_r = texture2D(gbuffer.normal_r, fragUV);
vec4 data = texture2D(gbuffer.data, fragUV);
float depth = texture2D(depthbuffer, fragUV).r;
// No pretexturing (for materials that have implicity scattering in their diffuse texture)
vec3 baseColor = color.a * vec3(1.0) / PI;
// Completely pre-texture (for materials with no scattering)
if (data.w == 0.0) baseColor = baseColor * color.rgb;
// Partially pre-texture. Color will be blended with surrounding pixels during SSS,
// bluring the diffuse texture a bit
if (data.w == 2.0) baseColor = baseColor * sqrt(color.rgb);
float roughness = normal_r.a;
vec3 fragViewPosition = (screenToView * (depth * vec4(fragCoords, 0.0, 1.0))).xyz;
vec3 fragWorldPosition = (viewToWorld * vec4(fragViewPosition, 1.0)).xyz;
vec3 V = normalize(eye - fragWorldPosition);
vec3 N = normal_r.rgb;
float NdotV = max(dot(N, V), 0.0);
float FV = max(pow(1.0 - NdotV, 5.0), 0.0);
vec3 diffuse = vec3(0.0);
vec3 retroreflect = vec3(0.0);
vec3 specular = vec3(0.0);
for (int i = 0; i < numLights; ++i)
{
PointLight light = lights[i];
vec3 L = normalize(light.position - fragWorldPosition);
float r = distance(light.position, fragWorldPosition);
vec3 up = vec3(0.0, 1.0, 0.0);
vec3 right = cross(up, -L);
up = cross(-L, right);
// Quick PCF Shadows from the point lights
float shadow = 0.0;
vec2 sampleDelta = PI / (2.0 * vec2(1024.0));
float bias = max(0.05 * (1.0 - dot(N, L)), 0.005);
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
{
float theta = float(x) * sampleDelta.x;
float phi = float(y) * sampleDelta.y;
// spherical to cartesian (in tangent space)
vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
// tangent space to world
vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * (-L);
float pcfDepth = textureCube(shadowmaps[i], sampleVec).r;
shadow += r - bias < pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
vec3 radiance = light.intensity * pow(light.color, vec3(2.2)) / (4.0 * PI * r * r);
float NdotL = max(dot(N, L), 0.0);
float FL = max(pow(1.0 - NdotL, 5.0), 0.0);
diffuse += baseColor * (1.0 - 0.5 * FL) * (1.0 - 0.5 * FV) * radiance * shadow * NdotL;
retroreflect += baseColor * fRR(roughness, L, V, N) * radiance * shadow * NdotL;
specular += fS(roughness, L, V, N) * radiance * shadow * NdotL;
}
vec3 ambient = textureCube(globalIllumination, N).rgb;
diffuse += baseColor * (1.0 - 0.5 * FV) * ambient;
// These are more complicated to do with image-based lighting, so they are not implemented
// retroreflect += color.rgb / PI * fRR(roughness, N, V, N) * vec3(0.0);
// specular += fS(roughness, N, V, N) * vec3(0.0);
gl_FragData[0] = vec4(diffuse + retroreflect, 1.0);
gl_FragData[1] = vec4(specular, 1.0);
}`,
};
for (let i = 0; i < 5; i++) {
command.uniforms['lights[' + i + '].position'] = (context, props) => {
return props['lights'][i].position;
};
command.uniforms['lights[' + i + '].color'] = (context, props) => {
return hex2rgb(props['lights'][i].color);
};
command.uniforms['lights[' + i + '].intensity'] = (context, props) => {
return props['lights'][i].intensity;
};
command.uniforms['shadowmaps[' + i + ']'] = (context, props) => {
return props['lights'][i].shadowmap;
};
}
this.draw = regl(command);
}