核心代码:
function initShader (gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) { const vertexShader = gl.createShader(gl.VERTEX_SHADER); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE); gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE); //编译着色器 gl.compileShader(vertexShader); gl.compileShader(fragmentShader); //创建程序对象 const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); return program; }html代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>堆代码-duidaima.com</title> <script src="./initShader.js"></script> </head> <body> <canvas id="canvas" width="300" height="400"> 不支持canvas </canvas> </body> <script> const ctx = document.getElementById('canvas') const gl = ctx.getContext('webgl') //着色器: 通过程序用固定的渲染管线,来处理图像的渲染,着色器分为两种,顶点着色器:顶点理解为坐标,片元着色器:像素 //顶点着色器源码 const VERTEX_SHADER_SOURCE = ` void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); gl_PointSize = 10.0; } ` //片元着色器源码 const FRAGMENT_SHADER_SOURCE = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` //创建着色器 const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) //执行绘制 gl.drawArrays(gl.POINTS, 0, 1) //gl.drawArrays(gl.LINES, 0, 1) //gl.drawArrays(gl.TRIANGLES, 0, 1) </script> </html>绘制效果如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>堆代码 duidaima.com</title> <script src="./initShader.js"></script> </head> <body> <canvas id="canvas" width="300" height="400"> 不支持canvas </canvas> </body> <script> const canvas = document.getElementById('canvas') const gl = canvas.getContext('webgl') const VERTEX_SHADER_SOURCE = ` precision mediump float; attribute vec2 a_Position; attribute vec2 a_Screen_Size; void main(){ vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0; position = position * vec2(1.0, -1.0); gl_Position = vec4(position, 0, 1); gl_PointSize = 10.0; } ` const FRAGMENT_SHADER_SOURCE = ` precision mediump float; uniform vec4 u_Color; void main() { vec4 color = u_Color / vec4(255, 255, 255, 1); gl_FragColor = color; } ` //前置工作,着色器可以渲染了! const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) //获取glsl的变量对应的属性做修改 var a_Position = gl.getAttribLocation(program, 'a_Position'); var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size'); var u_Color = gl.getUniformLocation(program, 'u_Color'); gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height); //给glsl的属性赋值两个浮点数 //给个默认背景颜色 gl.clearColor(0, 0, 0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); //存储点击位置的数组。 var points = []; canvas.addEventListener('click', e => { var x = e.pageX; var y = e.pageY; var color = { r: Math.floor(Math.random() * 256), g: Math.floor(Math.random() * 256), b: Math.floor(Math.random() * 256), a: 1 }; points.push({ x: x, y: y, color: color }) gl.clearColor(0, 0, 0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); for (let i = 0; i < points.length; i++) { var color = points[i].color; gl.uniform4f(u_Color, color.r, color.g, color.b, color.a); gl.vertexAttrib2f(a_Position, points[i].x, points[i].y); gl.drawArrays(gl.POINTS, 0, 1); } }) </script> </html>vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0; 注意这里的坐标转换,从canvas转为ndc坐标,其实就是看范围就行,[0, 1] -> [0, 2] -> [-1, 1]。上面总体的流程总结下就是,定义着色器,定义glsl着色器源码 -> 通过api获取canvas的信息转换坐标系 -> 监听点击事件传递变量到glsl中 -> 通过pointer缓存 -> drawArrays绘制。但是这种方法,很明显有大量的重复渲染,每次遍历都要把之前渲染的重复执行。