前言

作为前端开发人员,我们最关注的就是应用的交互体验,而元素背景是最基础的交互体验之一。一般而言,能够使用代码实现的界面,我们都会尽可能减少图片的使用,这主要是有几方面的原因,第一,是图片会消耗更多的带宽,对于移动端或者网络信号较差时的体验不够友好,第二是不够便捷,在使用图片的情况下即使有细微的调整也需要重新做图上传,第三是不够灵活,这一点主要体现在根据不同的条件需要呈现不同的效果。

本文就使用网格背景作为例子,来通过代码实现,这是在可视化课程中新学的内容,这里我做一个总结和复习。网格背景是一种常见的设计元素,它可以为网页增添一种现代感和动态感。要实现网格背景,我们可以使用CSS代码来实现,这类实现方式我在很多文章中都有看到过,但除了使用CSS,我们还可以通过WebGL实现,通过WebGL来实现,还能额外带来一些好处,比如有更好的性能,也能与画布上的其他元素更好地融合。

网格背景

在使用具体的代码实现之前,我们先简单来分析一下网格背景。

从上面的图片中我们能发现,网格背景其实可以看作是一个小的网格图案重复了n遍而形成的一个背景,也就是说这是一个重复图案的背景。

使用CSS实现

在使用WebGL来实现网格背景之前,我们还是先来看一下CSS代码是如何实现的。

在使用CSS代码实现网格背景时,需要用到CSS中的渐变函数,因为渐变函数最终形成的效果类似于图像,所以可以当作图像来使用,也就是说可以应用于background-image属性。渐变函数有三种:线性渐变、径向渐变和圆锥渐变,在这个例子中,由于网格是直线的,所以我们使用线性渐变函数就可以了。

线性渐变的语法比较简单,以下是MDN给出的表达式

<linear-gradient()> =
linear-gradient( [ <linear-gradient-syntax> ] ) <linear-gradient-syntax> =
[ <angle> | to <side-or-corner> ]? , <color-stop-list> <side-or-corner> =
[ left | right ] ||
[ top | bottom ] <color-stop-list> =
<linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]# <linear-color-stop> =
<color> <length-percentage>? <linear-color-hint> =
<length-percentage> <length-percentage> =
<length> |
<percentage>

我们主要向线性渐变函数传递三类参数:第一是渐变的方向,第二是初始色值,第三是最终色值。如果要控制得更细致,我们还可以通过上述公式中的length或者percentage来控制某个色值的范围,或者设置多个阶梯色值。

现在我们就可以通过以下CSS代码,来实现网格背景的设置。

.grid-bg {
width: 300px;
height: 300px;
background-image: linear-gradient(to right, transparent 95%, #ccc 0),
linear-gradient(to bottom, transparent 95%, #ccc 0);
background-size: 8px 8px, 8px 8px;
}

以上代码中首先使用background-size属性指定了一个网格图案的大小,然后我们知道background-repeat属性的默认值是repeat,所以会重复这个图案来铺满整个元素的背景,最后我们来看background-image这个属性,我们使用了线性渐变函数来给这个属性赋值,可以看到这里用了两个渐变函数。

第一个渐变函数,设置的渐变方向是to right,也就是自左向右进行渐变,初始色值是transparent也就是透明色,这里使用了百分数95%来控制透明色的范围,也就是说从0%的位置开始到95%的位置,都是透明色,然后最终色值是#ccc也就是灰色,我们想要95%到100%的范围都是#ccc的灰色,可以直接简写为0。

第二个渐变函数也是类似的。

在这两个渐变函数的作用下,最终形成的图案就是一个小网格,右边5%的宽度和下边5%的宽度由灰色填充;然后在background-repeat默认值repeat的作用下,就会铺满对应元素。

使用WebGL实现

那么既然CSS已经可以实现网格来替代图片了,为什么又要使用WebGL来实现呢?所以那肯定是WebGL的实现有其他的优势,首先是直接调用GPU的话,无论是有多少重复图案都能一次完成,理论上没有性能瓶颈,其次是能满足更多类型的需求,比如能使网格随着Canvas中的其他元素一起缩放。

那么在WebGL中要如何去实现网格背景呢?我们可以通过图案的使用来实现。

基础页面

首先最基础的,我们先在页面上放置一个Canvas。

<canvas width="512" height="512"></canvas>

操作WebGL

接着就可以开始操作WebGL去完成图案的绘制。

在本次实现中,使用了一个基础库gl-renderer来简化WebGL的操作,让我们可以将重心放在数据提供和编写shader上。

以下是操作WebGL的代码:

// 第一步:创建Renderer对象
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas);
// 第二步:创建并启用WebGL程序
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
// 第三步:设置uniform变量
renderer.uniforms.rows = 32; // 64; // 每一行显示多少个网格(在片元着色器中使用)
// 第四步:将顶点数据送入缓冲区
renderer.setMeshData([{
positions: [ // 顶点(覆盖整个画布)
[-1, -1], // 左下
[-1, 1], // 左上
[1, 1], // 右上
[1, -1] // 右下
],
attributes: {
uv: [ // 纹理坐标(坐标系:左下角[0,0] 右上角[1,1])
[0, 0], // 左下
[0, 1], // 左上
[1, 1], // 右上
[1, 0] // 右下
]
},
cells: [ // 顶点索引(三角剖分):将矩形剖分成两个三角形
[0, 1, 2],
[2, 0, 3]
]
}]);
// 第五步:执行渲染
renderer.render();

以上代码不难理解,就是向WebGL传递数据,并执行渲染。主要有以下几个操作:

  • 首先在初始化阶段,根据GLSL代码和Canvas的WebGL上下文创建WebGL程序;

  • 接着就是传递数据,包括uniform常量、顶点数据和纹理坐标。其中纹理坐标的使用与图案有关。

  • 然后是设置三角剖分的顶点索引。三角剖分简单来说就是将一个多边形使用多个三角形组合拼接来表示,可以参考下图来理解。

  • 最后一步就是渲染。

GLSL代码

现在我们来看关键的GLSL代码。

// 顶点着色器
attribute vec2 a_vertexPosition;
attribute vec2 uv;
varying vec2 vUv; void main() {
gl_PointSize = 1.0;
vUv = uv;
gl_Position = vec4(a_vertexPosition, 1, 1);
}

以上是顶点着色器,这段代码比较基础,找到要处理的像素点,其余的就是将纹理坐标传递给片元着色器。

在网格背景的实现中,片元着色器是比较关键的一环,现在我们就来看片元着色器的实现代码:

#ifdef GL_ES
precision mediump float;
#endif varying vec2 vUv; // 由顶点着色器传来的uv属性
uniform float rows; void main() {
// st:保存像素点对应纹理坐标的小数部分
vec2 st = fract(vUv * rows); // fract函数是一个用于获取向量中小数部分的函数
float d1 = step(st.x, 0.9); // step:阶梯函数。当step(a,b)中的b < a时,返回0;当b >= a时,返回1。
float d2 = step(0.1, st.y); // 根据d1*d2的值,决定使用哪个颜色来绘制当前像素。
// st.x <= 0.9 且 st.y >= 0.1时,d1*d2=1, 否则为0
gl_FragColor.rgb = mix(vec3(0.8), vec3(1.0), d1 * d2);
gl_FragColor.a = 1.0;
}

在编写shader时,要理解其中的GLSL代码不是太容易,因为对于课程中给出的解释也并不太能理解,所以我也是花了一点时间去思考。

首先就是这个fract函数的调用,课程中给出的说法是:它可以帮助我们获得重复的 rows 行 rows 列的值 st

当一个数从 0~1 周期性变化的时候, 我们只要将它乘以整数 N,然后再用 fract 取小数,就能得到 N 个周期的数值。

但我感觉这种说法并不太直观,至少我看下来觉得还是一头雾水,当然也可能是我的理解能力有限,着色器程序理论上是批量执行,那么这段代码针对某个待处理的像素点是什么含义呢?因为暂时我想先自己探索一番,所以我也没有参考其他资料。

经过多日思考,以下是我目前的几点理解:

  1. 首先是传递的纹理坐标,我们可以理解是一个单元的坐标,坐标的范围是一个正方形。

  2. 然后vUv * rows可以看作是相当于把纹理坐标在画布上的纵向放大rows倍,又因为纹理坐标范围的原因,同时相当于在横向上也放大了rows倍。

  3. 接着就得到了当前待处理的像素点映射到纹理坐标上的位置,后续会根据这个像素点的纹理坐标去计算设置。

  4. st保存了像素点对应纹理坐标的小数部分。

  5. step是阶梯函数,接收两个入参a和b,根据a和b的大小关系返回0或1;当b < a时,返回0;当b >= a时,返回1。

    两个step函数的执行后,我们就可以得到:

    st.x <= 0.9 且 st.y >= 0.1时,d1*d2=1, 否则为0

  6. 最后我们调用mix得到最终的色值。

    mix(a, b, c)是一个线性插值函数。a和b是两个色值,当c为0时,返回a;当c为1时,mix函数返回b。

    在这里vec3(0.8)是一个灰色色值,vec3(1.0)是白色;显而易见当d1或d2为0时,也就是st.x > 0.9 或 st.y < 0.1时,像素点渲染为灰色,否则渲染为白色。

具体渲染结果可参考下图(随手画的比较潦草),最终网格的数量由rows决定:

总之我理解的就是,在片元着色器中,我们主要去计算某个像素点的色值,在这个例子中,就是映射到纹理坐标并根据纹理坐标计算得到一个色值,最终像素点会被渲染为这个色值。

这段代码中,纹理坐标的小数部分是周期性重复的,所以就可以得到重复的图案,最终形成网格背景。

总结

最后总结一下吧,要理解WebGL代码,有时候还是需要转换一下思路,其实我也并不太确定自己的理解是不是对的,但我觉得有时候学习新的东西,就是给自己一个打开思路的机会,最重要的还是自己去思考理解,让自己得到新的启发。

可视化学习:使用WebGL实现网格背景的更多相关文章

  1. Tensorflow学习笔记3:TensorBoard可视化学习

    TensorBoard简介 Tensorflow发布包中提供了TensorBoard,用于展示Tensorflow任务在计算过程中的Graph.定量指标图以及附加数据.大致的效果如下所示, Tenso ...

  2. css实现网格背景

    只使用一个渐变时,我们能创建的图案并不多,当我们把多个渐变图案组合起来,让他们透过彼此的透明区域显现时,神奇的事情就发生了!我们首先想到的是把水平和水质条纹叠加起来,就可以得到各种各样的网格. 1. ...

  3. 【Visual C++】游戏编程学习笔记之六:多背景循环动画

    本系列文章由@二货梦想家张程 所写,转载请注明出处. 本文章链接:http://blog.csdn.net/terence1212/article/details/44264153 作者:ZeeCod ...

  4. 可视化学习Tensorboard

    可视化学习Tensorboard TensorBoard 涉及到的运算,通常是在训练庞大的深度神经网络中出现的复杂而又难以理解的运算.为了更方便 TensorFlow 程序的理解.调试与优化,发布了一 ...

  5. 第四界css大会 黑魔法-css网格背景、颜色拾取器、遮罩、文字颜色渐变、标题溢出渐变等

    1.css网格背景 <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  6. R语言可视化学习笔记之添加p-value和显著性标记

    R语言可视化学习笔记之添加p-value和显著性标记 http://www.jianshu.com/p/b7274afff14f?from=timeline   上篇文章中提了一下如何通过ggpubr ...

  7. 87、使用TensorBoard进行可视化学习

    1.还是以手写识别为类,至于为什么一直用手写识别这个例子,原因很简单,因为书上只给出了这个类子呀,哈哈哈,好神奇 下面是可视化学习的标准函数 ''' Created on 2017年5月23日 @au ...

  8. Echart可视化学习集合

    一.基本介绍:ECharts是一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表.ECharts最初由百度团队开源,并于2018年初捐赠给Apache ...

  9. Python可视化学习(1):Matplotlib的配置

    Matplotlib是一个优秀的可视化库,它提供了丰富的接口,让Python的可视化落地显得非常容易上手.本系列是本人学习python可视化的学习笔记,主要用于监督自己的学习进度,同时也希望和相关的博 ...

  10. matplotlab可视化学习

    1 使用pip安装 使用 Python 包管理器 pip 来安装 Matplotlib 是一种最轻量级的方式.打开 CMD 命令提示符窗口,并输入以下命令: pip install matplotli ...

随机推荐

  1. echarts去除坐标轴上的x和y轴

    通过 show:false控制手否显示 <!DOCTYPE html> <html lang="en"> <head> <meta cha ...

  2. js循环之map在工作中的使用

    map函数会返回一个全新的数组哈(重要 在实际中很有用) map循环空那个数组时,不会报错的. 使用map的优势 可以返回一个全新的数组 可以用于过滤 ps==>:map里面不要有判断,否者会返 ...

  3. 在Protocol Buffers中导入当前目录中的.proto文件

    在protobuf中导入当前目录中的.proto文件时,可以使用相对路径.相对路径是相对于当前.proto文件所在的目录来引用其他.proto文件. 假设有以下目录结构: my_project/ |- ...

  4. 语义检索系统:基于无监督预训练语义索引召回:SimCSE、Diffcse

    基于无监督预训练语义索引召回:SimCSE.Diffcse 语义索引(可通俗理解为向量索引)技术是搜索引擎.推荐系统.广告系统在召回阶段的核心技术之一.语义索引模型的目标是:给定输入文本,模型可以从海 ...

  5. Linux系统的一些实用操作 [补档-2023-07-30]

    Linux的实用操作 4-1.常用快捷键 强制停止:当某些程序运行时,或者命令输入错误时,可以通过 ctrl + c 来强制结束当前的操作. 退出或登出:当我们要退出某些用户时,或者要退出某些特殊的页 ...

  6. 关于Docker容器中的DNS配置

    Docker: 1.启动时指定: docker run --dns 8.8.8.8 busybox:latest 2.全局配置: vi /etc/docker/daemon.json { " ...

  7. Vite4+Typescript+Vue3+Pinia 从零搭建(1) - 项目初始化

    项目代码同步至码云 weiz-vue3-template 前提准备 1. node版本 Node.js版本 >= 12,如果有老项目需要旧版本的,推荐用 nvm 管理node版本. PS C:\ ...

  8. 【图论】【Matlab】最小生成树之Kruskal算法【贪心思想超详细详解Kruskal算法并应用】

    最小生成树之Kruskal算法 注意:内容学习来自:b站CleverFrank数模算法精讲 导航 前言 实际问题引入 Kruskal算法 整体代码展示 尾声 前言 博主今天给大家带来的是最小生成树中两 ...

  9. Java - CodeForces - 1230A

    题目: Dawid有了 4 包糖果.第 i 包里面有 Ai 个糖果. Dawid想把这四包糖果送给两个朋友,能否让两个朋友收到相同数量的糖果?注意,不能拆开任何一包糖,不能把糖果留给自己或扔掉,四包糖 ...

  10. .NET Core开发实战(第5课:依赖注入:良好架构的起点)--学习笔记(下)

    05 | 依赖注入:良好架构的起点 注册完毕之后,想替换某些组件的某些部分时,可以使用 Replace 和 RemoveAll services.AddSingleton<IOrderServi ...