草庐IT

webgl 系列 —— 绘制一个点(版本2、版本3、版本4、版本5)

彭加李 2023-03-28 原文

其他章节请看:

webgl 系列

绘制一个点

我们初步认识了 webgl,本篇主要围绕绘制一个点的示例,逐步实现下面功能:

  1. 点的位置从 js 传入着色器
  2. 点的大小由 js 传入着色器
  3. 通过鼠标点击绘点
  4. 通过鼠标点击绘点,并改变点的颜色

绘制一个点(版本2)

需求

在上篇中我们在canvas中心绘制了一个点(效果如下),但这点的位置是直接写在顶点着色器中 gl_Position = vec4(0.0, 0.0, 0.0, 1.0);

需求:点的位置从 js 传入着色器

思路

将位置信息从 js 传入着色器有两种方式:attribute 变量、uniform 变量

  • attribute - 传输的是那些与顶点相关的数据
  • uniform - 传输的是那些对于所有顶点都相同的数据

我们这里使用 attribute 变量,因为每个点都有各自的坐标

Tip:GLSL 中有三种类型的“变量”或者说数据存储类型。每一种类型都有特定的目标和使用方法:: attributes、varyings(复杂,暂不介绍)和uniforms —— Data in WebGL

attribute

attribute 是一种着色器语言(GLSL ES)变量,用于向顶点着色器内传输数据,只有顶点着色器能使用它。

使用 attribute 有如下三步:

  1. 在顶点着色器中声明 attribute 变量
  2. 在顶点着色器中将声明的 attribute 变量赋值给 gl_Position 变量
  3. 向 attribute 变量传输数据

实现

完整代码如下:

// point02.js
// 必须要 ;
const VSHADER_SOURCE = `
// 声明 attribute 变量
attribute vec4 a_Position;
void main() {
  // 将声明的 attribute 变量赋值给 gl_Position 变量
  gl_Position = a_Position;
  gl_PointSize = 10.0;               
}
`

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

function main() {
  const canvas = document.getElementById('webgl');

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

  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化着色器失败');
    return;
  }

  // 获取 attribute 变量的存储位置。如果找不到该属性则返回 -1。
  const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('找不到 a_Position 的存储位置');
    return;
  }
  // 为顶点 attibute 变量赋值
  gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);


  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT); 

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

Tip:顶点着色器中必须要写 ;

核心代码:

// 声明 attribute 变量
attribute vec4 a_Position; 
void main() {
  // 将声明的 attribute 变量赋值给 gl_Position 变量
  gl_Position = a_Position;
}

// 获取 attribute 变量的存储位置。如果找不到该属性则返回 -1。
// gl.program - 在 initShaders() 函数中创建了这个程序对象。现在只需知道它是一个参数
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
// 为顶点 attibute 变量赋值
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

Tip:习惯:所有 attribute 的变量以 a_ 开头,所有 uniform 的变量以 u_ 开头。

getAttribLocation

WebGLRenderingContext.getAttribLocation(program, name) 方法返回了给定 WebGLProgram对象 中某属性的下标指向位置。

vertexAttrib3f

vertexAttrib3f 是一系列同族方法中的一个,为顶点 attibute 变量赋值。

一系列方法指:

void gl.vertexAttrib1f(index, v0);
void gl.vertexAttrib2f(index, v0, v1);
// 将数据(v0, v1, v2) 传给 index 指定的 attribute 变量
void gl.vertexAttrib3f(index, v0, v1, v2);
void gl.vertexAttrib4f(index, v0, v1, v2, v3);

// 矢量版本 v(vector)
void gl.vertexAttrib1fv(index, value);
void gl.vertexAttrib2fv(index, value);
void gl.vertexAttrib3fv(index, value);
void gl.vertexAttrib4fv(index, value);
  • gl.vertexAttrib1f 传1个分量。第二第三分量设置为 0.0,第四个分量设置为1.0
  • gl.vertexAttrib3f 传3个分量。第四个分量设置为 1.0,以此类推。

矢量版本以 v 结尾,接受类型化数组。就像这样:

const floatArray = new Float32Array([10.0, 5.0, 2.0]);
gl.vertexAttrib3fv(a_foobar, floatArray);

绘制一个点(版本3)

需求

需求:点的大小由 js 传入着色器 —— 在版本2的基础上实现

实现

和版本2的实现类似:

 const VSHADER_SOURCE = `
 attribute vec4 a_Position;
+attribute float a_PointSize;
 void main() {
   // 将声明的 attribute 变量赋值给 gl_Position 变量
   gl_Position = a_Position;
-  gl_PointSize = 10.0;
+  gl_PointSize = a_PointSize;
 }
 `

function main() {
   gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

+  const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
+  if (a_PointSize < 0) {
+    console.log('找不到 a_PointSize 的存储位置');
+    return;
+  }
+  // 为顶点 attibute 变量赋值
+  gl.vertexAttrib1f(a_PointSize, 10.0);

   gl.clearColor(0.0, 0.0, 0.0, 1.0);
   gl.clear(gl.COLOR_BUFFER_BIT);

绘制一个点(版本4)

需求

需求:通过鼠标点击绘点

效果如下:

实现

完整代码如下:

const VSHADER_SOURCE = `
// 声明 attribute 变量
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
  // 将声明的 attribute 变量赋值给 gl_Position 变量
  gl_Position = a_Position;
  gl_PointSize = a_PointSize;               
}
`

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

function main() {
  const canvas = document.getElementById('webgl');

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

  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化着色器失败');
    return;
  }

  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
  if (a_PointSize < 0) {
    console.log('找不到 a_PointSize 的存储位置');
    return;
  }
  // 为顶点 attibute 变量赋值
  gl.vertexAttrib1f(a_PointSize, 10.0);

  // 获取 attribute 变量的存储位置。如果找不到该属性则返回 -1。
  const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('找不到 a_Position 的存储位置');
    return;
  }

  // 存储所有点
  const points = [];

  // 注册点击事件
  $(canvas).click(event => {
    // 将点击的坐标转为 webgl 坐标系统。
    const rect = event.target.getBoundingClientRect();
    const x = ((event.clientX - rect.left) - canvas.width / 2) / (canvas.width / 2);
    const y = (canvas.height / 2 - (event.clientY - rect.top)) / (canvas.height / 2);
    console.log(x, y)
    // 将点保存
    points.push({ x, y })
    // 注:使用预设值来清空缓冲。如果注释这行,颜色缓冲区会被 webgl 重置为默认的透明色(0.0, 0.0, 0.0, 0.0) —— 必须
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 绘点
    points.forEach(item => {
      // 为顶点 attibute 变量赋值
      // 注:即使 x, y 是整数程序也没问题
      gl.vertexAttrib3f(a_Position, item.x, item.y, 0.0);
      gl.drawArrays(gl.POINTS, 0, 1);
    })
  })
}

核心思路如下:

  • 给 canvas 注册点击事件(这里引入jQuery)
  • 点击时将 (x, y) 转为 webgl 坐标(怎么转换?百度搜索,这不是重点),并将该点存入一个变量 points 中
  • 循环 points 不停绘制点

:循环前得通过 gl.clear 清空绘图区,否则canvas背景会被重置为透明色。

绘制一个点(版本5)

需求

需求:通过鼠标点击绘点,并改变点的颜色 —— 在版本4的基础上实现

效果如下:

思路:颜色从硬编码改成从 js 传入。使用 uniform 变量。

uniform

前面我们用 attribute 向顶点着色器传输顶点的位置,只有顶点着色器才能使用 attribute。

对于片元着色器,需要使用 uniform 变量。

使用 uniform 有如下三步(和 attribute 类似):

  1. 在顶点着色器中声明 uniform 变量
  2. 在顶点着色器中将声明的 uniform 变量赋值给 gl_FragColor 变量
  3. 向 uniform 变量传输数据

实现

完整代码:

const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
  gl_Position = a_Position;
  gl_PointSize = a_PointSize;               
}
`

const FSHADER_SOURCE = `
// 片元着色器必须加上精度描述。否则浏览器报错:No precision specified for (float)
precision mediump float;
// 声明变量
uniform vec4 u_FragColor;
void main() {
  gl_FragColor = u_FragColor;
}
`

function main() {
  const canvas = document.getElementById('webgl');

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

  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化着色器失败');
    return;
  }

  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
  if (a_PointSize < 0) {
    console.log('找不到 a_PointSize 的存储位置');
    return;
  }
  gl.vertexAttrib1f(a_PointSize, 10.0);

  const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('找不到 a_Position 的存储位置');
    return;
  }

  // 获取 uniform 变量的存储位置。如果找不到该属性则返回 -1。
  const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
  if (u_FragColor < 0) {
    console.log('找不到 u_FragColor 的存储位置');
    return;
  }

  const points = [];

  // [0, 1)
  const getColor = () => Number.parseFloat(Math.random())
  
  $(canvas).click(event => {
    const rect = event.target.getBoundingClientRect();
    const x = ((event.clientX - rect.left) - canvas.width / 2) / (canvas.width / 2);
    const y = (canvas.height / 2 - (event.clientY - rect.top)) / (canvas.height / 2);
    
    // 将点的随机颜色
    points.push({ x, y, rgb: [getColor(), getColor(), getColor()] })
    gl.clear(gl.COLOR_BUFFER_BIT);
    points.forEach(item => {
      gl.vertexAttrib3f(a_Position, item.x, item.y, 0.0);
      // 给 unifrom 变量赋值
      gl.uniform4f(u_FragColor, ...item.rgb, 1.0);
      gl.drawArrays(gl.POINTS, 0, 1);
    })
  })
}

核心代码:

const FSHADER_SOURCE = `
// 片元着色器必须加上精度描述。否则浏览器报错:No precision specified for (float)
precision mediump float;
// 声明变量
uniform vec4 u_FragColor;
void main() {
  gl_FragColor = u_FragColor;
}
`

function main() {
  // 获取 uniform 变量的存储位置。如果找不到该属性则返回 -1。
  const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
  if (u_FragColor < 0) {
    console.log('找不到 u_FragColor 的存储位置');
    return;
  }

  $(canvas).click(event => {
    ...
    points.forEach(item => {
      ...
      // 给 unifrom 变量赋值
      gl.uniform4f(u_FragColor, ...item.rgb, 1.0);
      gl.drawArrays(gl.POINTS, 0, 1);
    })
  })
}

:片元着色器必须加上精度描述(例如:precision mediump float;)。否则浏览器报错:No precision specified for (float)

getUniformLocation

getUniformLocationgetAttribLocation 类似,只是这里返回 uniform 变量的位置

uniform4f

有了 uniform 变量的存储地址,就可以使用 uniform4f(和 vertexAttrib3f 很类似) 向变量中写入数据。

其他章节请看:

webgl 系列

有关webgl 系列 —— 绘制一个点(版本2、版本3、版本4、版本5)的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  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 - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  6. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  7. 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中的所有其他对象

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

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

  9. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

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

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

随机推荐