webGL入门 | 青训营笔记

121 阅读4分钟

webGL入门

这是我参与「第四届青训营 」笔记创作活动的的第9天

canvas 2d与webgl绘图的差异

canvas 2d

canvas 2d可以直接使用js就可以进行绘图,目标元素是画纸,js是笔,我们就可以用js绘制不同的东西

//获取canvas画纸
const canvas=document.getElementById('canvas');
//获取画笔
const ctx=canvas.getContext('2d');
//设置画笔的颜色
ctx.fillStyle='red';
//用画笔画一个矩形
ctx.fillRect(20,20,300,200);

webgl

但是webgl绘图并不能像下面这样

//获取canvas画纸
const canvas=document.getElementById('canvas');
//获取画笔
const ctx=canvas.getContext('webgl');
//设置画笔的颜色
ctx.fillStyle='red';
//用画笔画一个立方体
ctx.fillBox(20,20,300,200);

这是因为webgl绘图并不是直接在用笔画纸上作画,而是需要利用顶点着色器和片元着色器经过一层编译,就好像是用手绘板在电脑上绘图一样

webgl绘图

因为使用手绘板绘图我们就要有以下几个步骤

  1. 获取绘图的电脑

    <canvas id="canvas"></canvas>/**有这样一台'电脑'**/
    
    const canvas=document.getElementById('canvas');//获取这台'电脑'
    

    当然还需要确认一下这台电脑能不能用

    // 确认WebGL支持性
    if (!gl) {
        alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。");
        return;
    }
    

    再清理一下电脑画板

    // 使用完全不透明的黑色清除所有图像
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    // 用上面指定的颜色清除缓冲区
    gl.clear(gl.COLOR_BUFFER_BIT);
    
  2. 获取数位笔

    const gl=canvas.getContext('webgl');//获取数位笔
    
  3. 获取手绘板

    定义顶点着色器和片元着色器字符串的配置(手绘板的组件)

    const vsSource = `
        void main() {
            gl_Position = vec4(0.0 ,0.0 ,0.0 , 1.0);
            gl_PointSize = 10.0;
        }
    `;//顶点着色器
    const fsSource = `
        void main() {
            gl_FragColor = vec4(1.0 ,0.0 ,0.0 ,1.0);
        }
    `;//片元着色器
    

    或者也可以选择用html的script元素自定义

    //顶点着色器
    <script id="vertexShader" type="x-shader/x-vertex">
        void main() {
            gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
            gl_PointSize = 100.0;
        }
    </script>
    //片元着色器
    <script id="fragmentShader" type="x-shader/x-fragment">
        void main() {
            gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        }
    </script>
    
    const vsSource = document.getElementById('vertexShader').innerText;
    const fsSource = document.getElementById('fragmentShader').innerText;
    

    两种方法的结果都是一样的

    顶点着色器用于描述顶点的特征,如位置、颜色等。

    片元着色器用于进行逐片元处理,如光照。可以理解成顶点着色器确定绘制的点,片元着色器用于绘制链接各个顶点之间的像素

    然后有了手绘板的组件,接下来就要拼装起来

        initShaders();
        function initShaders(gl,vsSource,fsSource){
            //创建程序对象(创建和数位笔匹配的手绘板)
            const program = gl.createProgram();//
            //建立着色对象(获取到两个组件)
            const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
            const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
            if (!vertexShader || !fragmentShader) {
                return null;
            };//判断下着色器对象能不能用
            //把顶点着色对象装进程序对象中(装载顶点着色器)
            gl.attachShader(program, vertexShader);
            //把片元着色对象装进程序对象中(装载片元着色器)
            gl.attachShader(program, fragmentShader);
            //连接webgl上下文对象和程序对象(匹配数位笔和手绘板)
            gl.linkProgram(program);
            //判断下连接状态,连不好只能报废了
            const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
            if (!linked) {
                const error = gl.getProgramInfoLog(program);
                console.log('Failed to link program: ' + error);
                gl.deleteProgram(program);
                gl.deleteShader(fragmentShader);
                gl.deleteShader(vertexShader);
                return false;
            };
            //启动程序对象(打开数位板电源)
            gl.useProgram(program);
            //将程序对象挂到上下文对象上(连接数位笔和手绘板)
            gl.program = program;
            return true;
        }
    
        function loadShader(gl, type, source) {
            const shader = gl.createShader(type);//建立着色器对象(创建和数位笔匹配的手绘板组件)
            if (!program) {
                console.log('Failed to create program');
                return false;
            };//判断下色器对象能不能用
            gl.shaderSource(shader, source);//将着色器源文件传入着色器对象中
            gl.compileShader(shader);//编译着色器对象
            return shader; //返回着色器对象
        }
    
  4. 绘制

     gl.drawArrays(gl.POINTS, 0, 1);//绘制一个点
    

绘制更多

但是如上代码只能在我们设置的顶点着色器的位置绘制一个点

const vsSource = `
    void main() {
        gl_Position = vec4(0.0 ,0.0 ,0.0 , 1.0);
        gl_PointSize = 10.0;
    }
`;//顶点着色器

因此我们可以改变'gl_Position'的值去绘制不同的点,因此我们要把我们的顶点着色器改装一下,给他加个插件

const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    void main() {
        gl_Position = a_Position;
        gl_PointSize = 10.0;
    }
`;//顶点着色器
let a_Position = gl.getAttribLocation(gl.program, "a_Position");//获取属性的api(获取插件)
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);//修改属性值的api(操控插件)
gl.drawArrays(gl.POINTS, 0, 1);
​
gl.vertexAttrib3f(a_Position, 0.5, 0.5, 0.0);
gl.drawArrays(gl.POINTS, 0, 1);

这样我们就能通过控制插件来绘制不同的点了

总结

就是说这篇文章是我自己看到别的博客写笔记的不知道做不做数,先发了再说,我在实习的公司有3D库存的项目,在接触项目之前,想先了解一些基本的知识,因为真的在项目中用起来应该绘制用一些边界的框架,但是在使用时肯定会更加得心应手。