草庐IT

一起学 WebGL:动态绘制点

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

大家好,我是前端西瓜哥。上一篇文章讲解了如何绘制一个点。但这个点的信息是写死在渲染器源码中的,也就是硬编码。

这是系列文章,如果你是初学者,你需要看上一篇文章才好理解这节课的内容。

​《一起学 WebGL:绘制一个点》​

如果我们要频繁地改变点的位置去绘制,根据我们之前学到的知识点,那只能通过替换整个着色器代码字符串,不断地编译的方式去修改了,非常不便。

这节课我们会通过 JavaScript 给着色器动态提供数据,而不需要重新编译着色器。

静态绘制点

先贴上上篇文章的静态写法。

demo 地址:

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

/**
* wegbl 绘制一个点
*/

/** @type {HTMLCanvasElement} */
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");

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

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

/**** 渲染器生成处理 ****/
// 创建顶点渲染器
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);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);

渲染结果:

外部传递点的位置信息

下面我们希望顶点着色器的点的坐标可以通过 JavaScript 动态地修改。

加入 attribute 变量

修改顶点着色器的代码:

const vertexShaderSrc = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 20.0;
}
`;

这里西瓜哥我将原本的 vec4 类型的写死的颜色值,换成了一个变量 a_Position。attribute 是存储限定符,表示变量是 attribute 类型,它用于描述顶点数据,值可以通过外部传递被修改。后面我们从外部拿到这个变量,传入我们想要的值。

vec4 则是变量类型,表示一个有四个分量的矢量。

变量通常会用 a_ 作为前缀,表示该变量是 attribute 类型。

外部修改 attribute

然后是在 JavaScript 代码中获取这个 a_Position 变量,并传一个值给它。

const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttrib3f(a_Position, 0, 0.5, 0);

第一行 gl.getAttribLocation 方法的作用,是从 gl.program 程序对象中,取出一个名为 "a_Position" 的 attribute 变量的地址。如果找到,会返回一个大于等于 0 的值,找不到会返回  -1。

第二行,gl.vertexAttrib3f 就是根据地址给 a_Position 变量赋予值。a_Position 是 4 个浮点数组成的矢量类型,我们这里并没有传 4 个值,只传了 3 个。最后一个值对坐标系来说并无意义,但在矩阵变换时会用到,不提供会被自动填充为默认的 1.0。

你也可以用 gl.vertexAttrib4f 提供完成的 4 个浮点数,或者用 gl.vertexAttrib1f 或 gl.vertexAttrib2f 提供第一个或前两个浮点数。缺少的 y、z 默认会用 0.0,z 则使用 1.0。

这里我们给 y 设置了 0.5,渲染结果为:

关于坐标系的文章

《​​一起学 WebGL:坐标系​​》

补充一下 WebGL 的坐标系图示:

完整代码:

/**
* wegbl 绘制一个点
*/

/** @type {HTMLCanvasElement} */
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");

const vertexShaderSrc = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 20.0;
}
`;

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

/**** 渲染器生成处理 ****/
// 创建顶点渲染器
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;

/** 修改着色器的 attribute */
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttrib3f(a_Position, 0, 0.5, 0);

/** 画布绘制 **/
// 清空画布,并指定颜色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);

demo 地址:

https://codesandbox.io/s/webgl-dong-tai-hui-zhi-dian-5cdped?file=/index.js。

不断修改位置信息

下面我们用一个定时器,不断地修改点的位置。

代码实现:

/**
* webgl 动态绘制点 + 定时器
*/

/** @type {HTMLCanvasElement} */
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");

const vertexShaderSrc = `
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
gl_PointSize = 20.0;
}
`;

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

/**** 渲染器生成处理 ****/
// 创建顶点渲染器
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;

/** 修改着色器的 attribute */
const a_Position = gl.getAttribLocation(gl.program, "a_Position");

let x = -1;
let y = -1;

setInterval(() => {
gl.vertexAttrib3f(a_Position, x, y, 0);
x += 0.25;
if (x > 1) x = -1;
y += 0.25;
if (y > 1) y = -1;

/** 画布绘制 **/
// 清空画布,并指定颜色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
}, 180);

在定时器中,我们不断地修用 gl.vertexAttrib3f 修改点的位置,并重绘画布。

demo 地址:

https://codesandbox.io/s/webgl-dong-tai-hui-zhi-dian-ding-shi-qi-gnjx3t。

渲染效果:

结尾

我是前端西瓜哥,欢迎关注我,学习更多前端知识。

这里布置一个小练习:动态修改点的大小,这次要用 float 类型,并对应使用 g1.vertexAttriblf 传递值。

答案在:https://codesandbox.io/s/g4ft9m

下一节我们将学习如何动态修改片元着色器中点的颜色。

有关一起学 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-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了,所以充其量只能让更

  3. ruby - 在 Ruby 中动态创建数组 - 2

    有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.

  4. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  5. ruby-on-rails - 如何让 datamapper 与 postgresql 数据库一起工作? - 2

    我已经找到了几个使用datamapper的示例,并且能够让它们正常工作。不过,所有这些示例都是针对sqlite数据库的。我正在尝试将数据映射器与postgresql一起使用。我将datamapper中的调用从sqlite3更改为postgres,并且我已经安装了dm-postgres-adapter。但它仍然不起作用。我还需要做什么? 最佳答案 与SQLite不同,PostgreSQL不将数据库存储在单个文件中。在你拥有createdyourdatabase之后,尝试这样的事情:DataMapper.setup:default,{:

  6. ruby - 了解在 Ruby 中与 lambda 一起使用的 inject 行为 - 2

    我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject

  7. ruby-on-rails - 与 ActiveMerchant 一起使用的最佳支付网关是什么? - 2

    我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http

  8. ruby-on-rails - carrierwave:在序列化动态属性上安装 uploader - 2

    首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟

  9. ruby - 在 Ruby 中动态生成多维数组 - 2

    我正在尝试动态构建一个多维数组。我想要的基本上是这样的(为简单起见写出来):b=0test=[[]]test[b]这给了我错误:NoMethodError:undefinedmethod`test=[[],[],[]]而且它工作正常,但在我的实际使用中,我不会事先知道需要多少个数组。有一个更好的方法吗?谢谢 最佳答案 不需要像您正在使用的索引变量。只需将每个数组附加到您的test数组:irb>test=[]=>[]irb>test[["a","b","c"]]irb>test[["a","b","c"],["d","e","f"]]

  10. ruby-on-rails - 使用 gmaps4rails 动态加载谷歌地图标记 - 2

    如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail

随机推荐