Space / index.js
ewdlop's picture
Create index.js (#2)
b26b98b verified
raw
history blame
6.38 kB
const canvas = document.getElementById('gl');
const gl = canvas.getContext('webgl');
if (!gl) { alert('此瀏覽器不支援 WebGL'); }
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const w = Math.floor(window.innerWidth * dpr);
const h = Math.floor((window.innerHeight - document.querySelector('.ui').offsetHeight) * dpr);
canvas.width = w; canvas.height = Math.max(h, 2);
canvas.style.width = '100%'; canvas.style.height = '100%';
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
window.addEventListener('resize', resize);
const vertSrc = `
attribute vec2 aPos;
attribute vec2 aUV;
varying vec2 vUV;
void main(){
vUV = aUV;
gl_Position = vec4(aPos, 0.0, 1.0);
}`;
// fragment shader(點質量透鏡公式 β = θ - θ_E² θ / |θ|²)
const fragSrc = `
precision highp float;
varying vec2 vUV;
uniform sampler2D uTex;
uniform vec2 uPosition;
uniform float uThetaE2;
uniform float uRatio;
uniform float uShadowScale;
void main(){
float eps = 1e-6;
vec2 d = vUV - uPosition;
vec2 d_n = d / vec2(uRatio, 1.0);
float r2 = dot(d_n, d_n) + eps;
vec2 beta_n = d_n - (uThetaE2 * d_n / r2);
vec2 sampleUV = beta_n * vec2(uRatio, 1.0) + uPosition;
vec4 res = texture2D(uTex, clamp(sampleUV, 0.0, 1.0));
if (length(d_n) < uShadowScale * sqrt(uThetaE2)) {
res.rgb = vec3(0.0);
}
gl_FragColor = res;
}`;
function compile(type, src){
const sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(sh) || 'shader compile error');
}
return sh;
}
const vs = compile(gl.VERTEX_SHADER, vertSrc);
const fs = compile(gl.FRAGMENT_SHADER, fragSrc);
const prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
throw new Error(gl.getProgramInfoLog(prog) || 'program link error');
}
gl.useProgram(prog);
const quad = new Float32Array([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, 1, 1, 1,
]);
const ibo = new Uint16Array([0,1,2,2,1,3]);
const vbo = gl.createBuffer();
const ebo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, ibo, gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(prog, 'aPos');
const aUV = gl.getAttribLocation(prog, 'aUV');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
gl.enableVertexAttribArray(aUV);
gl.vertexAttribPointer(aUV, 2, gl.FLOAT, false, 16, 8);
const uTex = gl.getUniformLocation(prog, 'uTex');
const uPosition = gl.getUniformLocation(prog, 'uPosition');
const uThetaE2 = gl.getUniformLocation(prog, 'uThetaE2');
const uRatio = gl.getUniformLocation(prog, 'uRatio');
const uShadowScale = gl.getUniformLocation(prog, 'uShadowScale');
gl.uniform1i(uTex, 0);
let state = {
position: { x: 0.5, y: 0.5 },
thetaE2: parseFloat(document.getElementById('thetaE').value),
shadowScale: parseFloat(document.getElementById('shadowScale').value)
};
function updateUniforms(){
const ratio = gl.drawingBufferWidth / gl.drawingBufferHeight;
gl.uniform2f(uPosition, state.position.x, state.position.y);
gl.uniform1f(uThetaE2, state.thetaE2);
gl.uniform1f(uRatio, ratio);
gl.uniform1f(uShadowScale, state.shadowScale);
}
let texture = gl.createTexture();
function useTextureFromImage(img){
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
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);
}
function genStarfield(w=1024, h=512){
const cvs = document.createElement('canvas');
cvs.width = w; cvs.height = h; const ctx = cvs.getContext('2d');
ctx.fillStyle = '#000'; ctx.fillRect(0,0,w,h);
for(let i=0;i<5000;i++){
const x=Math.random()*w, y=Math.random()*h; const s=Math.random()*1.6+0.2;
const a=Math.random()*0.9+0.1; ctx.fillStyle = `rgba(255,255,255,${a})`;
ctx.fillRect(x, y, s, s);
}
return cvs;
}
useTextureFromImage(genStarfield());
const imgInput = document.getElementById('img');
imgInput.addEventListener('change', e => {
const file = e.target.files[0]; if (!file) return;
const img = new Image();
img.onload = () => useTextureFromImage(img);
img.src = URL.createObjectURL(file);
});
let dragging = false;
let animate = true;
const chkAnimate = document.getElementById('animate');
chkAnimate.addEventListener('change', () => animate = chkAnimate.checked);
canvas.addEventListener('mousedown', (e)=>{ dragging = true; move(e); });
window.addEventListener('mouseup', ()=> dragging = false);
window.addEventListener('mousemove', (e)=>{ if (dragging) move(e); });
function move(e){
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
state.position.x = x; state.position.y = 1.0 - y;
}
const thetaEEl = document.getElementById('thetaE');
const shadowEl = document.getElementById('shadowScale');
thetaEEl.addEventListener('input', ()=> state.thetaE2 = parseFloat(thetaEEl.value));
shadowEl.addEventListener('input', ()=> state.shadowScale = parseFloat(shadowEl.value));
document.getElementById('reset').addEventListener('click', ()=>{
state = { position: {x:0.5, y:0.5}, thetaE2: 0.01, shadowScale: 2.598 };
thetaEEl.value = state.thetaE2; shadowEl.value = state.shadowScale; chkAnimate.checked = false; animate = false;
});
resize();
let t0 = performance.now();
function render(now){
if (animate){
const t = (now - t0) * 0.001;
const r = 0.25; const cx = 0.5, cy = 0.55;
state.position.x = cx + r * Math.cos(t*0.3);
state.position.y = cy + r * Math.sin(t*0.3);
state.thetaE2 = 0.01 + 0.005*Math.sin(t*0.7);
}
updateUniforms();
gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);