对实现动画的前端同学们来说,canvas可以说是最自由,最能全面控制的一个动画实现载体。不但能通过javascript控制点、线、面的绘制,使用图片资源填充;还能改变输入参数作出交互动画,完全控制动画过程中的动作轨迹、速度、弹性等要素。

但使用canvas开发过较复杂一点的动画的同学,可能会发现,完全使用javascript绘制、控制的动画,某些效果不太好实现(这篇文章只讨论2D),像模糊,光照,水滴等效果。虽然用逐像素处理的方法也可以实现,但javascript对这类型大量数据的计算并不擅长,实现出来每一帧绘制的时间十分感人,用他实现动画并不现实。

canvas除了最常用的javascript API绘制方式(getContext('2d')),还有WebGL的方式(getContext(webgl)),对前面说到的大量数据计算的场景,可以说是最适合发挥的地方。WebGL对很多同学来说就是实现3D场景的,其实对2D绘图来说,也有很大的发挥场景。

为什么WebGL会比较厉害

我们来看看javascript API绘制和webGL绘制原理上的不同之处:

如果使用javascript对画布的逐个像素进行处理,那这部分处理工作就需要在javascript的运行环境里进行,我们知道javascript的执行是单线程的,所以只能逐个逐个像素进行计算和绘制。就像一个细长的漏斗,一滴一滴水的往下漏。

而WebGL的处理方式,是用GPU驱动的,对每一个像素的处理,都是在GPU上执行,而GPU有许多渲染管道,这些处理可以在这些管道中并行执行,这就是WebGL擅长这种大量数据计算场景的原因。

WebGL那么厉害,都用它绘图就好喇

WebGL虽然有上面说的优点,但也有个致命的缺点:不好学,想要简单画根线也要费一番力气。

GPU并行管道之间是不知道另一个管道输出的是什么,只知道自己管道的输入和需要执行的程序;而且不保留状态,管道自己并不知道在这次任务之前执行过什么程序,有什么输入输出值,类似现在纯函数的概念。这些观念上的不同就提升了使用WebGL绘图的门槛。

另外这些跑在GPU里的程序不是javascript,是一种类C语言,这也需要前端同学们另外再学习。

Hello, world

那门槛再高也总有需要跨过去的一天的,下面一步一步控制WebGL去一点图案,大家也可以体会一下,适合在什么时候使用这一门技术。

基础环境——大荧幕

为尽快进入GLSL着色器的阶段,这里基础WebGL环境搭建用了Three.js,大家可以研究下这个基础环境的搭建,不用第三方库其实也用不了多少代码量。

以下是基础环境的搭建:

function init(canvas) {
const renderer = new THREE.WebGLRenderer({canvas});
renderer.autoClearColor = false; const camera = new THREE.OrthographicCamera(
-1, // left
1, // right
1, // top
-1, // bottom
-1, // near,
1, // far
);
const scene = new THREE.Scene();
const plane = new THREE.PlaneGeometry(2, 2); const fragmentShader = '............'
const uniforms = {
u_resolution: { value: new THREE.Vector2(canvas.width, canvas.height) },
u_time: { value: 0 }
};
const material = new THREE.ShaderMaterial({
fragmentShader,
uniforms,
});
scene.add(new THREE.Mesh(plane, material)); function render() {
material.uniforms.u_time.value++;
renderer.render(scene, camera);
requestAnimationFrame(render);
} render()
}

解释一下上面这段代码做了什么:创建了一个3D场景(说好的2D呢?),把一个矩形平面糊在摄像机前面,占满摄像机视觉范围,就像看IMAX坐最前排,你能看到的就只有面前的屏幕的感觉,屏幕上的画面就是你的整个世界。我们的绘图就在这个屏幕上。

再说明一下,着色器分为顶点着色器VERTEX_SHADER和片段着色器FRAGMENT_SHADER

顶点着色器对3D场景里物体的每个顶点计算值,如颜色、法线向量等,在这里我们只讨论2D画面,顶点着色器的部分就由Three.js代劳了,实现的作用就是固定了场景中镜头和屏幕的位置。

而片段着色器的作用就是计算平面上每一个片段(在这里是屏幕上每一个像素)输出的颜色值,也是这篇文章研究的对象。

片段着色器入参有varyinguniform两种,varying简单说一下是由顶点着色器传入的,每个片段输入的值由相关的顶点线性插值得到,所以每个片段上的值不一样,本文先不讨论这部分(不然写不完了)。uniform是统一值,由着色器外部传入,每个片段得到的值是一样的,在这里就是我们从javascript输入变量的入口。上面的代码我们就为片段着色器传入了u_resolution,包含画布的宽高值。

第一个着色器

fragmentShader为着色器的程序代码,一般的构成为:

#ifdef GL_ES
precision mediump float;
#endif uniform vec2 u_resolution; void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

在前3行检查了是否定义了GL_ES,这通常在移动端或浏览器下会定义,第2行指定了浮点数float的精度为中等,也可以指定为低精度lowp或高精度highp,精度越低执行速度越快,但质量会降低。值得一提的是,同样的设置在不同的执行环境下可能会表现不一样,例如某些移动端的浏览器环境,需要指定为高精度才能获得和PC端浏览器里中等精度一样的表现。

第5行指定了着色器可以接收哪些入参,这里就只有一个入参:类型为vec2的u_resolution

最后3行描述了着色器的主程序,其中可以对入参和其他信息作处理,最后输出颜色到gl_FragColor,代表这个片段显示的颜色,其中4个数值代表RGBA(红、绿、蓝、透明度),数值范围为0.0 ~ 1.0

为什么要写0.0而不是0呢,因为GLSL里不像javascript数字只有一个类型,而是分成整形(int)和浮点数(float),而浮点数必须包含小数点,当小数点前是0的时候,写成.0也可以。

那大家看完这段解说,应该能猜到上面的着色器会输出什么吧,对,就是全屏的红色。

这就是最基础的片段着色器。

使用uniform

大家应该注意到上面的例子没有用到传入的uniform值,下面来说一下这些值怎么用。

看之前搭建基础环境的javascript代码可以看到,u_resolution存储了画布的宽高,这个值在着色器有什么用呢?

这要说到片元着色器的另一个内建的值gl_FragCoord,这个值存储的是片段(像素)的座标xy值,使用这两个值就可以知道当前着色器计算的是画布上哪个位置的颜色。举个例子:

#ifdef GL_ES
precision mediump float;
#endif uniform vec2 u_resolution; void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
gl_FragColor = vec4(st, 0.0, 1.0);
}

可以看到这样的图像:

上面的着色器代码,使用归一化后的xy座标输出到gl_FragColor的红、绿色部分。

从图中可以看出,gl_FragCoord(0, 0)点在左下角,x轴和y轴方向分别为向右和向上。

另一个uniform值u_time就是一个随着时间不断增加的值,利用这个值可以使图像随时间变化,实现动画的效果。

上面的着色器再改写一下:

#ifdef GL_ES
precision mediump float;
#endif uniform vec2 u_resolution;
uniform float u_time; void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
gl_FragColor = vec4(st, sin(u_time / 100.0), 1.0);
}

可以看到下图的效果:

http://storage.360buyimg.com/element-video/QQ20210330-195823.mp4

着色器中使用三角函数sin,在颜色输出的蓝色通道做一个从0到1的周期变化。

还能做什么?

掌握基本的原理后,就是开始从大师的作品中学习了。shadertoy是一个类似codepen的着色器playgroud,上面的着色器都是利用上面的基本工具,还有一些造型函数,造出各种眼花缭乱的特效、动画。

上面就是GLSL着色器基本的开发工具,现在就可以开始开发你自己的着色器,剩下就是使用数学方面的技能了。

欢迎关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:

GLSL着色器,来玩的更多相关文章

  1. GLSL 着色器程序

    除了使用Cg/HSL 着色器程序以外, OpenGL 着色器语言(GLSL)着色器可以直接书写shader. 然而,使用原生的GLSL只推荐作为测试使用,或者你清晰的知道你的目标平台是 Mac OS ...

  2. [GLSL]着色器周记02——火焰特效 【转】

    http://www.cnblogs.com/tkgamegroup/p/4214081.html 这周学了好多.包括伪随机数.柏林噪声.先说伪随机数.伪随机数我们用的是周期函数而不是那种由前一项乘一 ...

  3. OpenGLES2.0着色器语言glsl

    OpenGLES2.0中是强制使用可编程的渲染管线的,使用的是glsl着色器语言,因为着色器语言是使用的GPU,即图形处理单元,而不是CPU,这样可以使CPU从繁重的几何计算和像素的处理中解脱出来了. ...

  4. 【OPENGL】第三篇 着色器基础(二)

    在这一小节,主要学习GLSL的基本数据类型以及控制结构.GLSL具备了C++和Java的很多特性,我们会先了解所有着色阶段共有的特性,再了解各个着色器的专属特性. 1.着色器的基本结构 一个着色器程序 ...

  5. OpenGL学习脚印: uniform blocks在着色器中的使用 转自https://blog.csdn.net/wangdingqiaoit/article/details/52717963

    写在前面 目前,我们在着色器中要传递多个uniform变量时,总是使用多个uniform,然后在主程序中设置这些变量的值:同时如果要在多个shader之间共享变量,例如投影矩阵projection和视 ...

  6. OpenGL学习笔记(三)着色器

    目录 Shader是什么 GLSL 数据类型 输入与输出 顶点着色器向片段着色器发送数据 Uniform 制作三色渐变三角形 对着色器程序进行封装 参考资料:OpenGL中文翻译 Shader是什么 ...

  7. GLSL 的各种着色器效果

    Site Defunct 注意!截止到 16/9/2019 ,这个博客已经被搬迁到了 这里 .以后我的东西都会发在那里.拜拜啦! GLSL 很牛逼 Vignette ScanLine Pixelate ...

  8. Android OpenGL ES 开发(八): OpenGL ES 着色器语言GLSL

    前面的文章主要是整理的Android 官方文档对OpenGL ES支持的介绍.通过之前的文章,我们基本上可以完成的基本的形状的绘制. 这是本人做的整理笔记: https://github.com/re ...

  9. 着色器语言 GLSL (opengl-shader-language)入门大全

    基本类型: 类型 说明 void 空类型,即不返回任何值 bool 布尔类型 true,false int 带符号的整数 signed integer float 带符号的浮点数 floating s ...

随机推荐

  1. JDBC_10_使用Statement实现升序和降序

    使用Statement数据库操作对象实现升序和降序 Statement可以使用在需要SQL语句拼接的情况下,因为在这样的情况下如果使用PreparedStatement就会给需要拼接的某个SQL关键字 ...

  2. H5 hybrid开发-前端资源本地化方案纪要

    H5 hybrid-前端资源本地化方案纪要 就整个行业来说,大前端是趋势,现阶段,native方面除了一些偏CPU密集型工作与操作系统底层API方面的工作外,H5基本都可以满足需要. 目前的工作更偏向 ...

  3. Spring Cloud Alibaba(6)---Nacos持久化Mysql8.0版本

    Nacos持久化Mysql8.0版本 有关Nacos之前写过三篇文章. Spring Cloud Alibaba(3)---Nacos概述 Spring Cloud Alibaba(4)---Naco ...

  4. CPF 入门教程 - 属性和事件(七)

    CPF C#跨平台桌面UI框架 系列教程 CPF 入门教程(一) CPF 入门教程 - 数据绑定和命令绑定(二) CPF 入门教程 - 样式和动画(三) CPF 入门教程 - 绘图(四) CPF 入门 ...

  5. Standalone模式下,通过Systemd管理Flink1.11.1的启停及异常退出

    Flink以Standalone模式运行时,可能会发生jobmanager(以下简称jm)或taskmanager(以下简称tm)异常退出的情况,我们可以使用Linux自带的Systemd方式管理jm ...

  6. pyqt5 菜单栏+信息提示框

    前言 使用pyqt5 添加菜单栏 单击菜单栏 弹出信息框(MessageBox用法) 菜单栏 功能 Action是Qt中单独引入的一个对象,对应QAction类.Action表示一个独立的操作,是将界 ...

  7. 【JVM】空间分配担保机制

    抛几个问题: 1.谁进行空间担保? JVM使用分代收集算法,将堆内存划分为年轻代和老年代,两块内存分别采用不同的垃圾回收算法,空间担保指的是老年代进行空间分配担保 2.什么是空间分配担保? 在发生Mi ...

  8. Vue学习(二)-Vue中组件间传值常用的几种方式

    版本说明:vue-cli:3.0 主要分为两类: 1.父子组件间的传值 2.非父子组件间的传值 1.父子组件间传值 父组件向子组件传值 第一种方式: props 父组件嵌套的子组件中,使用v-bind ...

  9. 书评第001篇:《C++黑客编程揭秘与防范》

    本书基本信息 作者:冀云(编著) 出版社:人民邮电出版社 出版时间:2012-6-1 ISBN:9787115280640 版次:1 页数:265 字数:406000 印刷时间:2012-6-1 开本 ...

  10. hdu4974 简单题

    题意:       一个人看比赛,这些比赛一共有n个人参与,每一场有两个人比,然后每一场之后这个人都会给比赛的这两个人打分,最多1最少0,比如看完了A,B两人比赛,他可能给这两个人分别的分数是00,1 ...