绘制一个点

我们初步认识了 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 系列 —— 绘制一个点(版本2、版本3、版本4、版本5)的更多相关文章

  1. Atitit.播放系统规划新版本 and 最近版本回顾 v3  pbf.doc  1 版本11 (ing)41.1 规划h5本地缓存系列 41.2 Android版本app41.3 双类别系统,

    Atitit.播放系统规划新版本 and 最近版本回顾 v3  pbf.doc 1 版本11 (ing)4 1.1 规划h5本地缓存系列 4 1.2 Android版本app4 1.3 双类别系统, ...

  2. WebGL编程指南案例解析之绘制一个点

    <!DOCTYPE html> <html> <head> <title>webgl</title> <style type=&quo ...

  3. Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本

    Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本 前言 序言 再高大上的框架,也需要扎实的基础才能玩转,高频面试问题更是基础中的高频实战要点. 适合阅读人群 J ...

  4. js,onblur后下一个控件获取焦点判断、html当前活跃控件、jquery版本查看、jquery查看浏览器版本、setTimeout&setInterval

    需求: input控件在失去焦点后直接做验证,验证通不过的话,显示相应错误.但是如果失去焦点后点击的下个控件是比较特殊的控件(比如,退出系统),那么不执行验证操作,直接退出系统(防止在系统退出前,还显 ...

  5. Opentk教程系列-1绘制一个三角形

    本系列教程翻译自Neo Kabuto's Blog.已经取得作者授权. 本文原文地址http://neokabuto.blogspot.com/2013/02/opentk-tutorial-1-op ...

  6. C#的Installer生成的msi的安装文件,安装新版本时提示:已经安装了该产品的另一个版本。无法继续安装此版本

    之前折腾了个C#的项目: WLW (Windows Live Writer) Plugin–InsertSkydriveFiles 然后又弄了个对应的Installer: [已解决]给一个C#的Dll ...

  7. WebGL简易教程(三):绘制一个三角形(缓冲区对象)

    目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...

  8. WebGL简易教程(七):绘制一个矩形体

    目录 1. 概述 2. 示例 2.1. 顶点索引绘制 2.2. MVP矩阵设置 2.2.1. 模型矩阵 2.2.2. 投影矩阵 2.2.3. 视图矩阵 2.2.4. MVP矩阵 3. 结果 4. 参考 ...

  9. [Modern OpenGL系列(三)]用OpenGL绘制一个三角形

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...

  10. OpenGl 绘制一个立方体

    OpenGl 绘制一个立方体 为了绘制六个正方形,我们为每个正方形指定四个顶点,最终我们需要指定6*4=24个顶点.但是我们知道,一个立方体其实总共只有八个顶点,要指定24次,就意味着每个顶点其实重复 ...

随机推荐

  1. 过两年 JVM 可能就要被 GraalVM 替代了

    大家好,我是风筝,公众号「古时的风筝」,专注于 Java技术 及周边生态. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 今天说一说 Graal ...

  2. [OpenCV实战]50 用OpenCV制作低成本立体相机

    本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...

  3. Zotero自定义引文样式

    注意 在实际使用中发现还是有许多与要求不同的地方,之后会再次进行修改,特此记录 -----2022/11/28 16:57 目标格式: 期刊:[序号]作者.题名[J].刊名,出版年份,卷号 ( 期号 ...

  4. 複合語句塊——關於while循環的

    看這兩個小段代碼: /*code1.*/ index=0; while(index<10) sam = 10*index+2; printf("sam=%d\n",sam); ...

  5. 动力节点—day06

    常用类 String String表示字符串类型,属于引用数据类型,不属于基本数据类型 在Java中随便使用双引号括起来的都是String对象,例如"abc"."def& ...

  6. MySQL8.0性能优化(实践)

    一台几年前的旧笔记本电脑的虚拟系统运行环境,作为本次实践的运行工具,仅供参考. 案例环境:Linux.Docker.MySQLCommunity8.0.31.InnoDB. 过早的MySQL版本不一定 ...

  7. Java 进阶P-3.5+P-3.6

    对象数组的for-each循环 for-each是用于遍历数组的另一种形式的for循环.for-each循环显着减少了代码,并且循环中没有使用索引或计数器. 句法: For(<数组/列表的数据类 ...

  8. ASCLL编码器-算术运算符_四则与取模运算

    ASCLL编码器 public static void main(String[] args) { //字符类型变量 char c = 'a'; int i = 1; //字符类型和int类型计算 S ...

  9. python语法进阶这一篇就够了

    前言 前面我们已经学习了Python的基础语法,了解了Python的分支结构,也就是选择结构.循环结构以及函数这些具体的框架,还学习了列表.元组.字典.字符串这些Python中特有的数据结构,还用这些 ...

  10. 关于Spring的IoC容器,你了解多少

    IoC的基本概念   Ioc的全称是Inversion of Control,中文通常翻译为"控制反转".好莱坞原则"Dont't call us, we will ca ...