效果展示
该项目相比canvas和css实现
- 性能方面较高
- 容易实现的边缘模糊效果
- 不得不说css(box-shadow)挺香的
- 最重要的是可以实现更多有意思的效果,基于你的想象力
相关说明
- 本项目使用gl-renderer作为webgl基础库来简化代码,关于webgl原生js编写请自行查阅资料
- 借于GPU的并发性,主要利用shader的像素化处理来实现
一. 初始化canvas
const canvasWidth = 1450;
const canvasHeight = 828;
const canvas = document.querySelector("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
//存储偏移量 计算探照灯坐标时使用
const canvasOffsetX = canvas.offsetLeft;
const canvasOffsetY = canvas.offsetTop;
二. 监听鼠标移动并记录坐标
//初始化探照灯位置(纹理)
let curCanvasPosPercent = {
x: canvasWidth / 2,
y: canvasHeight / 2,
};
//这边可以依据个人情况适当节流
window.addEventListener("mousemove", (e) => {
curCanvasPosPercent.x = Math.max(Math.min(e.pageX - canvasOffsetX, canvasWidth), 0);
curCanvasPosPercent.y = canvasHeight - Math.max(Math.min(e.pageY - canvasOffsetY, canvasHeight), 0);
});
这一步主要是为了记录探照点坐标,当然也可以不依据鼠标位置,根据js自动变换鼠标位置
三. 初始化Webgl
(async function () {
const renderer = new GlRenderer(canvas);
//分别加载顶点着色器和片元着色器初始化 program
const program = await renderer.load("fragmentShader.frag", ["vertexShader.frag"]);
renderer.useProgram(program);
//加载纹理
const texture = await renderer.loadTexture("xiaowu.png");
renderer.uniforms.tMap = texture;
//将画布的大小传给shader 用于像素渲染计算
renderer.uniforms.canvasWidth = canvasWidth;
renderer.uniforms.canvasHeight = canvasHeight;
//设置顶点数据 顶点索引 uv坐标
renderer.setMeshData([
{
positions: [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1],
],
attributes: {
uv: [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
],
},
cells: [
[0, 1, 2],
[2, 0, 3],
],
},
]);
renderer.render();
function update() {
//在每一帧获取最新坐标
renderer.uniforms.xPot = curCanvasPosPercent.x;
renderer.uniforms.yPot = curCanvasPosPercent.y;
requestAnimationFrame();
}
update();
})();
这一步主要就是初始化webgl program 并且在每一帧更新探照灯的x,y坐标
关于gl-renderer的相关api自行查阅文档
四. 顶点着色器
//a_vertexPosition 是 GlRenderer 默认顶点变量
attribute vec2 a_vertexPosition;
attribute vec2 uv;
varying vec2 vUv;
void main(){
vUv=uv;
//vec2 -> vec4
gl_Position=vec4(a_vertexPosition,1,1);
}
这一步主要就是接收js传过来的顶点坐标 并将纹理坐标传递给片元着色器
五. 片元着色器
#ifdef GL_ES
precision highp float;
#endif
uniform sampler2D tMap;
uniform float canvasWidth;
uniform float canvasHeight;
uniform float xPot;
uniform float yPot;
varying vec2 vUv;
void main(){
//计算实际像素距离
float d=distance(vec2(vUv.x*canvasWidth,vUv.y*canvasHeight),vec2(xPot*canvasWidth,yPot*canvasHeight));
//平滑阶梯函数 分界部分产生模糊渐变 抗锯齿
float smoothValue=smoothstep(d,d+30.0,250.0);
vec4 color=texture2D(tMap,vUv);
gl_FragColor.rgb=smoothValue*color.rgb;
gl_FragColor.a=1.0;
}
核心代码分析:
- 通过distance内置函数计算每个片元坐标相对于探照灯中心的的距离(注意:这里的坐标计算是用的像素坐标而不是纹理(0,1)坐标,因为纹理坐标如果canvas宽高不一致的话,产生的探照灯就是椭圆了)
- smoothstep函数的介绍
- smoothstep相比step(0,1)阶梯函数,两个值得中间多了过渡的部分,产生了非0,1值,我们就利用这个平滑特性,实现探照灯边缘模糊处理
六. 扩展
- 通过了解探照灯的案例,我们可以利用webgl对图片处理的并发性,实现更多有意思的效果,并且这不会带来过多的性能压力。
完整代码
github:github.com/hyy126/shad…
如果觉得该作品对您有帮助的话,欢迎star