新手向!用WebGL写一个旋转的动态三角形,总共分三步!!

341 阅读5分钟

html部分还是比较简单,引入的matrix.js是矩阵变换的一些方法,网上有很多,大家可以搜一搜

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>demo</title>
</head>
<body>
    <canvas id="myCanvas" width="400" height="400"></canvas>
    <script type="text/javascript" src="./matrix.js"></script>
    <script type="text/javascript" src="./demo.js"></script>
</body>
</html>

下面是demo.js的代码

第一步(准备画一个静态三角形):先把顶点着色器和片元着色器和context绑定

program代表一个程序(着色程序),这个程序可以绑定顶点着色器和片元着色器,从而将顶点着色器和片元着色器加载到当前canvas要执行的程序段里来

let canvas = document.getElementById('myCanvas');
let gl = canvas.getContext('webgl');

let program = gl.createProgram();

// 先定义两个源代码
let VSHADER_SOURCE, FSHADER_SOURCE;

// 先定义两个shader
let vertexShader, fragmentShader;

function createShader(gl, sourceCode, type) {
    // 创建shader
    let shader = gl.createShader(type);

    // 给创建的shader挂载sourceCode
    gl.sourceCode(shader, sourceCode);

    // 编译shader
    gl.compileShader(shader);

    return shader;
}  

// 定义vertexShader
vertexShader = createShader(gl, VSHADER_SOURCE, gl.VERTEX_SHADER);

// 定义fragmentShader
fragmentShader = createShader(gl, FSHADER_SOURCE, gl.FRAGMENT_SHADER);

// program和shader绑定
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);

// 给context绑定program(这里是一个,也可以定义多个shader挂载到多个program上)
gl.linkProgram(program);
gl.useProgram(program);

gl.program = program;

第二步(画出一个静态三角形):实现a_Position和对应的buffer绑定

const gl = document.getElementById('myCanvas').getContext('webgl');
const program = gl.createProgram(); 

// vec4 是一个四维向量,要render的顶点坐标
const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    void main() {
        gl_Position = a_Position;
    }
`;

const FSHADER_SOURCE = `
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
`;

function createShader(gl, sourceCode, type) {
    // 创建shader
    const shader = gl.createShader(type);
    
    // 给创建的shader挂载sourceCode
    gl.shaderSource(shader, sourceCode);

    // 编译shader
    gl.compileShader(shader);

    return shader;
}

// 定义vertexShader
const vertexShader = createShader(gl, VSHADER_SOURCE, gl.VERTEX_SHADER);

// 定义fragmentShader
const fragmentShader = createShader(gl, FSHADER_SOURCE, gl.FRAGMENT_SHADER);  

// program和shader绑定
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);

// 给context绑定program(这里是一个,也可以定义多个shader挂载到多个program上)
gl.linkProgram(program);
gl.useProgram(program);
gl.program = program;

// 实现a_Position和对应的buffer绑定,从而将三角形三个顶点坐标传递到 a_Position attribute vec4中
function initVertexBuffers(gl) {
    // 3个顶点坐标,坐标没有z值,默认是0
    const vertices = new Float32Array([
        0, 0.5, -0.5, -0.5, 0.5, -0.5
    ]);
    const n = 3;
    
    // 创建buffer,利用buffer往vertexShader传递数据
    const vertexBuffer = gl.createBuffer();
    
    // 将vertexBuffer绑定到webgl上
    // gl.ARRAY_BUFFER叫做顶点缓冲区,还有一种顶点索引缓冲区(存的是顶点的索引)
    // 顶点缓冲区里有一些数据是重复的,比如同一个点在两个三角形或多个图形中都是顶点,多次重复出现,这种情况可以用索引来减少buffer的使用空间,对于相同的顶点坐标设为相同的顶点即可
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

    // 将data灌入buffer
    // gl.STATIC_DRAW第一次对缓冲区进行render后再也不会对缓冲区的顶点数据进行修改
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    // 获取attribute a_position变量地址
    const a_Position = gl.getAttribLocation(gl.program, 'a_Position');

    // 将当前buffer绑定到vertex attribute上并且带有一些特定的规则
    // 传入的2代表每两个数字为一个顶点坐标
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);  
    // 启用a_Position变量
    gl.enableVertexAttribArray(a_Position);

    return n;
}

// 顶点位置传到vertexShader
const n = initVertexBuffers(gl);
gl.clearColor(0, 0, 0, 1);

function draw() {
    // 绘制,先清空画布,增加背景色
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 以三角形的方式绘制,从buffer最开始的位置拿数据,拿n个(在上面       initVertexBuffers定义的)

    gl.drawArrays(gl.TRIANGLES, 0, n);
}

draw();

第三步(画出动态三角形):让三角形动起来

/** @type {HTMLCanvasElement} */
const gl = document.getElementById('myCanvas').getContext('webgl');
const program = gl.createProgram();

// vec4 是一个四维向量,要render的顶点坐标
const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    uniform mat4 u_ModelMatrix;
    void main() {
        gl_Position = u_ModelMatrix * a_Position;
    }
`;
const FSHADER_SOURCE = `
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
`;
function createShader(gl, sourceCode, type) {
    // 创建shader
    const shader = gl.createShader(type);
    // 给创建的shader挂载sourceCode
    gl.shaderSource(shader, sourceCode);
    // 编译shader
    gl.compileShader(shader);
    return shader;
}

// 定义vertexShader
const vertexShader = createShader(gl, VSHADER_SOURCE, gl.VERTEX_SHADER);
// 定义fragmentShader
const fragmentShader = createShader(gl, FSHADER_SOURCE, gl.FRAGMENT_SHADER);

// program和shader绑定
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);

// 给context绑定program(这里是一个,也可以定义多个shader挂载到多个program上)
gl.linkProgram(program);
gl.useProgram(program);

gl.program = program;

let currentAngle = 0;
// 第一次初始值
let g_last = Date.now();
function tick() {
    // 更新 新的旋转角度
    animate();
    // 绘制
    draw();
    // 常用绘制方法
    requestAnimationFrame(tick);
}

// 实现a_Position和对应的buffer绑定,从而将三角形三个顶点坐标传递到 a_Position attribute vec4中
function initVertexBuffers(gl) {
    // 3个顶点坐标,坐标没有z值,默认是0
    const vertices = new Float32Array([
        0, 0.5, -0.5, -0.5, 0.5, -0.5
    ]);
    const n = 3;
    // 创建buffer,利用buffer往vertexShader传递数据
    const vertexBuffer = gl.createBuffer();
    // 将vertexBuffer绑定到webgl上
    // gl.ARRAY_BUFFER叫做顶点缓冲区,还有一种顶点索引缓冲区(存的是顶点的索引)
    // 顶点缓冲区里有一些数据是重复的,比如同一个点在两个三角形或多个图形中都是顶点,多次重复出现,这种情况可以用索引来减少buffer的使用空间,对于相同的顶点坐标设为相同的顶点即可
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    // 将data灌入buffer
    // gl.STATIC_DRAW第一次对缓冲区进行render后再也不会对缓冲区的顶点数据进行修改
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    // 获取attribute a_position变量地址
    const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    // 将当前buffer绑定到vertex attribute上并且带有一些特定的规则
    // 传入的2代表每两个数字为一个顶点坐标
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
    // 启用a_Position变量
    gl.enableVertexAttribArray(a_Position);
    return n;
}

// 顶点位置传到vertexShader
const n = initVertexBuffers(gl);

gl.clearColor(0, 0, 0, 1);

// 拿到gl.program里面的u_ModelMatrix(在VSHADER_SOURCE中定义的那个)
const u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
// Matrix4比较好的封装了一些矩阵变换的操作
const modelMatrix = new Matrix4();

function animate() {
    // 记下时间,拿到现在的时间戳,因为每次requestAnimationFrame间隔时间是不知道的
    const now = Date.now();
    // 减去老的时间戳
    const duration = now - g_last;
    g_last = now;
    // 想要requestAnimationFrame间隔时间内转180度
    currentAngle = currentAngle + duration / 1000 * 180;
}

// webgl视角是沿着z轴负方向,望向圆点的,想让三角形在3D的世界里转起来,是需要z轴的
function draw() {
    // 绕y轴旋转currentAngle度
    modelMatrix.setRotate(currentAngle, 0, 1, 0);
    // 往uniform里传matrix的api
    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

    // 绘制,先清空画布,增加背景色
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 以三角形的方式绘制,从buffer最开始的位置拿数据,拿n个(在上面initVertexBuffers定义的)
    gl.drawArrays(gl.TRIANGLES, 0, n);
}
tick();

我在vscode上没有发现webgl的语法提示,就用了 @type {HTMLCanvasElement} 这句来做语法提示,大家有啥好用的插件推荐吗?