前言
放大镜是一种透明的光学镜片,经常用于放大视野中的细节。它有一个凸透镜或一个复合透镜,可以通过透镜的曲面来聚焦光线,使得物体在放大镜的另一端显得更大和更清晰。放大镜通常用于读取小字或细节,检查小物品或难以观察的事物,如昆虫、病毒等。
一、放大镜
WebGL可以用于实现放大镜效果,具体实现步骤如下:
-
创建一个WebGL上下文,并设置canvas的宽度和高度。
-
加载需要放大的图片,并创建WebGL纹理对象。可以使用gl.texImage2D函数将图片转换为纹理。
-
编写着色器程序,包括顶点着色器和片元着色器。顶点着色器负责将顶点位置传递给片元着色器。片元着色器负责将纹理坐标传递给gl_FragColor输出颜色。
-
绘制一个矩形,上面贴上纹理。这个矩形的大小应该和放大镜的大小一致。
-
监听鼠标移动事件,获取当前鼠标的位置,并将其转换为纹理坐标。
-
使用gl.scissor函数设置裁剪区域,并使用gl.drawArrays函数绘制放大镜区域。
-
最后使用requestAnimationFrame函数循环更新画面,实现动态放大镜效果。
案例代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../lib/index.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
canvas{
margin: 50px auto 0;
display: block;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
此浏览器不支持canvas
</canvas>
</body>
</html>
<script>
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
// 只传递顶点数据
attribute vec4 aPosition;
attribute vec4 aTex;
varying vec2 vTex;
varying vec4 vPosition;
void main() {
vPosition = aPosition;
gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
vTex = vec2(aTex.x, aTex.y);
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
uniform sampler2D uSampler;
varying vec2 vTex;
uniform vec2 lookAt; // 观察点的坐标
varying vec4 vPosition;
void main() {
vec2 uv = vTex;
float fOpacity = 0.0;
// 1. 判断当前点和 lookAt 的距离
float dis = distance(lookAt, vec2(vPosition));
if (dis > 0.2) {
fOpacity = 0.05;
} else {
fOpacity = 1.0;
vec2 diff = vPosition.xy - lookAt;
uv.x -= diff.x * 0.2;
uv.y += diff.y * 0.2;
}
vec4 color = texture2D(uSampler, uv);
gl_FragColor = vec4(color.xyz * fOpacity, fOpacity);
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aTex = gl.getAttribLocation(program, 'aTex');
const uSampler = gl.getUniformLocation(program, 'uSampler');
const lookAt = gl.getUniformLocation(program, 'lookAt');
const points = new Float32Array([
-0.9, 0.9, 0.0, 1.0,
-0.9, -0.9, 0.0, 0.0,
0.9, 0.9, 1.0, 1.0,
0.9, -0.9, 1.0, 0.0,
])
const buffer = gl.createBuffer();
const BYTES = points.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0);
gl.enableVertexAttribArray(aPosition)
gl.vertexAttribPointer(aTex, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2);
gl.enableVertexAttribArray(aTex)
const img = new Image();
img.onload = function() {
// 创建纹理对象
const texture = gl.createTexture();
// 翻转 图片 Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
// 开启一个纹理单元
gl.activeTexture(gl.TEXTURE0);
// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 处理放大缩小的逻辑
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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)
// 配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
gl.uniform1i(uSampler, 0);
}
img.src = '../assets/content.png'
function draw() {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(draw)
}
draw()
document.onmousemove = (ev) => {
// 坐标
const x = ev.clientX
const y = ev.clientY
const domPosition = ev.target.getBoundingClientRect();
const domx = x - domPosition.left
const domy = y - domPosition.top;
const halfWidth = ctx.offsetWidth / 2
const halfHeight = ctx.offsetHeight / 2
const clickX = (domx - halfWidth) / halfWidth
const clickY = (halfHeight - domy) / halfHeight
gl.uniform2fv(lookAt, [clickX, clickY]);
}
</script>