在前端方向里,WebGL 算是典型的小众领域,真正理解的人并不多。甚至一些拿 Three.js 写 3D 应用的同学,对 WebGL 本身也是一知半解。
之所以这样,是因为 WebGL 的技术栈和传统的 Web 前端技术有极大的差别。相对而言,传统 Web 前端使用的 API 比较高级,不存在太多需要理解的底层原理合概念,而 WebGL 的核心是 OpenGL,它是 OpenGL 在 Web 上的实现。OpenGL 是通过操作 GPU 来完成图形绘制渲染的,因此它的 API 相对比较底层,使用起来较为繁琐,这使得一些习惯于前端开发的工程师很难适应,所以就会觉得学习门槛较高。
实际上,要理解和学会 WebGL,并没有那么困难,我们只需要理解一下 GPU,了解它与 CPU 的不同点,然后再理解运行 GPU 代码的语言——glsl,了解着色器的基本概念和用法,就可以轻松理解 WebGL 的本质原理,然后在花一点时间和耐心,慢慢学习 WebGL 的 API,就可以掌握 WebGL 这门技术了。
那么什么是 GPU,它与 CPU 有什么不同?我们通过下面几张图来看一下:
上面这张图,是 CPU 的工作原理,它就像是一个管道,数据(图中的箱子)从左侧输入,在 CPU 中完成处理,然后从右侧输出。CPU 是由多个这样的管道构成的,每个管道我们叫做一个 CPU 内核,如果你打开你电脑的操作系统,查看本机信息,你可能会看到类似这样的信息:2.6 GHz 六核Intel Core i7
,这里的六核,你可以理解成有 6 个这样的管道,因此可以同时处理 6 个任务。
CPU 的工作能力,与管道本身的处理速度(频率)合管道的数量(内核数)有关系,频率越高,那么运算处理单一任务的速度就越快,内核数越多,那么能同时并行处理的任务数就越多。
虽然现代计算机的 CPU 运算能力很强,但是它也有局限性,对于某些场景,它并不擅长,比如图形渲染。
我们知道,计算机图像是由像素构成,所谓像素,可以简单理解为最终呈现在显示设备上的一个 1x1 的颜色小方块。
现在的显示设备非常先进,可以用非常多的像素小方块来精确构图。前端的 CSS 中的px
单位,就是像素单位,一张800px
长、600px
宽的图片,逻辑上是由600*800
,也就是 48 万个像素点构成的。如果要对这张图片的像素进行计算,用 CPU 来运算的话,单核 CPU 需要处理 48 万个微小任务,就像下面这张图:
不是说 CPU 不能完成这样的处理,每一个像素的计算可能是非常简单的(只是处理一下颜色),但是数量太多,对 CPU 这样的结构也会造成负担。因此,在这个时候,另外一种高并发结构,也就是 GPU 就登场了。
与 CPU 不同,GPU 可以看成是由数量非常多的微小管道构成的结构,每一个管道恰好可以处理“一粒沙子”,这样,如果对于一张 600 像素 x800 像素的图片,有 48 万个管道组成的 GPU,就可以同时处理这 48 万个像素点了!事实上,GPU 几乎就是这样做的。
WebGL 利用 JavaScript 来准备数据,将数据通过共享数据结构(ArrayBuffer) 传递给 GPU,由 GPU 进行着色处理,然后再将处理后的数据输出到帧缓冲区,最后再渲染出来。
这其中的关键是头两个步骤,也就是准备数据和着色处理,其中准备数据一般是通过 JavaScript 的类型数组(TypedArray),着色处理是通过 WebGL Program 执行一种特殊的 glsl 语言来实现。在 WebGL 中,着色阶段通常分成两步,分别是顶点着色和片段着色。
我前面也说过,WebGL 的 API 比较底层,所以操作起来比较繁琐,但是也有许多 JS 库,帮我们封装了基本的操作,因此在这里,我们可以先跳过繁琐的 API 部分,利用我简单封装的一个开源库gl-renderer[1]来学习一下 WebGL 的数据和着色部分。
动手实践是最好的理解问题的方法之一,所以我们来动手写一写代码。
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas, {webgl2: true});
const fragment = `#version 300 es
precision highp float;
out vec4 FragColor;
void main() {
FragColor = vec4(1, 0, 0, 1);
}
`;
const program = renderer.compileSync(fragment);
renderer.useProgram(program);
renderer.render();
https://code.juejin.cn/pen/7098235100726296589上面的代码里面,JS的部分很好理解,但是其中有一段fragment变量中的字符串,是需要我们关注的部分,没错,它就是两个着色器之一的片段着色器代码。
这段代码是用 glsl 语言写的,但是并不难理解,第一句 #version 300 es
声明这个着色器是 webgl 2.0 版本的着色器,浏览器目前同时支持 webgl 1.0 和 webgl 2.0 两个版本,它们有一些差异,但差异不是很大。
第二句precision highp float;
表示设置浮点数精度为高精度,这个可以暂时忽略,以后系列文章中再详细讲解。
第三句out vec4 FragColor
声明FragColor
是输出变量,它的类型是vec4
,是一个四维向量,用来表示一个 RGBA 颜色值,它与 CSS 的颜色区别是,CSS 的 RGB 值是 0 到 255,Alpha 值是 0 到 1,但是在着色器里面,RGBA 的值都是从 0 到 1。
void main
是主函数,vec4(1, 0, 0, 1)
表示将 FragColor 设置为红色。
你可以试着修改码上掘金中的代码,把 vec4(1, 0, 0, 1) 改成 vec4(0, 1, 0, 1),看看会发生什么?
在这里有必要解释一下为什么这么设置整个画布会变成红色。还记得前面说的,GPU 是并行计算的,也就是说,这段着色器代码,是每个像素都并行执行(严格来说是根据图元装配的结果来执行对应区域的像素,但在这里我们先略过这一点),因此,整个画布中,所有的像素点,你可以认为都执行了一遍上面的着色器代码,而且是同时、并行执行的,由于是无差别执行的设置颜色为红色,因此整块画布都成为了红色的。
我们可以修改一下上面例子的代码,看一下给每个像素设置不同颜色的情况:
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas, {webgl2: true});
const fragment = `#version 300 es
precision highp float;
out vec4 FragColor;
uniform vec2 resolution;
void main() {
vec2 st = gl_FragCoord.xy / resolution;
FragColor = vec4(st, 0, 1);
}
`;
const program = renderer.compileSync(fragment);
renderer.useProgram(program);
renderer.uniforms.resolution = [canvas.width, canvas.height];
renderer.render();
我们修改了上面的代码,在着色器中增加了一个uniform vec2 resolution
,这是声明了一个 resolution 变量,它的类型是二维向量,我们通过renderer.uniforms.resolution
将画布的宽高传入。
gl_FragCoord.xy
是一个内置变量,它表示当前渲染的像素在画布内的坐标,左下角是[0,0],右上角是[width,height],所以gl_FragCoord.xy / resolution
可以将坐标值“归一”(即将值限制到 0~1 区间,这是一种在写着色器的时候经常使用的数学技巧)。然后我们将 st 的值传给 FragColor,这样最终运行的结果如下:
https://code.juejin.cn/pen/7098245656740888612
通过这个例子,我们可以简单理解片段着色器的运行方式,片段着色器对图形十分重要,在后续的文章里,我们还有很多技巧需要逐步学习。
但是我们现在先回过头来,解决一个问题,为什么这里片段着色器对整个 canvas 生效?
所以,接下来我们就来了解另一个着色器:顶点着色器。
还是老办法,动手实践——让我们来修改代码:
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas, {webgl2: true});
const fragment = `#version 300 es
precision highp float;
out vec4 FragColor;
void main() {
FragColor = vec4(1, 0, 0, 1);
}
`;
const program = renderer.compileSync(fragment);
renderer.useProgram(program);
renderer.setMeshData([
{
positions: [[0, 1, 0], [-1, -1, 0], [1, -1, 0]],
cells: [[0, 1, 2]],
},
]);
renderer.render();
https://code.juejin.cn/pen/7098248752191766542
在这里,我们没有添加顶点着色器,只是给 renderer 设置了一些数据,在数据里我们指定了三个顶点,它们的三维坐标分别是[0, 1, 0], [-1, -1, 0], [1, -1, 0]
,这里我们没有用到 z 轴,所以 z 保持 0,x 和 y 在 WebGL 中,默认范围是从-1 到 1,所以 WebGL 的平面坐标系原点在中心,左下角是[-1,-1],右下角是[1, -1],右上角是[1, 1],左上角是[-1,1]。我们设置了 position 这三个顶点之后,绘制在画面上的就变成了一个三角形。
之所以我们没有指定顶点着色器,是因为 gl-renderer 有默认的顶点着色器,代码如下:
#version 300 es
precision highp float;
precision highp int;
in vec3 a_vertexPosition;
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(a_vertexPosition, 1);
}
我们也可以指定顶点着色器,我们可以继续修改代码:
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas, {webgl2: true});
const vertex = `#version 300 es
precision highp float;
precision highp int;
in vec3 a_vertexPosition;
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0.5 * a_vertexPosition, 1);
}`;
const fragment = `#version 300 es
precision highp float;
out vec4 FragColor;
void main() {
FragColor = vec4(1, 0, 0, 1);
}
`;
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
renderer.setMeshData([
{
positions: [[0, 1, 0], [-1, -1, 0], [1, -1, 0]],
cells: [[0, 1, 2]],
},
]);
renderer.render();
https://code.juejin.cn/pen/7098253710907670564
上面的代码我们指定了顶点着色器,在它里面我们把a_vertexPosition
,也就是我们传入的顶点坐标给乘以了 0.5,所以最终绘制出来的三角形周长就是原来的 1/2。
因为三角形是 WebGL 的基本图元,WebGL 支持点、线、三角形等基本图元。下面的代码我们更换了图元。
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas, {webgl2: true});
const vertex = `#version 300 es
precision highp float;
precision highp int;
in vec3 a_vertexPosition;
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0.5 * a_vertexPosition, 1);
}`;
const fragment = `#version 300 es
precision highp float;
out vec4 FragColor;
void main() {
FragColor = vec4(1, 0, 0, 1);
}
`;
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
renderer.setMeshData([
{
mode: 'LINE_STRIP',
positions: [[0, 1, 0], [-1, -1, 0], [1, -1, 0]],
cells: [[0, 1, 2, 0]],
},
]);
renderer.render();
https://code.juejin.cn/pen/7098254635743313927
最后剩下一个问题,我们默认不设置顶点的时候,绘制的图形是整个 canvas 范围,但是 WebGL 并不支持四边形图元,那么我们原本的绘制范围是如何界定的呢?
这个问题作为本篇文章的课后问题,留到下一讲来解决吧。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/r8DRhHsySIAjGB4Ij6l4WA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。