这是面向前端的图形学课程。心愿用通俗的语言为大家解释清图形学的一些概念。咱们应用的前端最容易 webgl,尽管未免还是要接触 GLSL,别打我,这曾经是最简略的!
咱们开始第一节,矩阵 …. 不还是画个正方形吧!
webgl 是什么?
说人话 webgl 就是个工具,能够拿来画图的,它依赖于 canvas,在 canvas 上你能够获取 2d 的上下文,也能够获取 webgl 的上下文。相似宝可梦新手村能够选蒜头王八也能够选黄皮耗子
所以第一步咱们就创立一个 canvas,并获取 webgl 上下文
<canvas id="canvas" width="1000" height="1000"></canvas>
<script>
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
</script>
开始擦滑板咯!小画家
这样咱们就获取了 canvas 的掌控权,canvas 相当于一块画布,在画图之前咱们得像保障画面是洁净的。
gl 提供了两个 api 来做这件事:gl.clearColor
与gl.clear
gl.clearColor
承受 (r,g,b,a)
的色彩数据,相当于给画布选一个底色;gl.clear
用来革除 buffer,至于什么是 buffer 咱们前面会讲。这里只须要晓得 gl 在没次绘图时都能够记录色彩和深度两个 buffer,再次绘制时须要革除!
定点着色器和片段着色器
着色器是足以燃烧你学习 webgl 的激情的魔鬼。但了解之后你也会感觉恍然大悟!首先三维空间的物体不论多简单都是一些集合体,所谓点动成线,线动成面,面动成体。
所以点是形容空间物体的最小单元。
定点着色器就是用来解决咱们传入的顶点的,在下节课咱们将应用顶点着色器对三角形进行旋转,缩放,平移等操作。
而与之绝对的是片段着色器,它次要解决围成图形的上色工作。
这并不是明确的定义,但有助于你的了解:顶点着色器提供裁剪空间坐标值
而片断着色器提供色彩值
着色器的语言与 js 不同是一品种 c 语言,就是大学计算机学的那种须要 main 函数的语言。所以作色器的语法总结起来就是
// 各种变量 balabala
void main() {// 各种操作 balabala}
上文咱们说过能够把变量传入着色器,因为作色器当初咱们来说下常见的变量
- Attributes(属性):属性用来指明怎么从缓冲中获取所需数据并将它提供给顶点着色器。例如你可能在缓冲中用三个 32 位的浮点型数据存储一个地位值。
- buffer(缓冲):缓冲是发送到 GPU 的一些二进制数据序列,通常状况下缓冲数据包含地位,法向量,纹理坐标,顶点色彩值等。你能够存储任何数据。
- Uniforms(全局变量):全局变量在着色程序运行前赋值,在运行过程中全局无效。
- Textures(纹理):纹理是一个数据序列,能够在着色程序运行中随便读取其中的数据。大多数状况咱们不会本人写材质,就间接用纹理了。
- Varyings(可变量):可变量是一种顶点着色器给片断着色器传值的形式,按照渲染的图元是点,线还是三角形,顶点着色器中设置的可变量会在片断着色器运行中获取不同的插值。
上面咱们来写一个最简略的顶点作色器
gl 承受一个字符串的作色器代码,你能够应用数组或任意模式提供这个字符串,我集体通常写在一个 script 标签中:
<script id="vertex-shader-2d" type="notjs">
// 一个属性变量,将会从缓冲中获取数据
attribute vec2 vertPosition;
attribute vec3 vertColor;
varying vec3 fragColor;
// 所有着色器都有一个 main 办法
void main() {
fragColor = vertColor;
// gl_Position 是一个顶点着色器次要设置的变量
gl_Position = vec4(vertPosition,0.0,1.0);
}
</script>
这里咱们定义了两个属性 vertPosition
与vertColor
用来存储传入的地位与色彩信息,可变量 fragColor
会持续传递给片段着色器应用。
内置变量 gl_Position
的值是四维向量 vec4(x,y,z,1.0)
, 前三个参数示意顶点的 xyz 坐标值,第四个参数是浮点数1.0
因为案例中咱们只须要绘制一个立体内的三角形,所以 z 值被咱们设为 0。
与之绝对咱们也能写出片段着色器:
<script id="fragment-shader-2d" type="notjs">
// 片断着色器没有默认精度,所以咱们须要设置一个精度
// mediump 是一个不错的默认值,代表“medium precision”(中等精度)precision mediump float;
varying vec3 fragColor;
void main() {
// gl_FragColor 是一个片断着色器次要设置的变量
gl_FragColor = vec4(fragColor, 1.0); //
}
</script>
应用作色器
webgl 让初学者窝火的另一个起因是,他的编程逻辑很像底层语言,而非 javascript,没有很多封装。做一件小事往往要执行多个办法。例如咱们上面要应用作色器,写的这段语法:
// 咱们先创立两个着色器
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 这两个详单于作色器的容器,但还未被填充内容。所以想在须要获取咱们后面写好的顶点作色器和片段作色器
var vertexShaderText = document.querySelector("#vertex-shader-2d").text;
var fragmentShaderText = document.querySelector("#fragment-shader-2d").text;
// 之后,咱们要把两者关联起来
gl.shaderSource(vertexShader, vertexShaderText);
gl.shaderSource(fragmentShader, fragmentShaderText);
// 最初再编译着色器,能力供咱们应用!gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
心田戏:three.js 真乃好创造!
这么还不能用,program 上场!
写完下面的代码,其实着色器还是个半成品!
WebGL 在电脑的 GPU 中运行。因而你须要应用可能在 GPU 上运行的代码。这样的代码须要提供成对的办法。每对办法中一个叫顶点着色器,另一个叫片断着色器,这些事你曾经晓得了!
而把他两组合起来就是 program!
var program = gl.createProgram();
// 咱们须要先创立 program
// 再把之前申明好的作色器附加到 program 上
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 而后咱们将这两个着色器 _link_(链接)到一个 _program_(着色程序)gl.linkProgram(program);
// 查看 WebGLProgram 程序是否链接胜利的同时还会查看其是否能在以后的 WebGL 中应用。gl.validateProgram(program);
这样所有的前置工作都曾经做好了,上面能够开始画三角形了。心累
创立 buffer
后面咱们说过在 webgl 中顶点和色彩信息被存在 buffer 上。
这里咱们就偷懒把他们写在一个数组里:
var triangleVertices =
[ // X, Y, R, G, B
-0.5, -0.5, 1.0, 0.0, 0.0,
-0.5, 0.5, 1.0, 1.0, 0.0,
0.5, -0.5, 1.0, 1.0, 0.0,
];
上文咱们晓得着色器须要调用 createShader 办法创立,buffer 天然如法炮制:
var triangleVertexBufferObject = gl.createBuffer();
// 绑定 buffer 数据类型
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBufferObject);
// 关联变量与数据
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertices), gl.STATIC_DRAW);
// 尽管咱们领有了顶点和色彩数据的数组,但无奈间接用。// WebGL 须要强类型数据,所以应用 new Float32Array(triangleVertices)创立了 32 位浮点型数据序列
// 最初一个参数 gl.STATIC_DRAW 是提醒 WebGL 咱们将怎么应用这些数据。
还是没用!
但此时 buffer
上的数据和着色器上的数据还未对应, 因而须要吧着色器的属性取出。
var positionAttribLocation = gl.getAttribLocation(program, 'vertPosition');
var colorAttribLocation = gl.getAttribLocation(program, 'vertColor');
// 并从 triangleVertices 中复制数据到序列中,而后 gl.bufferData 复制这些数据到 GPU 的 Buffer 对象上。// 因为咱们的顶点数据和色彩数据写在了一起,咱们须要通知 webgl 哪些是顶点哪些是色彩
gl.vertexAttribPointer(
positionAttribLocation, // 上文获取的本地属性
2, // 几个值能示意这个属性,这里是坐标只须要 2 个值 x,y
gl.FLOAT, // 类型
gl.FALSE,
5 * Float32Array.BYTES_PER_ELEMENT, // 步幅
0 // 起始点
);
gl.vertexAttribPointer(
colorAttribLocation,
3, // 这里是色彩须要 3 个值 r,g,b
gl.FLOAT,
gl.FALSE,
5 * Float32Array.BYTES_PER_ELEMENT,
2 * Float32Array.BYTES_PER_ELEMENT
);
// 启动属性
gl.enableVertexAttribArray(positionAttribLocation);
gl.enableVertexAttribArray(colorAttribLocation);
`gl.useProgram(program);`
// gl.useProgram 就与之前讲到的 gl.bindBuffer 类似,设置以后应用的着色程序。
绘制
至此咱们就功败垂成了,最初一步就是绘制,因为咱们应用的是三角形面,一个三角形仅仅须要 3 个顶点,所以最终执行
gl.drawArrays(gl.TRIANGLES, 0, 3);
功败垂成
var triangleVertices =
[ // X, Y, R, G, B
-0.5, -0.5, 1.0, 0.0, 0.0,
-0.5, 0.5, 1.0, .0, 0.0,
0.5, -0.5, 1.0, .0, 0.0,
];
如果想要绘制正方形,只有多加一组点即可。
var boxVertices =
[ // X, Y, R, G, B
-0.5, -0.5, 1.0, 0.0, 0.0,
-0.5, 0.5, 1.0, 1.0, 0.0,
0.5, -0.5, 1.0, 1.0, 0.0,
-0.5, 0.5, 1.0, 1.0, 0.0,
0.5, 0.5, 0.0, 1.0, 0.0,
0.5, -0.5, 1.0, 1.0, 0.0
];
但此时绘制的点就不再是 3 个,而是 6 个,因而批改绘制办法为:gl.drawArrays(gl.TRIANGLES, 0, 6);