Skip to content

Instantly share code, notes, and snippets.

@benc-uk
Last active May 8, 2024 13:40
Show Gist options
  • Save benc-uk/9edcefb9453f5b8cabc76ec0ae619252 to your computer and use it in GitHub Desktop.
Save benc-uk/9edcefb9453f5b8cabc76ec0ae619252 to your computer and use it in GitHub Desktop.
<html lang="en">
<head>
<title>Single Page GPU Raytracer - Ben Coleman</title>
</head>
<body style="margin: 0">
<canvas style="width: 100%" width="100" height="50"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twgl.js/4.19.5/twgl.min.js"></script>
<script id="vs" type="x-shader/x-vertex">
#version 300 es
in vec4 position;
void main() { gl_Position = position; }
</script>
<script id="fs" type="x-shader/x-fragment">
#version 300 es
precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
out vec4 fragColor;
struct Sphere { vec3 pos; float rad; vec3 color; };
Sphere scene[4] = Sphere[4](
Sphere(vec3(-2.4, 0.0, 0.0), 1.3, vec3(1.0, 0.2, 0.2)),
Sphere(vec3(1.4, 0.5, 4.8), 0.6, vec3(0.2, 0.8, 0.2)),
Sphere(vec3(0.0, 0.0, 3.0), 1.3, vec3(0.2, 0.2, 0.9)),
Sphere(vec3(0.0, -20002.5, 3.0), 20000.0, vec3(0.8, 0.7, 0.4))
);
float sphereHit(vec3 ro, vec3 rd, Sphere sph) {
vec3 oc = ro - sph.pos;
float b = dot(oc, rd);
float c = dot(oc, oc) - sph.rad * sph.rad;
float h = b * b - c;
if (h < 0.0) return -1.0; else return -b - sqrt(h);
}
void main() {
scene[0].pos.y += cos(u_time*3.0);
scene[1].pos.y += cos(u_time*3.5);
scene[2].pos.y += cos(u_time*1.5);
vec2 screenPos = (gl_FragCoord.xy / u_resolution) * vec2(1.0, 0.5) + vec2(0.0, 0.25);
vec3 ro = vec3(0.0, 0.0, 13.0);
vec3 rd = normalize(vec3(screenPos - 0.5, -1.0));
float minT = 1e9;
int hitIndex = -1;
for (int i = 0; i < scene.length(); i++) {
float t = sphereHit(ro, rd, scene[i]);
if (t > 0.0 && t < minT) {
minT = t;
hitIndex = i;
}
}
vec3 color = vec3(0.0);
vec3 lightPos = vec3(9.0, 13.0, 8.0);
if (hitIndex >= 0) {
vec3 pos = ro + rd * minT;
vec3 normal = normalize(pos - scene[hitIndex].pos);
vec3 lightDir = normalize(lightPos - pos);
float diff = max(dot(normal, lightDir), 0.0);
float specular = pow(max(dot(reflect(-lightDir, normal), -rd), 0.0), 50.0);
float shadowT = 1e9;
for (int i = 0; i < scene.length(); i++) {
if (i == hitIndex) continue;
float t = sphereHit(pos + normal * 0.001, lightDir, scene[i]);
if (t > 0.0 && t < shadowT) {
shadowT = t;
}
}
if (shadowT < 1e8) { diff *= 0.1; specular = 0.0; }
color = scene[hitIndex].color * diff + 0.02 + specular;
}
fragColor = vec4(color, 1.0);
}
</script>
<script>
const gl = document.querySelector('canvas').getContext('webgl2')
const progInfo = twgl.createProgramInfo(gl, ['vs', 'fs'])
gl.useProgram(progInfo.program)
twgl.resizeCanvasToDisplaySize(gl.canvas)
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: [-1, -1, 0, 1, -1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0],
})
twgl.setBuffersAndAttributes(gl, progInfo, bufferInfo)
function render(time) {
twgl.setUniforms(progInfo, {
u_time: time * 0.001,
u_resolution: [gl.canvas.width, gl.canvas.height],
})
twgl.drawBufferInfo(gl, bufferInfo)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment