Spaces:
Running
Running
Create index.js (#2)
Browse files- Create index.js (5ff24d76e55e503c147ead44acec8632ffc7ce8b)
index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const canvas = document.getElementById('gl');
|
| 2 |
+
const gl = canvas.getContext('webgl');
|
| 3 |
+
if (!gl) { alert('此瀏覽器不支援 WebGL'); }
|
| 4 |
+
|
| 5 |
+
function resize() {
|
| 6 |
+
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
| 7 |
+
const w = Math.floor(window.innerWidth * dpr);
|
| 8 |
+
const h = Math.floor((window.innerHeight - document.querySelector('.ui').offsetHeight) * dpr);
|
| 9 |
+
canvas.width = w; canvas.height = Math.max(h, 2);
|
| 10 |
+
canvas.style.width = '100%'; canvas.style.height = '100%';
|
| 11 |
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
| 12 |
+
}
|
| 13 |
+
window.addEventListener('resize', resize);
|
| 14 |
+
|
| 15 |
+
const vertSrc = `
|
| 16 |
+
attribute vec2 aPos;
|
| 17 |
+
attribute vec2 aUV;
|
| 18 |
+
varying vec2 vUV;
|
| 19 |
+
void main(){
|
| 20 |
+
vUV = aUV;
|
| 21 |
+
gl_Position = vec4(aPos, 0.0, 1.0);
|
| 22 |
+
}`;
|
| 23 |
+
|
| 24 |
+
// fragment shader(點質量透鏡公式 β = θ - θ_E² θ / |θ|²)
|
| 25 |
+
const fragSrc = `
|
| 26 |
+
precision highp float;
|
| 27 |
+
varying vec2 vUV;
|
| 28 |
+
uniform sampler2D uTex;
|
| 29 |
+
uniform vec2 uPosition;
|
| 30 |
+
uniform float uThetaE2;
|
| 31 |
+
uniform float uRatio;
|
| 32 |
+
uniform float uShadowScale;
|
| 33 |
+
|
| 34 |
+
void main(){
|
| 35 |
+
float eps = 1e-6;
|
| 36 |
+
vec2 d = vUV - uPosition;
|
| 37 |
+
vec2 d_n = d / vec2(uRatio, 1.0);
|
| 38 |
+
float r2 = dot(d_n, d_n) + eps;
|
| 39 |
+
vec2 beta_n = d_n - (uThetaE2 * d_n / r2);
|
| 40 |
+
vec2 sampleUV = beta_n * vec2(uRatio, 1.0) + uPosition;
|
| 41 |
+
vec4 res = texture2D(uTex, clamp(sampleUV, 0.0, 1.0));
|
| 42 |
+
if (length(d_n) < uShadowScale * sqrt(uThetaE2)) {
|
| 43 |
+
res.rgb = vec3(0.0);
|
| 44 |
+
}
|
| 45 |
+
gl_FragColor = res;
|
| 46 |
+
}`;
|
| 47 |
+
|
| 48 |
+
function compile(type, src){
|
| 49 |
+
const sh = gl.createShader(type);
|
| 50 |
+
gl.shaderSource(sh, src);
|
| 51 |
+
gl.compileShader(sh);
|
| 52 |
+
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
|
| 53 |
+
throw new Error(gl.getShaderInfoLog(sh) || 'shader compile error');
|
| 54 |
+
}
|
| 55 |
+
return sh;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
const vs = compile(gl.VERTEX_SHADER, vertSrc);
|
| 59 |
+
const fs = compile(gl.FRAGMENT_SHADER, fragSrc);
|
| 60 |
+
const prog = gl.createProgram();
|
| 61 |
+
gl.attachShader(prog, vs);
|
| 62 |
+
gl.attachShader(prog, fs);
|
| 63 |
+
gl.linkProgram(prog);
|
| 64 |
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
| 65 |
+
throw new Error(gl.getProgramInfoLog(prog) || 'program link error');
|
| 66 |
+
}
|
| 67 |
+
gl.useProgram(prog);
|
| 68 |
+
|
| 69 |
+
const quad = new Float32Array([
|
| 70 |
+
-1, -1, 0, 0,
|
| 71 |
+
1, -1, 1, 0,
|
| 72 |
+
-1, 1, 0, 1,
|
| 73 |
+
1, 1, 1, 1,
|
| 74 |
+
]);
|
| 75 |
+
const ibo = new Uint16Array([0,1,2,2,1,3]);
|
| 76 |
+
const vbo = gl.createBuffer();
|
| 77 |
+
const ebo = gl.createBuffer();
|
| 78 |
+
|
| 79 |
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
| 80 |
+
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
|
| 81 |
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo);
|
| 82 |
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, ibo, gl.STATIC_DRAW);
|
| 83 |
+
|
| 84 |
+
const aPos = gl.getAttribLocation(prog, 'aPos');
|
| 85 |
+
const aUV = gl.getAttribLocation(prog, 'aUV');
|
| 86 |
+
gl.enableVertexAttribArray(aPos);
|
| 87 |
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
|
| 88 |
+
gl.enableVertexAttribArray(aUV);
|
| 89 |
+
gl.vertexAttribPointer(aUV, 2, gl.FLOAT, false, 16, 8);
|
| 90 |
+
|
| 91 |
+
const uTex = gl.getUniformLocation(prog, 'uTex');
|
| 92 |
+
const uPosition = gl.getUniformLocation(prog, 'uPosition');
|
| 93 |
+
const uThetaE2 = gl.getUniformLocation(prog, 'uThetaE2');
|
| 94 |
+
const uRatio = gl.getUniformLocation(prog, 'uRatio');
|
| 95 |
+
const uShadowScale = gl.getUniformLocation(prog, 'uShadowScale');
|
| 96 |
+
|
| 97 |
+
gl.uniform1i(uTex, 0);
|
| 98 |
+
|
| 99 |
+
let state = {
|
| 100 |
+
position: { x: 0.5, y: 0.5 },
|
| 101 |
+
thetaE2: parseFloat(document.getElementById('thetaE').value),
|
| 102 |
+
shadowScale: parseFloat(document.getElementById('shadowScale').value)
|
| 103 |
+
};
|
| 104 |
+
|
| 105 |
+
function updateUniforms(){
|
| 106 |
+
const ratio = gl.drawingBufferWidth / gl.drawingBufferHeight;
|
| 107 |
+
gl.uniform2f(uPosition, state.position.x, state.position.y);
|
| 108 |
+
gl.uniform1f(uThetaE2, state.thetaE2);
|
| 109 |
+
gl.uniform1f(uRatio, ratio);
|
| 110 |
+
gl.uniform1f(uShadowScale, state.shadowScale);
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
let texture = gl.createTexture();
|
| 114 |
+
function useTextureFromImage(img){
|
| 115 |
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
| 116 |
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
|
| 117 |
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
|
| 118 |
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
| 119 |
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
| 120 |
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
| 121 |
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
function genStarfield(w=1024, h=512){
|
| 125 |
+
const cvs = document.createElement('canvas');
|
| 126 |
+
cvs.width = w; cvs.height = h; const ctx = cvs.getContext('2d');
|
| 127 |
+
ctx.fillStyle = '#000'; ctx.fillRect(0,0,w,h);
|
| 128 |
+
for(let i=0;i<5000;i++){
|
| 129 |
+
const x=Math.random()*w, y=Math.random()*h; const s=Math.random()*1.6+0.2;
|
| 130 |
+
const a=Math.random()*0.9+0.1; ctx.fillStyle = `rgba(255,255,255,${a})`;
|
| 131 |
+
ctx.fillRect(x, y, s, s);
|
| 132 |
+
}
|
| 133 |
+
return cvs;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
useTextureFromImage(genStarfield());
|
| 137 |
+
|
| 138 |
+
const imgInput = document.getElementById('img');
|
| 139 |
+
imgInput.addEventListener('change', e => {
|
| 140 |
+
const file = e.target.files[0]; if (!file) return;
|
| 141 |
+
const img = new Image();
|
| 142 |
+
img.onload = () => useTextureFromImage(img);
|
| 143 |
+
img.src = URL.createObjectURL(file);
|
| 144 |
+
});
|
| 145 |
+
|
| 146 |
+
let dragging = false;
|
| 147 |
+
let animate = true;
|
| 148 |
+
const chkAnimate = document.getElementById('animate');
|
| 149 |
+
chkAnimate.addEventListener('change', () => animate = chkAnimate.checked);
|
| 150 |
+
|
| 151 |
+
canvas.addEventListener('mousedown', (e)=>{ dragging = true; move(e); });
|
| 152 |
+
window.addEventListener('mouseup', ()=> dragging = false);
|
| 153 |
+
window.addEventListener('mousemove', (e)=>{ if (dragging) move(e); });
|
| 154 |
+
|
| 155 |
+
function move(e){
|
| 156 |
+
const rect = canvas.getBoundingClientRect();
|
| 157 |
+
const x = (e.clientX - rect.left) / rect.width;
|
| 158 |
+
const y = (e.clientY - rect.top) / rect.height;
|
| 159 |
+
state.position.x = x; state.position.y = 1.0 - y;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
const thetaEEl = document.getElementById('thetaE');
|
| 163 |
+
const shadowEl = document.getElementById('shadowScale');
|
| 164 |
+
thetaEEl.addEventListener('input', ()=> state.thetaE2 = parseFloat(thetaEEl.value));
|
| 165 |
+
shadowEl.addEventListener('input', ()=> state.shadowScale = parseFloat(shadowEl.value));
|
| 166 |
+
|
| 167 |
+
document.getElementById('reset').addEventListener('click', ()=>{
|
| 168 |
+
state = { position: {x:0.5, y:0.5}, thetaE2: 0.01, shadowScale: 2.598 };
|
| 169 |
+
thetaEEl.value = state.thetaE2; shadowEl.value = state.shadowScale; chkAnimate.checked = false; animate = false;
|
| 170 |
+
});
|
| 171 |
+
|
| 172 |
+
resize();
|
| 173 |
+
let t0 = performance.now();
|
| 174 |
+
function render(now){
|
| 175 |
+
if (animate){
|
| 176 |
+
const t = (now - t0) * 0.001;
|
| 177 |
+
const r = 0.25; const cx = 0.5, cy = 0.55;
|
| 178 |
+
state.position.x = cx + r * Math.cos(t*0.3);
|
| 179 |
+
state.position.y = cy + r * Math.sin(t*0.3);
|
| 180 |
+
state.thetaE2 = 0.01 + 0.005*Math.sin(t*0.7);
|
| 181 |
+
}
|
| 182 |
+
updateUniforms();
|
| 183 |
+
gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT);
|
| 184 |
+
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
|
| 185 |
+
requestAnimationFrame(render);
|
| 186 |
+
}
|
| 187 |
+
requestAnimationFrame(render);
|