WebGL 的 Hello World
本文整理自 div 侠于 凹凸 2022 年技术分享,简单介绍了 WebGL 画一个基础图形的流程,希望你了解之后,在使用 3d 渲染库的时候可以少点迷糊。
四种常用的页面绘图工具—
关于 h5 页面的图形绘制,我们大多谈及的是这四种工具:html+css,svg、canvas2d、WebGL。
html+css 是最常见的绘图工具了,使用 css 绘图跟平时写页面布局一样,在制作图表的时候,我们可以用 css 把图表的样式定义好,其他的,就是根据数据的不同 ,给元素添加上不同的属性。这样的开发对于图表元素简单、数据结点少的场景非常友好。不仅可以减少开发的工具量,而且不用引入多余的代码库。但是,随时需要绘制的图形越来越多, css 代码做变得越来越复杂,加上 css 本来没有逻辑语义,代码会变得不易阅读和维护。
svg 是可缩放矢量图形,他跟 html、css 的结合很紧密,可以把 svg 当做 img 的 src ,也可以用 css 操控 svg 的属性, svg 和 html 都是文本标记语言, svg 较 html 增加了对非线性图形的支持,包括圆弧,贝塞尔曲线等。同时,svg 支持
canvas2D 是 canvas 的 2d 绘图上下文,他提供了一系列方法,用于对 canvas 区域的图像进行修改和绘制,相比于前两者的开箱即用, canvas2d 很多图形和颜色都需要自己实现和封装使得这个工具上手的难度大了不少,但是,如果把这些基础的事情做好,你将拥有一个功能完全覆盖前面两个工具,而且便于扩展的绘图工具。
WebGL 也是 canvas 的绘图上下文,是 OpenGL es 的 web 实现。最大的特点,就是更低层,可以直接使用 gpu 的并行能力。在处理图形数量非常多,像素级处理和 3d 物体的场景下,拥有很高的性能优势。
四种工具的选择思路
当我们拿到一个绘图需求的时候,应该先看看这个需求用到的图形是不是比较少,而且简单。如果是的话,可以直接选择 css 进行快速开发。如果图形虽然简单但比较多,或者图形有一些曲线需求,这个时候 svg 还可以快速应付。如果图形之间的结构复杂,数量比较多的时候选择 canvas2d 。而当图形的数量级大到一定的量,或者需要对每一个像素进行处理,或者需要大量的 3d 展示的时候,我们得使用 WebGL 了
WebGL 的 Hello World—
WebGL 的 Hello World 不像其他工具一样可以一两行代码就搞定,而是足足有四十多行代码。虽然这串代码在各个 3d 渲染库里都有对应封装的方法,基本不用我们自己徒手去写,但是学习这串代码可以让我们对 WebGL 绘图过程有一个最基础的了解。
WebGL 绘图一共有五个步骤:
-
创建 WebGL 绘图上下文 -
创建着色器编程,关联到 gl 上下文中 (跟第3步并行) -
创建数据,放入缓冲区并把缓冲区关联到 gl 上下文中(跟第2步并行) -
GPU 加载缓存中的数据 -
绘制图形
创建 WebGL 上下文
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
创建着色器程序
const program = gl.createProgram();
gl.attachShader(program, /*某个着色器(下文的vertexShader)*/);
gl.linkProgram(program);
gl.useProgram(program);
着色器是一段给 gpu 运行的程序,我们用 glCreateProgram 创建一个空的程序对象,然后使用 glAttachShader 给这个程序对象填充编译后的着色器代码。着色器是什么,怎么编译后面再说,这里可以把他当成某一个函数编译后的代码。把几个这种编译后的函数放入程序对象后, GPU 执行这个程序对象,就会把像素信息当做入参,依次执行程序对象中的函数。
填充完着色器代码后,调用 glLinkProgram 把程序关联到 gl 上下文中,并用 glUseProgram 来启用这个程序。
接下来,来看一下着色器代码怎么搞出来。
const vertex = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 1.0, 1.0);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
首先我们定义了一个变量 vertex 并给他赋值一串其他语言格式的代码字符串,这个串代码是 glsl 代码,是一个跟 c 语言很相似的代码。代码接收一个传入的二维向量 position ,然后把他执行环境中的全局变量 gl_Position 设置成一个四维向量,这个四维向量前两个维度的分量是传入的二维向量。
接下来用 glCreateShader 创建一个着色器, VERTEX_SHADER 常量说明这个着色器是一个顶点着色器,跟顶点着色器对应的是片元着色器,顶点着色器处理做为确定点的位置。片元着色器则对顶点构成的图形中的所有位置进行逐个处理,比如两点画一个直线,两点是顶点着色器确定的,直线是片元着色器在确定了两个点的位置之后画的。
在我们创建了一个空的顶点着色器对象 vertexShader 之后,就可以用 glShaderSource 把前面的字符串代码放入顶点着色器对象中,然后用 glCompileShader 把这段代码编译成可执行文件。这个过程跟 c 语言的编译过程是相似的。
gl.attachShader(program, /*某个着色器(下文的vertexShader)*/);
gl.attachShader(program, vertexShader);
完成这一步之后,就要回到上面写注释那里,把着色器对象关联到程序对象里。当然,你还得去写一个片元着色器,用同样的步骤把一个片元着色器也关联到程序对象里。
将数据存入缓冲区
const points = new Float32Array([-1, -1, 0, 1, 1, -1]);
const bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
经过上文的操作之后,我们已经有了一个装载着着色器代码的程序对象,这个对象放到 gl 绘图上下文中被启用了。接下来,我们要定义的就是给这个程序用的数据。
在顶点着色器那一块,代码里面接受一个传入的二维向量,就是我们现在要定义的。首先定义一个类型化数组,初始化的时候放入 6 个数,这个 6 个数后面会被绘图程序分成三组放到三次顶点着色器调用中。另外,使用类型化数组是为了优化性能,让大量数据的情况下,数据占用的空间更小。
有了数据之后,调用 glCreateBuffer 创建一个缓冲区对象,用 glBindBuffer 把这个对象跟 gl 绘图上下文关联起来,最后调用 glBufferData 把 points 的数据放入缓冲区中。
gpu加载缓存中的数据
const vPosition = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
在这一步中,我们先调用 glGetAttribLocation 拿到程序对象中 position 这个变量的位置,调用 glVertexAttribPointer 把这个变量的长度设置为 2,类型设置成 glFLOAT,并用 glEnableVertexAttribArray 启用这个变量
绘制图形
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
到了最后一步,只要用 glClear 把颜色缓冲区清空,然后用 glDrawArrays 进行绘图就行了。其中 gl.TRIANGLES 确定了片元着色器的绘图范围,当这个值是 gl.POINTS,着色器会把点两两连接,而 gl.TRIANGLES 让第三个点成一组绘制三角形
这样,WebGL 的一个 Hello World 就完成了,上面的三角形就是这40 行代码输出的图像。
总结—
这段程序在 three.js 和其他的 3d 框架和工具库里都有一定的封装,通过那些库进行 WebGL 的绘图相对来说会方便很多,但如果不知道这些库最根本的操作,就很容易在遇到问题的时候绕进去。所以希望本文能增加大家对 web 3d 底层方面的理解,给大家在学习这些3d工具库的时候提供一些帮助。
参考资料—
GPU与渲染管线:如何用WebGL绘制最简单的几何图形?: https://time.geekbang.org/column/article/63c01cb24d76d96cfd6a9ce64db6a623/share?code=CsdMNTf%2FboqZugxI1qspWwJxCaw2PUoiTwhMmOZ4Klw%3D&source=app_share
本文分享自微信公众号 - 凹凸实验室(AOTULabs)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
3D 沙盒游戏之避障踩坑和实现之旅
背景— 最近在实现一个 3D 的沙盒类游戏,基本的功能就是在一个 3D 平面里,进行建筑物的搭建,可以在场景内添加或者编辑建筑物,然后平面内存在一个人物模型,他可以穿梭行走于建筑物之间。 在实现人物的行走功能的时候,我们很自然地想到取终点和起点两点的坐标,然后连成直线进行行走即可,在没有建筑物的时候这个想法确实很好,可是一旦在地图中出现建筑物之后就会发现:忒喵的人物穿模啦! 至此,寻找避障方案之旅开始了。 使用 Babylon.js 自带的避障功能— 由于我们的项目是使用 Babylon.js 框架来进行开发的,因此本着“能不自己写坚决不自己写”的原则,我们的第一个想法就是使用 Babylon.js 自带的避障功能。 介绍 简单介绍一下 Babylon.js 中自带的避障功能,它属于框架的一种拓展功能,需要结合 RecastJS 依赖进行使用。 利用 RecastJS 进行导航网格(Navigation Mesh)的生成。然后再通过 RecastJS 中的 Crowd Agents 模块进行自动的寻路和避障。 可能看到这里的你会有一个疑问,什么是导航网格,这里引用一段我在其他博客中看到...
-
下一篇
TensorFlow在美团外卖推荐场景的GPU训练优化实践
美团机器学习平台基于内部深度定制的TensorFlow研发了Booster GPU训练架构。该架构在整体设计上充分考虑了算法、架构、新硬件的特性,从数据、计算、通信等多个角度进行了深度的优化,最终其性价比达到CPU任务的2~4倍。本文主要讲述Booster架构的设计实现、性能优化及业务落地工作,希望能对从事相关开发的同学有所帮助或者启发。 1 背景 在推荐系统训练场景中,美团内部深度定制的TenorFlow(简称TF)版本[1],通过CPU算力支撑了美团内部大量的业务。但随着业务的发展,模型单次训练的样本量越来越多,结构也变得越来越复杂。以美团外卖推荐的精排模型为例,单次训练的样本量已达百亿甚至千亿,一次实验要耗费上千核,且优化后的训练任务CPU使用率已达90%以上。为了支持业务的高速发展,模型迭代实验的频次和并发度都在不断增加,进一步增加了算力使用需求。在预算有限的前提下,如何以较高的性价比来实现高速的模型训练,从而保障高效率的模型研发迭代,是我们迫切需要解决的问题。 近几年,GPU服务器的硬件能力突飞猛进,新一代的NVIDIA A100 80GB SXM GPU服务器(8卡)[2]...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL数据库在高并发下的优化方案
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker快速安装Oracle11G,搭建oracle11g学习环境