绘制一个点

我们初步认识了 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 很类似) 向变量中写入数据。

最新文章

  1. HOLOTOOLKIT的使用
  2. Ionic 常见问题及解决方案
  3. java入门之从C#快速入门java
  4. ArcGIS的许可文件问题
  5. STATIC::含义
  6. Spring 框架概述
  7. 牛课--C/C++
  8. 错误“Unexpected namespace prefix &quot;xmlns&quot; found for tag LinearLayout”的解决方法
  9. JS常用的7中跨域方式总结
  10. hdu 1240 Asteroids! (三维bfs)
  11. html5 canvas 实现一个简单的叮当猫头部
  12. 文档数据库RavenDB-介绍与初体验
  13. 实现我博客旁边的线条效果 html canvas-nest.js 源码
  14. HA机制下的Hadoop配置
  15. 学习1__STM32--FatFS之逻辑盘符与物理盘符
  16. 严重: A child container failed during start的问题解决方法
  17. golang学习笔记11 golang要用jetbrain的golang这个IDE工具开发才好
  18. Linux中.rar文件解压
  19. jstack实战死循环与死锁学习笔记
  20. javascript 字面量 和变量 之间的关系

热门文章

  1. hook详解和应用
  2. .NET 云原生架构师训练营(基于 OP Storming 和 Actor 的大型分布式架构一)--学习笔记
  3. 一篇文章教你实战Docker容器数据卷
  4. 【机器学习】李宏毅——Explainable ML(可解释性的机器学习)
  5. 【转载】EXCEL VBA 同时选择多个工作表的方法
  6. Windows关闭网络防火墙教程
  7. day14-功能实现13
  8. [编程基础] Python日志记录库logging总结
  9. 使用插件式开发称重仪表驱动,RS232串口对接各类地磅秤数据实现ERP管理
  10. 02安装一个最小化的Hadoop