Created
July 19, 2024 18:09
-
-
Save jeantimex/76ffdca437be2ac9b757940c6c3c903f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>WebGL2 Triangle, Plane, Point Light, and Rotating Shadow</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script> | |
</head> | |
<body> | |
<canvas id="glCanvas" width="800" height="600"></canvas> | |
<script> | |
const vsSource = `#version 300 es | |
in vec4 aPosition; | |
in vec4 aColor; | |
in vec3 aNormal; | |
out vec4 vColor; | |
out vec4 vPositionFromLight; | |
out vec3 vNormal; | |
out vec3 FragPos; | |
uniform mat4 uModelViewMatrix; | |
uniform mat4 uProjectionMatrix; | |
uniform mat4 uLightSpaceMatrix; | |
uniform mat4 uModelMatrix; | |
uniform mat4 uShadowModelMatrix; | |
void main() { | |
FragPos = vec3(uModelMatrix * aPosition); | |
gl_Position = uProjectionMatrix * uModelViewMatrix * aPosition; | |
vPositionFromLight = uLightSpaceMatrix * uShadowModelMatrix * aPosition; | |
vColor = aColor; | |
vNormal = mat3(transpose(inverse(uModelMatrix))) * aNormal; | |
}`; | |
const fsSource = `#version 300 es | |
precision highp float; | |
in vec4 vColor; | |
in vec4 vPositionFromLight; | |
in vec3 vNormal; | |
in vec3 FragPos; | |
out vec4 fragColor; | |
uniform sampler2D uShadowMap; | |
uniform vec3 lightPos; | |
float ShadowCalculation(vec4 fragPosLightSpace) | |
{ | |
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; | |
projCoords = projCoords * 0.5 + 0.5; | |
float closestDepth = texture(uShadowMap, projCoords.xy).r; | |
float currentDepth = projCoords.z; | |
vec3 normal = normalize(vNormal); | |
vec3 lightDir = normalize(lightPos - FragPos); | |
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); | |
float shadow = 0.0; | |
vec2 texelSize = 1.0 / vec2(textureSize(uShadowMap, 0)); | |
for(int x = -1; x <= 1; ++x) | |
{ | |
for(int y = -1; y <= 1; ++y) | |
{ | |
float pcfDepth = texture(uShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; | |
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; | |
} | |
} | |
shadow /= 9.0; | |
if(projCoords.z > 1.0) | |
shadow = 0.0; | |
return shadow; | |
} | |
void main() { | |
float shadow = ShadowCalculation(vPositionFromLight); | |
vec3 lighting = (1.0 - shadow * 0.5) * vColor.rgb; | |
fragColor = vec4(lighting, 1.0); | |
}`; | |
const shadowVsSource = `#version 300 es | |
in vec4 aPosition; | |
uniform mat4 uLightSpaceMatrix; | |
uniform mat4 uShadowModelMatrix; | |
void main() { | |
gl_Position = uLightSpaceMatrix * uShadowModelMatrix * aPosition; | |
}`; | |
const shadowFsSource = `#version 300 es | |
precision highp float; | |
void main() { | |
// gl_FragDepth = gl_FragCoord.z; | |
}`; | |
let gl; | |
let programInfo; | |
let shadowProgramInfo; | |
let buffers; | |
let shadowFramebuffer; | |
let shadowMap; | |
function initGL() { | |
const canvas = document.getElementById('glCanvas'); | |
gl = canvas.getContext('webgl2'); | |
if (!gl) { | |
alert('Unable to initialize WebGL2. Your browser may not support it.'); | |
return; | |
} | |
const shaderProgram = initShaderProgram(gl, vsSource, fsSource); | |
const shadowShaderProgram = initShaderProgram(gl, shadowVsSource, shadowFsSource); | |
programInfo = { | |
program: shaderProgram, | |
attribLocations: { | |
vertexPosition: gl.getAttribLocation(shaderProgram, 'aPosition'), | |
vertexColor: gl.getAttribLocation(shaderProgram, 'aColor'), | |
vertexNormal: gl.getAttribLocation(shaderProgram, 'aNormal'), | |
}, | |
uniformLocations: { | |
projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), | |
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'), | |
lightSpaceMatrix: gl.getUniformLocation(shaderProgram, 'uLightSpaceMatrix'), | |
shadowMap: gl.getUniformLocation(shaderProgram, 'uShadowMap'), | |
modelMatrix: gl.getUniformLocation(shaderProgram, 'uModelMatrix'), | |
shadowModelMatrix: gl.getUniformLocation(shaderProgram, 'uShadowModelMatrix'), | |
lightPos: gl.getUniformLocation(shaderProgram, 'lightPos'), | |
}, | |
}; | |
shadowProgramInfo = { | |
program: shadowShaderProgram, | |
attribLocations: { | |
vertexPosition: gl.getAttribLocation(shadowShaderProgram, 'aPosition'), | |
}, | |
uniformLocations: { | |
lightSpaceMatrix: gl.getUniformLocation(shadowShaderProgram, 'uLightSpaceMatrix'), | |
shadowModelMatrix: gl.getUniformLocation(shadowShaderProgram, 'uShadowModelMatrix'), | |
}, | |
}; | |
buffers = initBuffers(gl); | |
initShadowMap(); | |
gl.enable(gl.DEPTH_TEST); | |
gl.depthFunc(gl.LEQUAL); | |
render(); | |
} | |
function initBuffers(gl) { | |
const positions = [ | |
// Triangle vertices | |
0.0, 1.0, 0.0, | |
-1.0, -1.0, 0.0, | |
1.0, -1.0, 0.0, | |
// Plane vertices | |
-2.0, -1.5, -2.0, | |
2.0, -1.5, -2.0, | |
2.0, -1.5, 2.0, | |
-2.0, -1.5, 2.0, | |
]; | |
const positionBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); | |
const colors = [ | |
// Triangle colors | |
1.0, 0.0, 0.0, 1.0, | |
0.0, 1.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, 1.0, | |
// Plane colors | |
0.5, 0.5, 0.5, 1.0, | |
0.5, 0.5, 0.5, 1.0, | |
0.5, 0.5, 0.5, 1.0, | |
0.5, 0.5, 0.5, 1.0, | |
]; | |
const colorBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); | |
const normals = [ | |
// Triangle normals | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
// Plane normals | |
0.0, 1.0, 0.0, | |
0.0, 1.0, 0.0, | |
0.0, 1.0, 0.0, | |
0.0, 1.0, 0.0, | |
]; | |
const normalBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW); | |
const indices = [ | |
// Triangle | |
0, 1, 2, | |
// Plane | |
3, 4, 5, | |
5, 6, 3 | |
]; | |
const indexBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); | |
return { | |
position: positionBuffer, | |
color: colorBuffer, | |
normal: normalBuffer, | |
indices: indexBuffer, | |
}; | |
} | |
function initShadowMap() { | |
const SHADOW_WIDTH = 4096, | |
SHADOW_HEIGHT = 4096; | |
shadowFramebuffer = gl.createFramebuffer(); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, shadowFramebuffer); | |
shadowMap = gl.createTexture(); | |
gl.bindTexture(gl.TEXTURE_2D, shadowMap); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F, | |
SHADOW_WIDTH, SHADOW_HEIGHT, 0, gl.DEPTH_COMPONENT, gl.FLOAT, null); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, shadowMap, 0); | |
gl.drawBuffers([gl.NONE]); | |
gl.readBuffer(gl.NONE); | |
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { | |
console.error('Framebuffer is not complete'); | |
} | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
} | |
function render() { | |
const SHADOW_WIDTH = 4096, | |
SHADOW_HEIGHT = 4096; | |
// First render pass: render to shadow map | |
gl.bindFramebuffer(gl.FRAMEBUFFER, shadowFramebuffer); | |
gl.viewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); | |
gl.clear(gl.DEPTH_BUFFER_BIT); | |
const lightProjectionMatrix = mat4.create(); | |
const lightViewMatrix = mat4.create(); | |
const lightSpaceMatrix = mat4.create(); | |
// Set up light position and projection | |
const lightPos = [2, 4, -1]; | |
mat4.ortho(lightProjectionMatrix, -10, 10, -10, 10, 1, 20); | |
mat4.lookAt(lightViewMatrix, lightPos, [0, 0, 0], [0, 1, 0]); | |
mat4.multiply(lightSpaceMatrix, lightProjectionMatrix, lightViewMatrix); | |
gl.useProgram(shadowProgramInfo.program); | |
gl.uniformMatrix4fv(shadowProgramInfo.uniformLocations.lightSpaceMatrix, false, lightSpaceMatrix); | |
// Draw scene for shadow map | |
drawScene(shadowProgramInfo, lightSpaceMatrix, true); | |
// Second render pass: render scene with shadows | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
const fieldOfView = 45 * Math.PI / 180; | |
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; | |
const zNear = 0.1; | |
const zFar = 100.0; | |
const projectionMatrix = mat4.create(); | |
mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar); | |
const viewMatrix = mat4.create(); | |
const eye = [0, 2, 5]; | |
const center = [0, 0, 0]; | |
const up = [0, 1, 0]; | |
mat4.lookAt(viewMatrix, eye, center, up); | |
gl.useProgram(programInfo.program); | |
gl.uniformMatrix4fv(programInfo.uniformLocations.projectionMatrix, false, projectionMatrix); | |
gl.uniformMatrix4fv(programInfo.uniformLocations.lightSpaceMatrix, false, lightSpaceMatrix); | |
gl.uniform3fv(programInfo.uniformLocations.lightPos, lightPos); | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, shadowMap); | |
gl.uniform1i(programInfo.uniformLocations.shadowMap, 0); | |
// Draw scene with shadows | |
drawScene(programInfo, viewMatrix, false); | |
requestAnimationFrame(render); | |
} | |
function drawScene(program, viewMatrix, isShadowPass) { | |
const rotation = performance.now() * 0.001; | |
// Triangle model matrix | |
const triangleModelMatrix = mat4.create(); | |
mat4.translate(triangleModelMatrix, triangleModelMatrix, [0.0, 0.5, -2.0]); | |
mat4.rotate(triangleModelMatrix, triangleModelMatrix, rotation, [0, 1, 0]); | |
// Shadow model matrix (without vertical offset) | |
const triangleShadowModelMatrix = mat4.create(); | |
mat4.translate(triangleShadowModelMatrix, triangleShadowModelMatrix, [0.0, -1.0, -2.0]); | |
mat4.rotate(triangleShadowModelMatrix, triangleShadowModelMatrix, rotation, [0, 1, 0]); | |
// Plane model matrix | |
const planeModelMatrix = mat4.create(); | |
mat4.translate(planeModelMatrix, planeModelMatrix, [0.0, 0.0, -2.0]); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); | |
gl.vertexAttribPointer(program.attribLocations.vertexPosition, 3, gl.FLOAT, false, 0, 0); | |
gl.enableVertexAttribArray(program.attribLocations.vertexPosition); | |
if (program.attribLocations.vertexColor !== undefined) { | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color); | |
gl.vertexAttribPointer(program.attribLocations.vertexColor, 4, gl.FLOAT, false, 0, 0); | |
gl.enableVertexAttribArray(program.attribLocations.vertexColor); | |
} | |
if (program.attribLocations.vertexNormal !== undefined) { | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal); | |
gl.vertexAttribPointer(program.attribLocations.vertexNormal, 3, gl.FLOAT, false, 0, 0); | |
gl.enableVertexAttribArray(program.attribLocations.vertexNormal); | |
} | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices); | |
// Draw triangle | |
let modelViewMatrix = mat4.create(); | |
mat4.multiply(modelViewMatrix, viewMatrix, triangleModelMatrix); | |
gl.uniformMatrix4fv(program.uniformLocations.modelViewMatrix, false, modelViewMatrix); | |
gl.uniformMatrix4fv(program.uniformLocations.modelMatrix, false, triangleModelMatrix); | |
gl.uniformMatrix4fv(program.uniformLocations.shadowModelMatrix, false, triangleShadowModelMatrix); | |
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0); | |
// Draw plane | |
modelViewMatrix = mat4.create(); | |
mat4.multiply(modelViewMatrix, viewMatrix, planeModelMatrix); | |
gl.uniformMatrix4fv(program.uniformLocations.modelViewMatrix, false, modelViewMatrix); | |
gl.uniformMatrix4fv(program.uniformLocations.modelMatrix, false, planeModelMatrix); | |
gl.uniformMatrix4fv(program.uniformLocations.shadowModelMatrix, false, planeModelMatrix); | |
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 6); | |
} | |
function initShaderProgram(gl, vsSource, fsSource) { | |
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); | |
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); | |
const shaderProgram = gl.createProgram(); | |
gl.attachShader(shaderProgram, vertexShader); | |
gl.attachShader(shaderProgram, fragmentShader); | |
gl.linkProgram(shaderProgram); | |
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { | |
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram)); | |
return null; | |
} | |
return shaderProgram; | |
} | |
function loadShader(gl, type, source) { | |
const shader = gl.createShader(type); | |
gl.shaderSource(shader, source); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); | |
gl.deleteShader(shader); | |
return null; | |
} | |
return shader; | |
} | |
window.onload = initGL; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment