const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
const vsSource = `
attribute vec2 aPosition;
varying highp vec2 vUV;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
vUV = aPosition;
}
`;
const fsSource = `
varying highp vec2 vUV;
uniform highp float uTime;
highp float sdSphere(vec3 p, float r) {
return length(p) - r;
}
highp float sdBox(vec3 p, vec3 b) {
vec3 d = abs(p) - b;
return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}
highp float smin(float a, float b, float k) {
float h = max(k - abs(a - b), 0.0) / k;
return min(a, b) - h * h * k * 0.25;
}
highp float map(vec3 p) {
float t = uTime * 0.5;
vec3 p1 = p + vec3(sin(t) * 0.3, 0.0, 0.0);
vec3 p2 = p - vec3(sin(t) * 0.3, 0.0, 0.0);
float sphere = sdSphere(p1, 0.5);
float box = sdBox(p2, vec3(0.4));
return smin(sphere, box, 0.5);
}
highp vec3 calcNormal(vec3 p) {
const float h = 0.001;
const vec2 k = vec2(1, -1);
return normalize(
k.xyy * map(p + k.xyy * h) +
k.yyx * map(p + k.yyx * h) +
k.yxy * map(p + k.yxy * h) +
k.xxx * map(p + k.xxx * h)
);
}
void main() {
vec3 ro = vec3(0.0, 0.0, 2.5);
vec3 rd = normalize(vec3(vUV, -1.5));
float t = 0.0;
for (int i = 0; i < 64; i++) {
vec3 p = ro + rd * t;
float d = map(p);
if (d < 0.001 || t > 10.0) break;
t += d;
}
vec3 color = vec3(0.0);
if (t < 10.0) {
vec3 p = ro + rd * t;
vec3 normal = calcNormal(p);
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(dot(normal, lightDir), 0.0);
float spec = pow(max(dot(reflect(-lightDir, normal), -rd), 0.0), 32.0);
vec3 baseColor = vec3(0.3, 0.6, 1.0);
color = baseColor * diff + vec3(1.0) * spec + vec3(0.1);
}
gl_FragColor = vec4(color, 1.0);
}
`;
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);
return shaderProgram;
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
const posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
const posAttrib = gl.getAttribLocation(shaderProgram, 'aPosition');
const timeUniform = gl.getUniformLocation(shaderProgram, 'uTime');
let time = 0;
function render() {
time += 0.016;
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.vertexAttribPointer(posAttrib, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(posAttrib);
gl.useProgram(shaderProgram);
gl.uniform1f(timeUniform, time);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(render);
}
render();