?
?
<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>液體玻璃效果演示</title><style>body {margin: 0;overflow: hidden;background-color: #000;}canvas {display: block;width: 100vw;height: 100vh;}/* 控制面板 */#controls {position: absolute;top: 10px;left: 10px;padding: 10px;background-color: rgba(0, 0, 0, 0.5);color: white;font-family: sans-serif;border-radius: 5px;user-select: none;}#controls label {cursor: pointer;}</style></head><body><canvas id="glslCanvas"></canvas><imgid="backgroundImage"src="https://pic2.zhimg.com/v2-a53c740141ab75eb4fe16af3ef8c35c5_r.jpg"crossorigin="anonymous"style="display: none"/><div id="controls"><label><input type="checkbox" id="useCircleCheckbox" />使用圓形</label></div><script id="vertex-shader" type="x-shader/x-vertex">// 將頂點位置傳遞出去,這樣片段著色器就可以在構成平面的兩個三角形上運行attribute vec2 a_position;void main() {gl_Position = vec4(a_position, 0.0, 1.0);}</script><script id="fragment-shader" type="x-shader/x-fragment">precision mediump float; // 為浮點數設置中等精度// 從 JavaScript 傳入的變量uniform vec3 iResolution;uniform float iTime;uniform vec4 iMouse;uniform vec3 iImageResolution;uniform sampler2D iImage1;uniform float useCircle;// 已適配 WebGL ---vec2 R;const float PI = 3.14159265;// 創建旋轉矩陣mat2 Rot(float a) {float c = cos(a);float s = sin(a);return mat2(c, -s, s, c);}// 像素歸一化處理float PX(float a) {return a / R.y;}// 矩形距離場float Box(vec2 p, vec2 b) {vec2 d = abs(p) - b;return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);}// 圓形距離場float Circle(vec2 p, float r) {return length(p) - r;}// 根據 useCircle 選擇形狀(圓形或圓角矩形)float Shape(vec2 p, vec2 b, float r) {return useCircle > 0.5 ? Circle(p, r) : Box(p, b);}// 液體玻璃效果vec4 LiquidGlass(vec2 uv, float direction, float quality, float size) {vec2 radius = size / R;vec4 color = texture2D(iImage1, uv);float d_step = PI / direction; // 方向步長float i_step = 1.0 / quality; // 質量步長float d = 0.0;// 循環被展開以兼容一些舊的GPUfor (int j = 0; j < 10; j++) {if (float(j) >= direction) break;float i = i_step;for (int k = 0; k < 10; k++) {if (float(k) >= quality) break;color += texture2D(iImage1, uv + vec2(cos(d), sin(d)) * radius * i);i += i_step;}d += d_step;}color /= quality * direction + 1.0; // 歸一化return color;}// 形狀扭曲效果vec4 Distortion(vec2 uv) {float shape = Shape(uv, vec2(PX(50.0)), PX(50.0));float shapeShape = smoothstep(PX(1.5), 0.0, shape - PX(50.0)); // 形狀平滑過渡float shapeDisp = smoothstep(PX(75.0), 0.0, shape - PX(25.0)); // 邊框寬度float shapeLight = shapeShape * smoothstep(0.0, PX(20.0), shape - PX(40.0)); // 光照強度return vec4(shapeShape, shapeDisp, shapeLight, 0.0);}void main() {R = iResolution.xy;vec2 uv = gl_FragCoord.xy / R; // 歸一化UV坐標vec2 st = (gl_FragCoord.xy - 0.5 * R) / R.y; // 屏幕空間坐標vec2 M = iMouse.xy == vec2(0.0) ? vec2(0.0) : (iMouse.xy - 0.5 * R) / R.y; // 鼠標位置// 如果鼠標沒有移動過,則將效果定位在中心if (iMouse.x == 0.0 && iMouse.y == 0.0) {M = vec2(0.0);}vec4 dist = Distortion(st - M); // 計算扭曲效果vec2 uv2 = uv;uv2 *= 0.5 + 0.5 * smoothstep(0.5, 1.0, dist.y); // 縮放UVvec3 col = mix(vec3(0.0), // 透明黑色背景0.2 + LiquidGlass(uv2, 10.0, 10.0, 5.0).rgb * 0.7, // 應用液體玻璃效果dist.x); // 根據圖標形狀混合col += dist.z * 0.9 + dist.w; // 添加圖標光照和圖案// 應用陰影效果col *= 1.0 - 0.2 * smoothstep(PX(80.0), 0.0, Shape(st - M + vec2(0.0, PX(40.0)), vec2(PX(50.0)), PX(50.0)));// 使用 dist.x 控制透明度:圖標區域不透明,其他區域透明float alpha = dist.x;gl_FragColor = vec4(col, alpha); // 返回最終顏色和透明度}</script><script>const canvas = document.getElementById('glslCanvas');const gl = canvas.getContext('webgl');if (!gl) {alert('抱歉,您的瀏覽器不支持 WebGL。');}// 獲取 DOM 元素const backgroundImage = document.getElementById('backgroundImage');const useCircleCheckbox = document.getElementById('useCircleCheckbox');// 創建著色器程序function createShader(gl, type, source) {const shader = gl.createShader(type);gl.shaderSource(shader, source);gl.compileShader(shader);const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);if (success) {return shader;}console.error(gl.getShaderInfoLog(shader));gl.deleteShader(shader);}const vertexShaderSource = document.getElementById('vertex-shader').text;const fragmentShaderSource =document.getElementById('fragment-shader').text;const vertexShader = createShader(gl,gl.VERTEX_SHADER,vertexShaderSource);const fragmentShader = createShader(gl,gl.FRAGMENT_SHADER,fragmentShaderSource);function createProgram(gl, vertexShader, fragmentShader) {const program = gl.createProgram();gl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);gl.linkProgram(program);const success = gl.getProgramParameter(program, gl.LINK_STATUS);if (success) {return program;}console.error(gl.getProgramInfoLog(program));gl.deleteProgram(program);}const program = createProgram(gl, vertexShader, fragmentShader);gl.useProgram(program);// 準備數據 獲取 uniform 和 attribute 的位置const positionAttributeLocation = gl.getAttribLocation(program,'a_position');const resolutionUniformLocation = gl.getUniformLocation(program,'iResolution');const timeUniformLocation = gl.getUniformLocation(program, 'iTime');const mouseUniformLocation = gl.getUniformLocation(program, 'iMouse');const imageResolutionUniformLocation = gl.getUniformLocation(program,'iImageResolution');const imageSamplerUniformLocation = gl.getUniformLocation(program,'iImage1');const useCircleUniformLocation = gl.getUniformLocation(program,'useCircle');// 創建一個緩沖區來放置一個覆蓋整個畫布的矩形const positionBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);const positions = [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1];gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(positions),gl.STATIC_DRAW);gl.enableVertexAttribArray(positionAttributeLocation);gl.vertexAttribPointer(positionAttributeLocation,2,gl.FLOAT,false,0,0);// 設置紋理let imageTexture;backgroundImage.onload = function () {imageTexture = gl.createTexture();gl.bindTexture(gl.TEXTURE_2D, imageTexture);// 設置紋理參數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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);// 將圖片數據上傳到紋理gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,backgroundImage);// 設置圖像分辨率 uniformgl.uniform3f(imageResolutionUniformLocation,backgroundImage.width,backgroundImage.height,0);// 啟動渲染requestAnimationFrame(render);};// 如果圖片已經加載完成 (例如從緩存加載)if (backgroundImage.complete) {backgroundImage.onload();}// 渲染循環let startTime = Date.now();let mouseX = 0;let mouseY = 0;function render(time) {// 調整畫布大小以匹配顯示大小const displayWidth = gl.canvas.clientWidth;const displayHeight = gl.canvas.clientHeight;if (gl.canvas.width !== displayWidth ||gl.canvas.height !== displayHeight) {gl.canvas.width = displayWidth;gl.canvas.height = displayHeight;gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);}gl.clearColor(0, 0, 0, 0);gl.clear(gl.COLOR_BUFFER_BIT);// 更新 uniformsgl.uniform3f(resolutionUniformLocation,gl.canvas.width,gl.canvas.height,1.0);gl.uniform1f(timeUniformLocation, (Date.now() - startTime) * 0.001);gl.uniform4f(mouseUniformLocation,mouseX,gl.canvas.height - mouseY,0,0); // y坐標需要翻轉gl.uniform1f(useCircleUniformLocation,useCircleCheckbox.checked ? 1.0 : 0.0);// 綁定紋理gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D, imageTexture);gl.uniform1i(imageSamplerUniformLocation, 0);// 繪制gl.drawArrays(gl.TRIANGLES, 0, 6);// 請求下一幀requestAnimationFrame(render);}// 事件監聽window.addEventListener('mousemove', (e) => {mouseX = e.clientX;mouseY = e.clientY;});</script></body>
</html>