<canvas id="c" width="400" height="300"></canvas>
<script id="vertex-shader-2d" type="notjs">
// an attribute will receive data from a buffer
attribute 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;
}
</script>
<script id="fragment-shader-2d" type="notjs">
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default
precision mediump float;
void main() {
// gl_FragColor is a special variable a fragment shader
// is responsible for setting
gl_FragColor = vec4(1, 0, 0.5, 1); // return reddish-purple
}
</script>
canvas {
border: 1px solid red;
}
/**
* 创建着色器
* @param {WebGLRenderingContext} gl
* @param {GLenum} type
* @param {string} source
* @returns {WebGLShader | null}
*/
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;
} else {
// 如果编译失败,打印编译错误信息
console.log(gl.getShaderInfoLog(shader));
// 删除失败的着色器对象
gl.deleteShader(shader);
// 返回null
return null;
}
}
/**
* 创建着色器程序
* @param {WebGLRenderingContext} gl
* @param {WebGLShader} vertexShader
* @param {WebGLShader} fragmentShader
* @returns {WebGLProgram}
*/
function createProgram(gl, vertexShader, fragmentShader) {
// 创建一个程序对象
const program = gl.createProgram();
// 将顶点着色器附加到程序对象上
gl.attachShader(program, vertexShader);
// 将片元着色器附加到程序对象上
gl.attachShader(program, fragmentShader);
// 链接程序对象
gl.linkProgram(program);
// 检查链接是否成功
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
// 如果链接成功,则打印链接信息并返回程序对象
console.log("链接成功:", gl.getProgramInfoLog(program));
return program;
} else {
// 如果链接失败,则打印链接信息并删除程序对象
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
}
(() => {
// 获取canvas元素
const canvas = document.querySelector("#c");
// 获取webgl上下文
const gl = canvas.getContext("webgl");
// 获取顶点着色器源代码
const vertexShaderSource = document.querySelector("#vertex-shader-2d")
.textContent;
// 获取片元着色器源代码
const fragmentShaderSource = document.querySelector("#fragment-shader-2d")
.textContent;
// 创建顶点着色器
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
// 创建片元着色器
const fragmentShader = createShader(
gl,
gl.FRAGMENT_SHADER,
fragmentShaderSource
);
// 创建程序
const program = createProgram(gl, vertexShader, fragmentShader);
// 获取顶点属性位置
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
console.table({ positionAttributeLocation: positionAttributeLocation });
// 创建一个用于存储位置信息的缓冲区对象
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
/**
* 1. 第一件事我们有一个 position 的 js 数组。
* 2. WebGL 需要强类型,所以这里我们用 new Float32Array() 创建一个 32 位浮点数数组。
* 3. gl.bufferData 拷贝数据到 GPU positionBuffer 中。
* 4. 它使用位置缓冲区,因为我们将其绑定到上面的 ARRAY_BUFFER 绑定点。
* 5. 最后一个参数事 gl.STATIC_DRAW,告诉 WebGL,我们不太可能对这个数据进行太大的改变。
*/
// 通过绑定点引用数据,将数据放入该缓冲区中
const position = [0, 0, 0, 0.5, 0.7, 0];
// 将顶点位置数据绑定到数组缓冲区
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW);
/**
* 渲染
*/
// 我们需要告诉 WebGL 如何将我们将要设置 gl_Position 的剪辑空间值转换回像素,通常称为屏幕空间。为此,我们调用 gl.viewport 并传递画布的当前大小。
// 设置画布显示范围为整个窗口
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// 清除画布的颜色为透明
gl.clearColor(0, 0, 0, 0);
// 清除画布的颜色缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
// 告诉 WebGL 我们将要使用哪个程序
gl.useProgram(program);
// 告诉 WebGL 如何从上面设置的缓冲区中获取数据,并将其提供给着色器中的属性
// 1. 首先打开属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 2. 然后指定如何提取数据
// 绑定 position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 告诉属性如何从positionBuffer(ARRAY_BUFFER)中获取数据
const size = 2; // 每次迭代 2 个组件
const type = gl.FLOAT; // 数据时 32 位浮点型
const normalize = false; // 不归一化数据
// 表示相邻顶点数据之间的字节间隔,这里是 0,表示顶点数据是紧密排列的。
const stride = 0; // 0 = 每次迭代向前移动 size * sizeof(type) 以获得下一个位置
const offset = 0; // 表示顶点数据的起始偏移,从 buffer 的开头开始读取
// 通过调用 gl.vertexAttribPointer 方法,可以将这些属性配置应用到指定的顶点属性位置上,从而告诉 WebGL 如何解释顶点数据。
// 隐藏的 gl.vertexAttribPointer 部分是它将电流 ARRAY_BUFFER 绑定到属性。换言之,现在此属性绑定到 positionBuffer 。这意味着我们可以自由地将其他东西绑定到 ARRAY_BUFFER 绑定点。该属性将继续使用 positionBuffer .
gl.vertexAttribPointer(
positionAttributeLocation,
size,
type,
normalize,
stride,
offset
);
const primitiveType = gl.TRIANGLES;
const offset2 = 0; // 偏移量。如果是 3 ,就是从第二个三角形开始绘制
const count = 3; // 点总数。如果是两个三角形就要6次
gl.drawArrays(primitiveType, offset2, count);
// 因为 count 是 3 ,所以将执行顶点着色器 3 次。
// 第一次,定点着色器属性中的 a_position.x 和 a_position.y 将被设置为 positionBuffer 中的前两个值。
// 第二次,a_position.x 和 a_position.y 将设置为第二个2个值(也就是3-4)。
// 第三次,将被设置为最后两个值。
// 将 primitiveType 设置为 gl.TRIANGLES,所以每次定点着色器运行 3 次,WebGL 都会根据我们设置的 gl_Position 的 3 个值绘制一个三角形。无论我们的画布大小如何,这些值都在剪辑空间坐标中,每个方向从 -1 到 1。
// 因为我们的顶点着色器只是将 positionBuffer 值复制到 gl_Position ,因此三角形将在剪辑空间坐标处绘制。
// 如果画布大小恰好是 400x300,则从剪辑空间转换为屏幕空间,我们会得到这样的结果
/**
* clip space screen space
* 0, 0 -> 200, 150
* 0, 0.5 -> 200, 225
* 0.7, 0 -> 340, 150
*/
})();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.