经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » WebGL » 查看文章
webgl 系列 —— 渐变三角形
来源:cnblogs  作者:彭加李  时间:2023/3/15 8:54:04  对本文有异议

其他章节请看:

webgl 系列

渐变三角形

本文通过一个渐变三角形的示例逐步分析:varying变量、合并缓冲区、图形装配光栅化varying 内插

绘制三个点v1

需求:绘制三个相同颜色的点,效果如下:

通过三角形的学习,这个需求非常容易实现。代码如下:

  1. const VSHADER_SOURCE = `
  2. attribute vec4 a_Position;
  3. void main() {
  4. gl_Position = a_Position;
  5. gl_PointSize = 10.0;
  6. }
  7. `
  8. const FSHADER_SOURCE = `
  9. void main() {
  10. gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  11. }
  12. `
  13. function main() {
  14. const canvas = document.getElementById('webgl');
  15. const gl = canvas.getContext("webgl");
  16. if (!gl) {
  17. console.log('Failed to get the rendering context for WebGL');
  18. return;
  19. }
  20. if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  21. console.log('Failed to intialize shaders.');
  22. return;
  23. }
  24. gl.clearColor(0, 0, 0, 1);
  25. gl.clear(gl.COLOR_BUFFER_BIT);
  26. const vertices = {
  27. data: new Float32Array([
  28. 0.0, 0.5,
  29. -0.5, -0.5,
  30. 0.5, -0.5
  31. ]),
  32. vertexNumber: 3,
  33. count: 2,
  34. }
  35. initVertexBuffers(gl, vertices)
  36. gl.drawArrays(gl.POINTS, 0, vertices.vertexNumber);
  37. }
  38. function initVertexBuffers(gl, {data, count}) {
  39. const vertexBuffer = gl.createBuffer();
  40. if (!vertexBuffer) {
  41. console.log('创建缓冲区对象失败');
  42. return -1;
  43. }
  44. gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  45. gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  46. const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  47. if (a_Position < 0) {
  48. console.log('Failed to get the storage location of a_Position');
  49. return -1;
  50. }
  51. gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, 0, 0);
  52. gl.enableVertexAttribArray(a_Position);
  53. }

绘制三个点v2

需求

需求:绘制三个不同颜色的点(基于版本1),效果如下:

Tip: 绘制三个点不同颜色的点其实也就完成了渐变三角形的绘制。这里调用了两次 drawArrays(),也就是绘制了两个图元,一系列点、三角形。

核心代码

相对版本1,变化的代码如下:

  1. const VSHADER_SOURCE = `
  2. attribute vec4 a_Position;
  3. +attribute vec4 a_Color;
  4. +varying vec4 v_Color;
  5. void main() {
  6. gl_Position = a_Position;
  7. - gl_PointSize = 10.0;
  8. + gl_PointSize = 10.0;
  9. + v_Color = a_Color;
  10. }
  11. `
  12. const FSHADER_SOURCE = `
  13. +precision mediump float;
  14. +varying vec4 v_Color;
  15. void main() {
  16. - gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  17. + gl_FragColor = v_Color;
  18. }
  19. `
  20. function main() {
  21. const vertices = {
  22. data: new Float32Array([
  23. - 0.0, 0.5,
  24. - -0.5, -0.5,
  25. - 0.5, -0.5
  26. + 0.0, 0.5, 1.0, 0.0, 0.0,
  27. + -0.5, -0.5, 0.0, 1.0, 0.0,
  28. + 0.5, -0.5, 0.0, 0.0, 1.0,
  29. ]),
  30. vertexNumber: 3,
  31. count: 2,
  32. initVertexBuffers(gl, vertices)
  33. gl.drawArrays(gl.POINTS, 0, vertices.vertexNumber);
  34. + gl.drawArrays(gl.TRIANGLE_FAN, 0, vertices.vertexNumber);
  35. }
  36. function initVertexBuffers(gl, {data, count}) {
  37. const vertexBuffer = gl.createBuffer();
  38. - gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, 0, 0);
  39. + const FSIZE = data.BYTES_PER_ELEMENT;
  40. + gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, FSIZE * 5, 0);
  41. gl.enableVertexAttribArray(a_Position);
  42. + const a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  43. + if (a_Color < 0) {
  44. + console.log('Failed to get the storage location of a_Color');
  45. + return -1;
  46. + }
  47. + gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
  48. + gl.enableVertexAttribArray(a_Color);
  49. }

完整代码

  1. const VSHADER_SOURCE = `
  2. attribute vec4 a_Position;
  3. attribute vec4 a_Color;
  4. varying vec4 v_Color;
  5. void main() {
  6. gl_Position = a_Position;
  7. gl_PointSize = 10.0;
  8. v_Color = a_Color;
  9. }
  10. `
  11. const FSHADER_SOURCE = `
  12. precision mediump float;
  13. varying vec4 v_Color;
  14. void main() {
  15. gl_FragColor = v_Color;
  16. }
  17. `
  18. function main() {
  19. const canvas = document.getElementById('webgl');
  20. const gl = canvas.getContext("webgl");
  21. if (!gl) {
  22. console.log('Failed to get the rendering context for WebGL');
  23. return;
  24. }
  25. if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  26. console.log('Failed to intialize shaders.');
  27. return;
  28. }
  29. gl.clearColor(0, 0, 0, 1);
  30. gl.clear(gl.COLOR_BUFFER_BIT);
  31. const vertices = {
  32. data: new Float32Array([
  33. 0.0, 0.5, 1.0, 0.0, 0.0,
  34. -0.5, -0.5, 0.0, 1.0, 0.0,
  35. 0.5, -0.5, 0.0, 0.0, 1.0,
  36. ]),
  37. vertexNumber: 3,
  38. count: 2,
  39. }
  40. initVertexBuffers(gl, vertices)
  41. gl.drawArrays(gl.POINTS, 0, vertices.vertexNumber);
  42. gl.drawArrays(gl.TRIANGLE_FAN, 0, vertices.vertexNumber);
  43. }
  44. function initVertexBuffers(gl, { data, count }) {
  45. const vertexBuffer = gl.createBuffer();
  46. if (!vertexBuffer) {
  47. console.log('创建缓冲区对象失败');
  48. return -1;
  49. }
  50. gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  51. gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  52. const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  53. if (a_Position < 0) {
  54. console.log('Failed to get the storage location of a_Position');
  55. return -1;
  56. }
  57. const FSIZE = data.BYTES_PER_ELEMENT;
  58. gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, FSIZE * 5, 0);
  59. gl.enableVertexAttribArray(a_Position);
  60. const a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  61. if (a_Color < 0) {
  62. console.log('Failed to get the storage location of a_Color');
  63. return -1;
  64. }
  65. gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
  66. gl.enableVertexAttribArray(a_Color);
  67. }

改变颜色(varying)

前面我们说过着色器语言(GLSL ES)有三种类型的“变量”,我们已经使用了两种:

  • attribute - 传输的是那些与顶点相关的数据。只有顶点着色器才能使用。例如顶点的位置、大小、颜色
  • uniform - 传输的是那些对于所有顶点都相同的数据。例如变化矩阵

现在我们可以将颜色从 js 传入 attribute。但真正影响颜色绘制的是片元着色器的 gl_FragColor,目前我们是静态设置。就像这样:

  1. const FSHADER_SOURCE = `
  2. void main() {
  3. gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  4. }
  5. `

如何将顶点着色器中的数据传入片元着色器?

我们曾经通过 uniform 给片元着色器传递颜色。就像这样:

  1. const FSHADER_SOURCE = `
  2. uniform vec4 u_FragColor;
  3. void main() {
  4. gl_FragColor = u_FragColor;
  5. }
  6. `

但是 uniform 是相同的的变量,没法为每个顶点准备一个值。为了让每个点的颜色不同,需要使用varying(不同的)变量。

使用 varying 给片元着色器传递值(颜色)。就像这样:

  1. const VSHADER_SOURCE = `
  2. // 定义一个 attribute 变量,用于接收 js 传入的颜色
  3. attribute vec4 a_Color;
  4. // 定义 varying 变量。用于传递给片元着色器
  5. varying vec4 v_Color;
  6. void main() {
  7. gl_Position = a_Position;
  8. gl_PointSize = 10.0;
  9. // 给 varying 变量赋值
  10. v_Color = a_Color;
  11. }
  12. `
  13. const FSHADER_SOURCE = `
  14. precision mediump float;
  15. // 声明一个与顶点着色器中相同的 varying 变量名,用于接收颜色
  16. varying vec4 v_Color;
  17. void main() {
  18. gl_FragColor = v_Color;
  19. }
  20. `

代码解析:

  • 通过在顶点着色器中声明一个 attribute 变量用于接收 js 传入的颜色
  • 在顶点着色器中声明一个 varying 变量,用于接收 attribute 中的颜色,并将颜色传给片元着色器
  • 片元着色器声明一个与顶点着色器中相同的 varying 变量名,接收颜色

Tip:顶点着色器中的 varying 变量 v_Color 与 片元着色器中的 varying 变量 v_Color 不同。中间涉及 varying 内插,下文会介绍。

合并缓冲区

渐变三角形将顶点和每个顶点的颜色写在一起,数据结构如下:

  1. data: new Float32Array([
  2. - 0.0, 0.5,
  3. - -0.5, -0.5,
  4. - 0.5, -0.5
  5. + 0.0, 0.5, 1.0, 0.0, 0.0,
  6. + -0.5, -0.5, 0.0, 1.0, 0.0,
  7. + 0.5, -0.5, 0.0, 0.0, 1.0,
  8. ]),

在渐变三角形示例中我们只用了一个缓冲区对象(const vertexBuffer = gl.createBuffer();),当然也可以使用两个缓冲区对象来实现相同的效果。核心代码如下:

  1. // 声明第二个缓冲区对象:颜色缓冲区
  2. const vertexColorBuffer = gl.createBuffer();
  3. if (!vertexColorBuffer) {
  4. console.log('创建缓冲区对象失败');
  5. return -1;
  6. }
  7. gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
  8. // 颜色数据抽离出来
  9. const colors = new Float32Array([
  10. 1.0, 0.0, 0.0,
  11. 0.0, 1.0, 0.0,
  12. 0.0, 0.0, 1.0,
  13. ]);
  14. gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
  15. const a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  16. if (a_Color < 0) {
  17. console.log('Failed to get the storage location of a_Color');
  18. return -1;
  19. }
  20. gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
  21. gl.enableVertexAttribArray(a_Color);

将多个缓冲区合并,代码更简洁思路

  • 首先将顶点位置和颜色写在一个数组中
  • 然后通过 vertexAttribPointer() 来读取不同的信息(顶点位置、颜色)。

请看代码:

  1. const vertices = {
  2. // 顶点位置和颜色写在一起
  3. data: new Float32Array([
  4. 0.0, 0.5, 1.0, 0.0, 0.0,
  5. -0.5, -0.5, 0.0, 1.0, 0.0,
  6. 0.5, -0.5, 0.0, 0.0, 1.0,
  7. ]),
  8. vertexNumber: 3,
  9. count: 2,
  10. }
  11. // 每个元素所占用的字节数
  12. const FSIZE = data.BYTES_PER_ELEMENT;
  13. // FSIZE * 5 - 指定每个点的字节数
  14. // 0 - 偏移量
  15. gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, FSIZE * 5, 0);
  16. /*
  17. 提取颜色:
  18. 3 - 分量数
  19. FSIZE * 2 - 偏移量,从第三个开始
  20. */
  21. gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);

例如提取颜色:每个点总共字节数是 FSIZE * 5,颜色占3个分量,从第三(FSIZE * 2)个数开始读取 3 个分量。

为什么是渐变

我们定义了三个不同颜色的点,绘制出来的三角形为什么却是渐变色彩?

要回答这个问题,需要说一下整个绘制过程。

请看下图:

  • 首先确定顶点坐标,我们传了三个顶点
  • 接着将孤立的顶点坐标装配成几何图形。几何图形的类别由 drawArrays() 第一个参数决定
  • 将装配好的几何图形转为片元(简单认为是像素,这里为了示意,只显示了10个片元),这个过程称为光栅化

图形装配光栅化过程如下图所示:

一旦光栅化结束,程序就开始逐片元调用片元着色器。这里调用了10次,每调用一次就处理一个片元。对于每个片元,片元着色器计算出该片元的颜色,并写入颜色缓冲区,当最后一个片元被处理完成,浏览器就会显示最终结果。就像这样:

渐变其实是由 varying 变量的内插导致的。比如绘制一条线,一端是红色,一端是蓝色,我们在顶点着色器向 varying 变量 v_Color 赋上两个颜色,webgl 会计算出线段上所有点(片元)的颜色,并赋值给片元着色器中的 varying 变量 v_Color。就像这样:

顶点着色器中的 v_Color 和片元着色器中的 v_Color 不是一回事。示意如下:

其他章节请看:

webgl 系列

原文链接:https://www.cnblogs.com/pengjiali/p/17216109.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号