WebGL2的基本原理
此系列是对学习WebGL2理论基础的记录,想查看全部知识的可查看原文进行学习。
基础概念
WebGL仅仅是栅格化引擎,它会基于代码来画点、线条和三角形。通过复杂组合最终形成复杂的3D效果。WebGL是在GPU上运行的,其代码是一对函数的形式,分别是:点着色器(Vertex Shader)
和片段着色器(Fragment Shader)
。他们是一种类似C++的强类型语言GLSL编写的,一对函数组合叫做程序(Program)
。
点着色器的任务是计算点的位置。基于函数输出的位置,WebGL能狗栅格化不同种类的基本元素,如:点、线条、三角形。在栅格化这些基本元素的同时也会调用片段着色器,它的任务主要是计算当前正在绘制图形的每个像素的颜色。所有的WebGL API都是为这些函数对的运行来设置状态。通过设置一堆状态然后再调用gl.drawArrays
和gl.drawElements
在GPU上运行你的着色器。
着色器有如下四种方法接收数据:
属性(Attributes)
、缓冲区(Buffers)
和顶点数组(Vertex Arrays)
缓存区以二进制数据形式的数组传给GPU。缓存区可以放任意数据,通常有位置,归一化参数,纹理坐标,顶点颜色等等
属性用来指定数据如何从缓冲区获取并提供给顶点着色器。比如你可能将位置信息以3个32位的浮点数据存在缓存区中, 一个特定的属性包含的信息有:它来自哪个缓存区,它的数据类型(3个32位浮点数据),在缓存区的起始偏移量,从一个位置到下一个位置有多少个字节等等。
缓冲区并非随机访问的,而是将顶点着色器执行指定次数。每次执行时,都会从每个指定的缓冲区中提取下一个值并分配给一个属性。
属性的状态收集到一个顶点数组对象(VAO)中,该状态作用在每个缓冲区,以及如何从这些缓冲区中提取数据。
Uniforms
Uniforms是在执行着色器程序前设置的全局变量
纹理(Textures)
纹理是能够在着色器程序中随机访问的数组数据。大多数情况下纹理存储图片数据,但它也用于包含颜色以为的数据。
Varyings
Varyings是一种从点着色器到片段着色器传递数据的方法。根据显示的内容如点,线或三角形, 顶点着色器在Varyings中设置的值,在运行片段着色器的时候会被解析。
代码列子
WebGL只关注两件事:剪辑空间坐标和颜色,所以只需要编写两个着色器代码:点着色器提供剪辑空间坐标,片段着色器提供颜色即可。需要注意的是,剪辑空间坐标的取值范围是-1 ~ 1。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL</title>
</head>
<body>
<canvas id="gl" height="400px" width="400px" style="background-color: #ccc;"></canvas>
<script type="module" src="./index.js"></script>
</body>
</html>
index.js
const main = () => {
// 获取canvas元素
const canvas = document.querySelector('#gl')
// 开启webgl2
const gl = canvas.getContext("webgl2")
// 需要注意的是:
// #version 300 es 必须位于着色器代码的第一行。 它前面不允许有任何的注释或空行!
// #version 300 es 的意思是你想要使用WebGL2的着色器语法:GLSL ES 3.00。 如果你没有把它放到第一行,将默认设置为GLSL ES 1.00,即WebGL1.0的语法。相比WebGL2的语法,会少很多特性。
// 顶点着色器GLSL代码
const vertexShaderSource = `#version 300 es
// an attribute is an input (in) to a vertex shader.
// It will receive data from a buffer
in vec4 a_position;
// all shaders have a main function
void main() {
// gl_Position is a special variable a vertex shader
// is responsible for setting
gl_Position = a_position;
}
`
// 片段着色器GLSL代码
const fragmentShaderSource = `#version 300 es
// fragment shaders don't have a default precision so we need
// to pick one. highp is a good default. It means "high precision"
precision highp float;
// we need to declare an output for the fragment shader
out vec4 outColor;
void main() {
// Just set the output to a constant reddish-purple
outColor = vec4(1, 0, 0.5, 1);
}
`
// 创建点着色器
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
// 创建片段着色器
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// 创建程序并链接两个着色器
var program = createProgram(gl, vertexShader, fragmentShader);
// 查找a_position属性位置
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// 创建缓存区,用于存放属性
var positionBuffer = gl.createBuffer();
// 绑定缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 将点放入缓冲区
var positions = [
0, 0,
0, 0.5,
0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 创建点属性集合
var vao = gl.createVertexArray();
// 绑定集合
gl.bindVertexArray(vao);
// 启用属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 从缓冲期中取出数据
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset)
// 设置视窗大小
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// 运行着色器程序
gl.useProgram(program);
// 绑定点属性集合
gl.bindVertexArray(vao);
// 运行GLSL程序
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
}
function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
main()
代码运行如下:
上述代码是使用坐标指定的,如果要根据像素指定的话可以参考下面的代码
// 获取canvas元素
const canvas = document.querySelector('#gl')
// 开启webgl2
const gl = canvas.getContext("webgl2")
// 顶点着色器GLSL代码
var vertexShaderSource = `#version 300 es
// an attribute is an input (in) to a vertex shader.
// It will receive data from a buffer
in vec2 a_position;
// Used to pass in the resolution of the canvas
uniform vec2 u_resolution;
// all shaders have a main function
void main() {
// convert the position from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}
`;
// 片段着色器GLSL代码
const fragmentShaderSource = `#version 300 es
// fragment shaders don't have a default precision so we need
// to pick one. highp is a good default. It means "high precision"
precision highp float;
// we need to declare an output for the fragment shader
out vec4 outColor;
void main() {
// Just set the output to a constant reddish-purple
outColor = vec4(1, 0, 0.5, 1);
}
`
// 创建点着色器
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
// 创建片段着色器
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// 创建程序并链接两个着色器
var program = createProgram(gl, vertexShader, fragmentShader);
// 查找a_position属性位置
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// 查找u_resolution属性位置
var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
// 创建缓存区,用于存放属性
var positionBuffer = gl.createBuffer();
// 绑定缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 将点放入缓冲区
var positions = [
10, 20,
80, 20,
10, 30,
10, 30,
80, 20,
80, 30,
;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 创建点属性集合
var vao = gl.createVertexArray();
// 绑定集合
gl.bindVertexArray(vao);
// 启用属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 从缓冲期中取出数据
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset)
// 设置视窗大小
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// 运行着色器程序
gl.useProgram(program);
// 绑定点属性集合
gl.bindVertexArray(vao);
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height)
// 运行GLSL程序
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
总结
- WebGL是一个栅格化引擎,只负责绘制点、线条、三角形。复杂的图形需要自己去组合绘制
- WebGL代码是以一对函数的形式运行。分别是
点着色器(Vertex Shader)
和片段着色器(Fragment Shader)
- 点着色器(Vertex Shader)是计算点的位置,从而绘制出图形
- 片段着色器(Fragment Shader)是绘制图形颜色
- 着色器接收数据的方法有:
属性(Attributes),缓冲区(Buffers)和顶点数组(Vertex Arrays)
、Uniforms
、纹理(Textures)
、Varyings
- WebGL的剪辑空间坐标范围是-1 ~ 1