-
第一个 WebGL 程序
-
【P42】 默认情况下,
<canvas>
是透明的 -
【P44】 它不直接提供绘图方法,而是提供一种叫上下文(context)的机制来进行绘图。
-
【P45】 计算机系统通常使用红、绿、蓝这三原色组合来表示颜色,这种颜色表示方式被称为RGB格式,当a (透明度)加进来之后,就成为RGBA格式。
-
<!DOCTYPE html> <html> <head> <script src="index.js"></script> </head> <body onload="main()"> <canvas id="canvas" width="400" height="400"> 你的浏览器不支持 canvas。 </canvas> </body> </html>
function main() { var canvas = document.getElementById('canvas'); // 错误检查 if (!canvas) { alert('无法获取 <canvas> 标签。'); return; } var ctx = canvas.getContext('2d'); // 参数指定 2D/3D ctx.fillRect(120, 10, 150, 150); // 参数 x y width height }
-
-
清空绘图区
- 本节代码
var gl = getWebGLContext(canvas); if (!gl) { alert('无法获取初始化 WebGL。'); return; } gl.clearColor(0.5, 0.5, 0.5, 1.0); gl.clear(gl.COLOR_BUFFER_BIT);
- 书中引用的一些库:
<script src="https://rodger.global-linguist.com/webgl/lib/webgl-utils.js"></script> <script src="https://rodger.global-linguist.com/webgl/lib/webgl-debug.js"></script> <script src="https://rodger.global-linguist.com/webgl/lib/cuon-utils.js"></script>
- 指定背景色
-
gl.clearColor(r, g, b, a) // 三个参数的值都是 [0.0, 1.0]
- 【P50】 一旦指定了背景色之后,背景色就会驻存在WebGL系统(WebGL System)中,在下一次调用gl∙clearcols()方法前不会改变。
-
- 清空
<canvas>
-
gl.clear(gl.COLOR_BUFFER_BIT);
- 【P51】 清空绘图区域,实际上是在清空颜色缓冲区(color buffer),传递参数gi-color_buffer_bit就是在告诉WebGL清空颜色缓冲区。
-
- 本节代码
-
绘制一个点 1(着色器介绍)
- JS 代码
- 不像之前用 canvas 绘制 2D 矩形那样简单,用 WebGL 绘制一个点必须要用着色器。
- WebGL 需要的两种着色器
- 【P55】 顶点着色器(Vertex shader):顶点着色器是用来描述顶点特性(如位置、颜色等)的程序。顶点(vertex)是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点。绘制一个点(版本1) 25
- 【P56】 片元着色器(Fragment shader):进行逐片元处理过程如光照(见第8章“光照”)的程序。片元(fragment)是一个WebGL术语,你可以将其理解为像素(图像的单元)。
- TODO 两者的区别?
- 【P61】 WebGL程序包括运行在浏览器中的JavaScript和运行在WebGL系统的着色器程序这两个部分。
- 【P58】 着色器使用类似于c的OpenGL ES着色器语言(GLSLES)来编写。
- 使用着色器的基本框架
gl = getWebGLContext(); // 获取 WebGL 上下文 initShaders(); // 初始化着色器 gl.clearColor(0, 0, 0, 0) // 设置背景色 // 开始绘图
- 【P61】 顶点着色器先执行,它对gl_Position变量和gl_PointSize变量进行赋值,并将它们传入片元着色器,然后片元着色器再执行。
-
分析着色器
- 顶点着色器:
// `main()` 函数不能有参数,返回值必须为 `void` void main() { // `gl_Position`:内置变量,顶点位置,类型 `vec4`,必选。 // vec4() 函数用于构造一个 vec4 实例。 gl_Position = vec4(0.0, 0.0, 0.0, 1.0); // `gl_PointSize`:内置变量,顶点大小,类型 `float`,可选。 gl_PointSize = 10.0; }
- (PS:OpenGL ES 语言的 Markdown 代码块缩写为
glsl
,但不一定支持。可以用c
作为代替。 )
- (PS:OpenGL ES 语言的 Markdown 代码块缩写为
- 注意:OpenGL ES 中整数不会自动转换为浮点数,
gl_PointSize = 10
会报错! vec4
类型 / 齐次坐标- 【P62】 vec4 表示由四个浮点数组成的矢量
- 【P63】 由4个分量组成的矢量被称为齐次坐标
- 【P63】 齐次坐标(x, y, z, w)等价于三维坐标(x∕w, y∕w, z/w)。所以如果齐次坐标的第4个分量是1,你就可以将它当做三维坐标来使用。
- 片元着色器:
void main() { // `gl_FragColor`:内置变量,顶点颜色,类型 `vec4`。 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }
- 顶点着色器:
-
绘制操作
- 绘制操作使用
gl.drawArrays()
进行。
-
gl.drawArrays(gl.POINTS, 0, 1);
- 绘制操作使用
-
WebGL 坐标系统
- WebGL 使用右手坐标系。X 轴从左到右,Y 轴从下到上,Z 轴从你到屏幕。
- 【P65】 观察者的眼睛位于原点(0Q 0.0, 0.0)处,视线则是沿着Z轴的负方向,从你指向屏幕
- WebGL 中坐标轴的范围是 [-1.0, 1.0]。
-
绘制一个点 2(GLES -> JS 通信)
- attribute 变量和 uniform 变量 能用来实现 GLES -> Javascript 的通信。
- attribute 变量
- 【P70】 关键词attribute被称为存储限定符(storage qualifier
- 【P68】 attribute变量传输的是那些与顶点相关的数据
- 【P68】 attribute变量是一种GLSLES变量,被用来从外部向顶点着色器内传输数据,只有顶点着色器能使用它。
- uniform 变量
- 【P68】 uniform变量传输的是那些对于所有顶点都相同(或与顶点无关)的数据
- attribute 变量
- 【P70】 attribute变量必须声明成全局变量
- 使用 attribute 变量通信的流程:获取变量地址 -> 设置变量值
- GLES 代码:
JS 代码:attribute vec4 aPos; // 外部参数 void main() { gl_Position = aPos; gl_PointSize = 20.0; }
// ... // 初始化 Shader if (!initShaders(gl, VERTEX_SHADER, FRAGMENT_SHADER)) { console.error('无法初始化着色器'); } // 获取 aPos 变量地址 // gl.program 在 initShaders() 调用之后自动创建 var pos = gl.getAttribLocation(gl.program, 'aPos'); // 设置 aPos 的值 gl.vertexAttrib3f(pos, 0.5, 0.5, 0); gl.clearColor(0.5, 0.5, 0.5, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // ...
- #+BEGIN_IMPORTANT
注意:在 GLES 中声明的变量如果不用,会被编译器优化掉,导致在后面的 Javascript 中获取变量地址时失败!
#+END_IMPORTANT gl.vertexAttrib3f()
函数是其一系列函数中的一个3
表示矢量的元素个数,f
表示浮点数,i
表示整数,v
表示传入参数为矢量(数组)。- 如果传入的元素个数 < 目标变量的元素个数,那么
[v2, v3, v4]
的会被赋予默认值[0, 0, 1]
(哪个少就补上哪个的默认值)。 -
gl.vertexAttribf1f(location, v1); gl.vertexAttribf2f(location, v1, v2); gl.vertexAttribf3f(location, v1, v2, v3); gl.vertexAttribf4f(location, v1, v2, v3, v4); gl.vertexAttribf1i(location, v1); gl.vertexAttribf2i(location, v1, v2); gl.vertexAttribf3i(location, v1, v2, v3); gl.vertexAttribf4i(location, v1, v2, v3, v4); gl.vertexAttribf1fv(location, arr); gl.vertexAttribf2fv(location, arr); gl.vertexAttribf3fv(location, arr); gl.vertexAttribf4fv(location, arr);
- attribute 变量和 uniform 变量 能用来实现 GLES -> Javascript 的通信。
-
通过鼠标点击绘点(响应点击事件)
- 在线 Demo
Javascript 源码 -
坐标转换
-
var points = []; // 响应点击事件 canvas.onmousedown = (event) => { console.clear(); // 获取点击坐标 // 转换步骤:页面坐标 -> canvas 坐标 -> WebGL 坐标 // 这个坐标是页面坐标 var x = event.clientX; var y = event.clientY; // 转换为 <canvas> 坐标 // = 页面坐标 - <canvas> 左上角在页面中的坐标 var rect = canvas.getBoundingClientRect(); x = x - rect.left; y = y - rect.top; // 转换为 WebGL 坐标 // 将原点从左上角移到中间位置 console.log(x, y); y = -y; x = x + (-canvas.width / 2); y = y + (canvas.height / 2) // 缩放坐标轴 x /= canvas.width / 2; y /= canvas.height / 2; console.log(x, y); points.push([x, y]); // 画点 points.forEach((value) => { gl.vertexAttrib2fv(locPos, value); gl.drawArrays(gl.POINTS, 0, 1); }); };
- Canvas 坐标 -> WebGL 坐标 变换示意图
(建议放大查看)
-
-
同时画多个点
gl.drawArrays()
函数是可以重复调用的,会在颜色缓冲区上继续绘制,也就是和之前的结果叠加。-
var points = []; canvas.onmousedown = (event) => { // ... console.log(x, y); // 前面是一样的 // 保存坐标 points.push([x, y]); // 画点 gl.clear(gl.COLOR_BUFFER_BIT); points.forEach((value) => { gl.vertexAttrib2fv(locPos, value); gl.drawArrays(gl.POINTS, 0, 1); }); };
- 在线 Demo
-
改变点的颜色
-
在线 Demo
JS 源代码 -
从 Javascript 向片元着色器中传递数据需要用 uniform 变量。
- 【P88】 uniform变量用来从JavaScript程序向顶点着色器和片元着色器传输“一致的”(不变的)数据。
-
gl.uniform4f()
函数和gl.VertexAttrub4f()
函数类似,也是一个系列的函数:
-
GLES 代码(片元着色器):
precision mediump float; // 指定浮点数的精度为中等精度(包括范围和有效小数位) // 第五章讨论精度问题 uniform vec4 uColor; // 外部参数 void main(){ gl_FragColor = uColor; }
JS 代码:
// 获取 uColor 变量地址 var uColor = gl.getUniformLocation(gl.program, 'uColor'); // ... canvas.onmousedown = function (event) { // 画点 gl.clear(gl.COLOR_BUFFER_BIT); points.forEach((value) => { gl.vertexAttrib2fv(aPos, value); // 设置颜色(uColor) // 这里每次绘制都随机生成颜色 var uColor = gl.getUniformLocation(gl.program, 'uColor'); gl.drawArrays(gl.POINTS, 0, 1); }); } gl.clear(gl.COLOR_BUFFER_BIT); points.forEach((value) => { gl.vertexAttrib2fv(aPos, value); // 设置颜色(uColor) // 这里每次绘制都随机生成颜色 var uColor = gl.getUniformLocation(gl.program, 'uColor'); gl.drawArrays(gl.POINTS, 0, 1); }); }
-