「译」WebGL 系列 03 着色器 uniforms 基础

时间:2020-9-6 作者:admin

这是 WebGL 系列的第 3 天教程,每天都有新文章发布。

订阅或者加入邮件列表以便及时获取更新内容。

源代码在这里

第 2 天我们画出了简单的基础元素:点。首先解决昨天布置的作业:

我们需要删除硬编码的点数据

📄 src/webgl-hello-world.js

const positionPointer = gl.getAttribLocation(program, 'position');

- const positionData = new Float32Array([
-     -1.0, // top left x
-     -1.0, // top left y
- 
-     1.0, // point 2 x
-     1.0, // point 2 y
- 
-     -1.0, // point 3 x
-     1.0, // point 3 y
- 
-     1.0, // point 4 x
-     -1.0, // point 4 y
- ]);
+ const points = [];
+ const positionData = new Float32Array(points);

  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);

遍历画布的每个垂直像素线 [0..width]

📄 src/webgl-hello-world.js

const positionPointer = gl.getAttribLocation(program, 'position');

  const points = [];
+ 
+ for (let i = 0; i < canvas.width; i++) {
+ 
+ }
+ 
  const positionData = new Float32Array(points);

  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);

将值从 [0..width]转换为 [-1..1](还记得 webgl 协调网格吗?这是最左边和最右边的坐标)

📄 src/webgl-hello-world.js

const points = [];

  for (let i = 0; i < canvas.width; i++) {
- 
+     const x = i / canvas.width * 2 - 1;
  }

  const positionData = new Float32Array(points);

计算 cos并添加对应的 x 和 y 到 points数组

📄 src/webgl-hello-world.js

for (let i = 0; i < canvas.width; i++) {
      const x = i / canvas.width * 2 - 1;
+     const y = Math.cos(x * Math.PI);
+ 
+     points.push(x, y);
  }

  const positionData = new Float32Array(points);

图看起来有点怪异,让我们修复顶点着色器

📄 src/webgl-hello-world.js

attribute vec2 position;

  void main() {
-     gl_PointSize = 20.0;
-     gl_Position = vec4(position / 2.0, 0, 1);
+     gl_PointSize = 2.0;
+     gl_Position = vec4(position, 0, 1);
  }
  `;

非常棒😎我们现在有精美的 cos 图!

「译」WebGL 系列 03 着色器 uniforms 基础

我们使用 JavaScript 进行 cos计算了,但是如果我们需要为大型数据集计算一些内容,则javascript可能会阻塞渲染线程。为什么不提高GPU的计算能力(将并行计算每个点的余弦)?

GLSL 没有 Math名称空间,因此我们需要在其中定义 M_PI变量
cos函数显然是不够的

📄 src/webgl-hello-world.js

const vShaderSource = `
  attribute vec2 position;

+ #define M_PI 3.1415926535897932384626433832795
+ 
  void main() {
      gl_PointSize = 2.0;
-     gl_Position = vec4(position, 0, 1);
+     gl_Position = vec4(position.x, cos(position.y * M_PI), 0, 1);
  }
  `;


  for (let i = 0; i < canvas.width; i++) {
      const x = i / canvas.width * 2 - 1;
-     const y = Math.cos(x * Math.PI);
- 
-     points.push(x, y);
+     points.push(x, x);
  }

  const positionData = new Float32Array(points);

我们在循环内还有另一个 JavaScript 计算,在该循环中我们将像素坐标转换为 [-1..1]范围,
如何将其移动到 GPU?
我们了解到可以使用来将一些数据传递给着色器 attribute,但是 width它是固定的,在各点之间不会改变。

有一种特殊的变量 – uniforms。把 uniform视为全局变量,在绘制调用之前只能分配一次,并且对于所有“点”均保持不变。

让我们定义一个 uniform

📄 src/webgl-hello-world.js

const vShaderSource = `
  attribute vec2 position;
+ uniform float width;

  #define M_PI 3.1415926535897932384626433832795

要将值分配给 uniform,我们需要执行与属性相似的操作,为此需要获取 uniform的位置。

📄 src/webgl-hello-world.js

gl.useProgram(program);

  const positionPointer = gl.getAttribLocation(program, 'position');
+ const widthUniformLocation = gl.getUniformLocation(program, 'width');

  const points = [];

有很多方法可以为 uniform分配不同类型的值

  • gl.uniform1f– float uniform 赋值(gl.uniform1f(0.0))
  • gl.uniform1fv– 将长度为 1 的数组分配给 float uniform (gl.uniform1fv([0.0]))
  • gl.uniform2f– 为 vec2 uniform 分配两个数值(gl.uniform2f(0.0, 1.0))
  • gl.uniform2f– 将长度为 2 的数组分配给 vec2 uniform (gl.uniform2fv([0.0, 1.0]))

等等

📄 src/webgl-hello-world.js

const positionPointer = gl.getAttribLocation(program, 'position');
  const widthUniformLocation = gl.getUniformLocation(program, 'width');

+ gl.uniform1f(widthUniformLocation, canvas.width);
+ 
  const points = [];

  for (let i = 0; i < canvas.width; i++) {

最后,让我们将 js 计数器移至着色器

📄 src/webgl-hello-world.js

#define M_PI 3.1415926535897932384626433832795

  void main() {
+     float x = position.x / width * 2.0 - 1.0;
      gl_PointSize = 2.0;
-     gl_Position = vec4(position.x, cos(position.y * M_PI), 0, 1);
+     gl_Position = vec4(x, cos(x * M_PI), 0, 1);
  }
  `;

  const points = [];

  for (let i = 0; i < canvas.width; i++) {
-     const x = i / canvas.width * 2 - 1;
-     points.push(x, x);
+     points.push(i, i);
  }

  const positionData = new Float32Array(points);

渲染线条

现在让我们尝试渲染线条

我们需要用直线的起点和终点坐标填充位置数据

📄 src/webgl-hello-world.js

gl.uniform1f(widthUniformLocation, canvas.width);

- const points = [];
+ const lines = [];
+ let prevLineY = 0;

- for (let i = 0; i < canvas.width; i++) {
-     points.push(i, i);
+ for (let i = 0; i < canvas.width - 5; i += 5) {
+     lines.push(i, prevLineY);
+     const y =  Math.random() * canvas.height;
+     lines.push(i + 5, y);
+ 
+     prevLineY = y;
  }

- const positionData = new Float32Array(points);
+ const positionData = new Float32Array(lines);

  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);

我们还需要转换 y到一个 WebGL 剪辑空间,为此我们需要传递画布的分辨率,而不仅仅是宽度

📄 src/webgl-hello-world.js

const vShaderSource = `
  attribute vec2 position;
- uniform float width;
+ uniform vec2 resolution;

  #define M_PI 3.1415926535897932384626433832795

  void main() {
-     float x = position.x / width * 2.0 - 1.0;
+     vec2 transformedPosition = position / resolution * 2.0 - 1.0;
      gl_PointSize = 2.0;
-     gl_Position = vec4(x, cos(x * M_PI), 0, 1);
+     gl_Position = vec4(transformedPosition, 0, 1);
  }
  `;

  gl.useProgram(program);

  const positionPointer = gl.getAttribLocation(program, 'position');
- const widthUniformLocation = gl.getUniformLocation(program, 'width');
+ const resolutionUniformLocation = gl.getUniformLocation(program, 'resolution');

- gl.uniform1f(widthUniformLocation, canvas.width);
+ gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);

  const lines = [];
  let prevLineY = 0;

最后一件事–我们需要将原始类型更改为 gl.LINES

📄 src/webgl-hello-world.js

gl.enableVertexAttribArray(positionPointer);
  gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);

- gl.drawArrays(gl.POINTS, 0, positionData.length / 2);
+ gl.drawArrays(gl.LINES, 0, positionData.length / 2);

酷! 我们现在可以渲染线条 👍

「译」WebGL 系列 03 着色器 uniforms 基础

让我们尝试使线更粗

与点大小不同,线宽应通过 JavaScript 设置。有一种方法 gl.lineWidth(width)

让我们尝试使用它

📄 src/webgl-hello-world.js

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);
+ gl.lineWidth(10);

  const attributeSize = 2;
  const type = gl.FLOAT;

没什么改变😢但是为什么呢?

那是因为浏览器不支持😂

「译」WebGL 系列 03 着色器 uniforms 基础

没人在乎。

因此,如果您需要一条带有定制线帽的花哨的线 – gl.LINES不适合您

但是我们如何渲染花哨的线条呢?

事实证明一切都可以在接下要讲的一个 WebGL 原语的帮助下呈现 – 三角形。
这是可以用WebGL渲染的最后一个原语。

从三角形构建自定义宽度的线似乎是一项艰巨的任务,但是不用担心,有很多软件包可以帮助您渲染自定义 2d形状(甚至是 svg

其中一些工具:

还有一些其它的

从现在开始,请记住:一切都可以用三角形构建,这就是渲染的工作方式

  1. 输入 – 三角形顶点
  2. 顶点着色器 – 将顶点转换为webgl剪贴空间
  3. 栅格化 – 计算特定三角形内的像素
  4. 计算每个像素的颜色

这是来自 https://www.tutorialspoint.com/webgl/images/webgl_graphics_pipeline.jpg的说明。

「译」WebGL 系列 03 着色器 uniforms 基础

Disclamer: 这是幕后工作的简化版本,请阅读此内容以获取更详细的说明。

所以让我们最后渲染一个三角形

再次 – 我们需要更新位置数据

并更改原始类型

📄 src/webgl-hello-world.js

gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);

- const lines = [];
- let prevLineY = 0;
+ const triangles = [
+     0, 0, // v1 (x, y)
+     canvas.width / 2, canvas.height, // v2 (x, y)
+     canvas.width, 0, // v3 (x, y)
+ ];

- for (let i = 0; i < canvas.width - 5; i += 5) {
-     lines.push(i, prevLineY);
-     const y =  Math.random() * canvas.height;
-     lines.push(i + 5, y);
- 
-     prevLineY = y;
- }
- 
- const positionData = new Float32Array(lines);
+ const positionData = new Float32Array(triangles);

  const positionBuffer = gl.createBuffer(gl.ARRAY_BUFFER);

  gl.enableVertexAttribArray(positionPointer);
  gl.vertexAttribPointer(positionPointer, attributeSize, type, nomralized, stride, offset);

- gl.drawArrays(gl.LINES, 0, positionData.length / 2);
+ gl.drawArrays(gl.TRIANGLES, 0, positionData.length / 2);

还有一件事…让我们通过 JavaScript 传递颜色,而不是在片段着色器中对其进行硬编码。

我们需要执行与解析统一方法相同的步骤,但是在片段着色器中声明此统一方法

📄 src/webgl-hello-world.js

`;

  const fShaderSource = `
+     uniform vec4 color;
+ 
      void main() {
-         gl_FragColor = vec4(1, 0, 0, 1);
+         gl_FragColor = color / 255.0;
      }
  `;


  const positionPointer = gl.getAttribLocation(program, 'position');
  const resolutionUniformLocation = gl.getUniformLocation(program, 'resolution');
+ const colorUniformLocation = gl.getUniformLocation(program, 'color');

  gl.uniform2fv(resolutionUniformLocation, [canvas.width, canvas.height]);
+ gl.uniform4fv(colorUniformLocation, [255, 0, 0, 255]);

  const triangles = [
      0, 0, // v1 (x, y)

等一下,出现错误了🛑 😱

No precision specified for (float)

那是什么?

事实证明 glsl着色器支持不同的 float精度,您需要指定它。
通常 mediump既高效又精确,但有时您可能想使用 lowphighp。但请注意,某些移动 GPU 不支持 highp,并且不能保证得到你想要的结果。

📄 src/webgl-hello-world.js

`;

  const fShaderSource = `
+     precision mediump float;
      uniform vec4 color;

      void main() {

作业

使用三角形渲染不同的形状:

  • 长方形
  • 六边形
  • 园圆

我们明天见 👋

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。