草庐IT

一起学 WebGL:绘制一个点

前端西瓜哥 2023-04-17 原文

大家好,我是前端西瓜哥。

本文讲解如何用 WebGL 绘制一个点。

WebGL

WebGL 是浏览器支持的一种绘制图形的 API,是一个标准。我们可以通过 Canvas 元素 在网页的特定区域绘制 2D 和 3D 图形。

相比 Canvas 2D,WebGL 利用了 GPU 的计算能力,绘制速度更快,性能更优。

WebGL 基于 OpenGL 发展而来,某种意义上就是 Web 版的 OpenGL,但是阉割了一些功能。

更具体点,是来自 OpenGL 的一个特殊版本 OpenGL ES 2.0,全称为 OpenGL for Embedded Systems,“用于嵌入式系统的 OpenGL”。

使用 WebGL,除了浏览器正统脚本语言 JavaScript,还要使用一种 名为 GLSL ES 的类 C  着色器语言。

绘制过程和着色器

将代码描述的效果真正绘制到屏幕上的过程,称为 渲染管线。

管线(pipeling)这个词有点奇怪,因为它没有对应的比较好的翻译,是一个直译。

管线指的是 数据处理的流水线,这个流水线上有很多处理器,会将数据一步步地进行处理,最终得到一个成品。渲染管线,就是渲染过程中执行的一个个步骤。

渲染管线的流程为:

  1. 顶点处理阶段。接收顶点信息,比如我要画一个三角形,三个点的位置在哪里,尺寸、颜色分别是多少。
  2. 图形装配。多个点组合成怎样的图形。我们会用 API 进行指定,比如点、线段、三角形。
  3. 光栅化。将顶点转换为需要绘制的像素信息,比如位置、深度。
  4. 片元处理阶段。设置像素的颜色信息。

这四个流程中,我们能操作的是第 1 和第 4 步。

绘制一个点

Demo 地址:

https://codesandbox.io/s/webgl-hui-zhi-yi-ge-dian-2-bpwz8p。

下面我们来讲解如何绘制一个点。

首先是 WebGL 绘制的地方 Canvas。我们需要在 HTML 中添加一个 Canvas 元素,然后在 JavaScript 中获取这个元素,并拿到 WebGL 渲染上下文。

const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");

如果你用过 Canvas 2D,它对应的上下文变量命名通常是 ctx(context)。但对于 WebGL,我们通常会跟随 OpenGL 的习惯,将变量命名为 gl(OpenGL 的 gl,Graphics Library 的意思)。

接着是顶点着色器。

顶点着色器,用于设置图形的顶点相关的信息,设置好顶点,WebGL 才能确定好图形的位置等信息,好绘制出来。

着色器的代码是在 JavaScript 脚本中,用字符串来写 glsl。

顶点着色器

先是顶点着色器。

const vertexShaderSrc = `
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 20.0;
}
`;

gl_Postion 和 gl_PointSize 是 WebGL 顶点着色器的内部变量,用于设置点的位置和点的大小。

vec4 表示矢量类型,同时也是内置函数,调用它就能得到一个 vec4 类型。

这里设置了顶点的位置 (0.0, 0.0, 0.0, 1.0)。WebGL 使用的是三维坐标系,并使用右手坐标系,就是 x 轴指向右侧,y 轴指向上方,z 轴指向观察者。原点就在画布的正中央。

三维中,一个点只要三个维度 x、y、z 就够了,但引入了第四个维度 w,从笛卡尔坐标升维为齐次坐标,作用是方便做矩阵变换和透视投影。齐次坐标 ​​(x, y, z, w)​​​ 等价于三维坐标 ​​(x/w, y/w, z/w)​​。w 通常会设置为 1。

需要注意的是,着色器中的的数值需要加上小数点,表示用的是浮点数类型。这和 JavaScript 随便写都会变成浮点数不一样。

片元着色器

然后是片元着色器。

顶点着色器确定图形的点的位置,片元着色器则是用于设置多个顶点围成的图形的像素点的 颜色

const fragmentShaderSrc = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;

gl_FragColor 是片元着色器的内部变量,这里设置为红色。

创建渲染器

因为是入门文章,细节不展开讲了。

总之下面这段代码,将前面声明的顶点着色器和片元着色器的两段源码,进行了编译。

/**** 渲染器生成处理 ****/
// 创建顶点渲染器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);

// 创建片元渲染器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);

// 程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.program = program;

清空缓存

gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

首先第一行代码将背景色设置为黑色。当然你也可以设置为其他颜色,比如绿色 (0, 1, 0, 1)。

第二行是清空缓存区,填充背景色。

绘制点

gl.drawArrays(gl.POINTS, 0, 1);

该 API 用于调用绘制指令,有三个参数:

  1. mode。绘制怎样的图元。比如点(gl.POINTS)、各种类型的线段、各种类型的三角形。也就这三种图元。再复杂的图形也是由一个个三角形组成的。这里用到的微积分的思想,将三维物体不断的细分,其实就是一个个非常小的平面组成的,三个点确定一个平面,也就是三角形。正方形也是两个三角形组成。
  2. first。从哪个顶点开始绘制,通常是 0。本文只是画一个点,这个参数没太大意义,存在多个顶点时才有用,虽然也少用。
  3. count。使用到几个顶点。

绘制结果如下:

结尾

下一篇画个三角形。

有关一起学 WebGL:绘制一个点的更多相关文章

  1. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  2. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  5. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  6. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  7. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  8. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

    如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

  9. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐