可视化学习:使用WebGL实现网格背景
前言
作为前端开发人员,我们最关注的就是应用的交互体验,而元素背景是最基础的交互体验之一。一般而言,能够使用代码实现的界面,我们都会尽可能减少图片的使用,这主要是有几方面的原因,第一,是图片会消耗更多的带宽,对于移动端或者网络信号较差时的体验不够友好,第二是不够便捷,在使用图片的情况下即使有细微的调整也需要重新做图上传,第三是不够灵活,这一点主要体现在根据不同的条件需要呈现不同的效果。
本文就使用网格背景作为例子,来通过代码实现,这是在可视化课程中新学的内容,这里我做一个总结和复习。网格背景是一种常见的设计元素,它可以为网页增添一种现代感和动态感。要实现网格背景,我们可以使用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 个周期的数值。
但我感觉这种说法并不太直观,至少我看下来觉得还是一头雾水,当然也可能是我的理解能力有限,着色器程序理论上是批量执行,那么这段代码针对某个待处理的像素点是什么含义呢?因为暂时我想先自己探索一番,所以我也没有参考其他资料。
经过多日思考,以下是我目前的几点理解:
首先是传递的纹理坐标,我们可以理解是一个单元的坐标,坐标的范围是一个正方形。
然后
vUv * rows可以看作是相当于把纹理坐标在画布上的纵向放大rows倍,又因为纹理坐标范围的原因,同时相当于在横向上也放大了rows倍。接着就得到了当前待处理的像素点映射到纹理坐标上的位置,后续会根据这个像素点的纹理坐标去计算设置。
st保存了像素点对应纹理坐标的小数部分。
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
最后我们调用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实现网格背景的更多相关文章
- Tensorflow学习笔记3:TensorBoard可视化学习
TensorBoard简介 Tensorflow发布包中提供了TensorBoard,用于展示Tensorflow任务在计算过程中的Graph.定量指标图以及附加数据.大致的效果如下所示, Tenso ...
- css实现网格背景
只使用一个渐变时,我们能创建的图案并不多,当我们把多个渐变图案组合起来,让他们透过彼此的透明区域显现时,神奇的事情就发生了!我们首先想到的是把水平和水质条纹叠加起来,就可以得到各种各样的网格. 1. ...
- 【Visual C++】游戏编程学习笔记之六:多背景循环动画
本系列文章由@二货梦想家张程 所写,转载请注明出处. 本文章链接:http://blog.csdn.net/terence1212/article/details/44264153 作者:ZeeCod ...
- 可视化学习Tensorboard
可视化学习Tensorboard TensorBoard 涉及到的运算,通常是在训练庞大的深度神经网络中出现的复杂而又难以理解的运算.为了更方便 TensorFlow 程序的理解.调试与优化,发布了一 ...
- 第四界css大会 黑魔法-css网格背景、颜色拾取器、遮罩、文字颜色渐变、标题溢出渐变等
1.css网格背景 <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- R语言可视化学习笔记之添加p-value和显著性标记
R语言可视化学习笔记之添加p-value和显著性标记 http://www.jianshu.com/p/b7274afff14f?from=timeline 上篇文章中提了一下如何通过ggpubr ...
- 87、使用TensorBoard进行可视化学习
1.还是以手写识别为类,至于为什么一直用手写识别这个例子,原因很简单,因为书上只给出了这个类子呀,哈哈哈,好神奇 下面是可视化学习的标准函数 ''' Created on 2017年5月23日 @au ...
- Echart可视化学习集合
一.基本介绍:ECharts是一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表.ECharts最初由百度团队开源,并于2018年初捐赠给Apache ...
- Python可视化学习(1):Matplotlib的配置
Matplotlib是一个优秀的可视化库,它提供了丰富的接口,让Python的可视化落地显得非常容易上手.本系列是本人学习python可视化的学习笔记,主要用于监督自己的学习进度,同时也希望和相关的博 ...
- matplotlab可视化学习
1 使用pip安装 使用 Python 包管理器 pip 来安装 Matplotlib 是一种最轻量级的方式.打开 CMD 命令提示符窗口,并输入以下命令: pip install matplotli ...
随机推荐
- vue全局事件总线和消息订阅详细讲解
全局事件总线 在写组件的时候,我们都知道父传递子 也知道子传递给父 但是组件间嵌套复杂的时候我们应该怎么通信呢? 有的小伙伴会说适用vuex,的确是可以解决问题的 下面我们说一下全局事件总线 一种组件 ...
- Python 潮流周刊第 29 期(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- ABP-VNext 用户权限管理系统实战03---动态api调用并传递token
一.使用动态api的目的 ABP可以自动创建C# API 客户端代理来调用远程HTTP服务(REST APIS).通过这种方式,你不需要通过 HttpClient 或者其他低级的HTTP功能调用远程服 ...
- [1] HEVD 学习笔记:HEVD 环境搭建
1. HEVD 概述 + 环境搭建 HEVD作为一个优秀的内核漏洞靶场受到大家的喜欢,这里选择x86的驱动来学习内核漏洞,作为学习笔记记录下来 实验环境 环境 备注 调试主机操作系统 Window ...
- 独立安装VS的C++编译器build tools
Microsoft C++ 生成工具 Microsoft C++ 生成工具 - Visual Studio Microsoft C++ 生成工具通过可编写脚本的独立安装程序提供 MSVC 工具集,无需 ...
- 自定义httpServletRequestWrapper导致上传文件请求参数丢失
问题背景 项目是 SpringBoot 单体式,在项目中,为了实现调用 controller 请求的日志记录功能.因此做了以下配置: 创建自定义拦截器 LogInterceptor; 因为需要使用到流 ...
- 6.3 Windows驱动开发:内核枚举IoTimer定时器
今天继续分享内核枚举系列知识,这次我们来学习如何通过代码的方式枚举内核IoTimer定时器,内核定时器其实就是在内核中实现的时钟,该定时器的枚举非常简单,因为在IoInitializeTimer初始化 ...
- Asp.net平台常用的框架整理
分布式缓存框架: Microsoft Velocity:微软自家分布式缓存服务框架. Memcahed:一套分布式的高速缓存系统,目前被许多网站使用以提升网站的访问速度. Redis:是一个高性能的K ...
- tp、laravel 伪静态配置
一.Apache下的伪静态配置 <IfModule mod_rewrite.c> Options +FollowSymlinks -Multiviews RewriteEngine On ...
- (数据科学学习手札158)基于martin为在线地图快速构建精灵图服务
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,martin作为快速发展中的新 ...