WebGL学习笔记一
WebGL是什么?
WebGL 是一组基于 JavaScript 语言的图形规范,浏览器厂商按照这组规范进行实现,为 Web 开发者提供一套3D图形
相关的 API
这些 API 能够让 Web 开发者使用 JavaScript 语言直接和显卡(GPU)进行通信。
WebGL 应用由 JavaScript 程序和着色器程序构成。
WebGL的编程开发者需要针对 CPU 和 GPU 进行编程,CPU 部分是 JavaScript 程序,GPU 部分是着色器程序。
GLSL——OpenGL Shading Language(OpenGL 着色语言),用来在 OpenGL 编写着色器程序的语言
术语:
图元:WebGL 能够绘制的基本图形元素,包含三种:点
、线段
、三角形
片元:即像素,像素着色阶段是在片元着色器中
剪裁坐标系:是顶点着色器中的 gl_Position
内置变量接收到的坐标所在的坐标系
设备坐标系(NDC 坐标系):是裁剪坐标系各个分量对 w 分量相除得到的坐标系,特点是 x、y、z 坐标分量的取值范围都在 【-1,1】之间,可以将它理解为边长为 2 的正方体,坐标系原点在正方体中心
立体模型:
由一个一个的点
组成,GPU 将这些点用三角形图元
绘制成一个个的微小平面,这些平面之间互相连接就构成了立体模型
GPU 渲染管线的处理过程:
1.顶点着色器阶段,利用 GPU 的并行计算优势对顶点逐个进行坐标变换。 2.图元装配阶段,将顶点按照图元类型组装成图形。 3.光栅化阶段,光栅化阶段将图形用不包含颜色信息的像素填充。 4.片元着色器阶段,该阶段为像素着色,并最终显示在屏幕上。
顶点着色器
void main(){ //声明顶点位置 gl_Position = vec4(0.0, 0.0, 0.0, 1.0); //声明待绘制的点的大小。 gl_PointSize = 10.0; }
片元着色器——顶点着色器中的数据经过图元装配和光栅化之后,来到了片元着色器
void main(){ //设置像素的填充颜色为红色。 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }
说明:
gl_Position、gl_PointSize、gl_FragColor 是 GLSL 的内置属性
gl_Position:
顶点的裁剪坐标系坐标
,包含 X, Y, Z,W 四个坐标分量
顶点着色器接收到这个坐标之后,对它进行透视除法,即将各个分量同时除以 W,转换成 NDC 坐标
NDC 坐标每个分量的取值范围都在【-1, 1】之间,GPU 获取这个属性值作为顶点的最终位置进行绘制
gl_PointSize:
绘制到屏幕的点的大小,只有在绘制图元是点
的时候才会生效
gl_FragColor:
片元(像素)颜色,包含 R, G, B, A 四个颜色分量,且每个分量的取值范围在【0,1】之间,GPU 获取这个值作为像素的最终颜色进行着色
常规颜色的【0,255】取值范围,转换公式为: (R值/255,G值/255,B值/255,A值/1)
vec4:
包含四个浮点元素的容器类型
,vec 是 vector(向量)。
vec4 代表包含 4 个浮点数的向量。 延伸:vec2
包含2个
浮点数的容器
eg:
(1)在页面上画一个点
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> <title>WebGL</title> </head> <body style="background: #000"> <canvas id="canvas"></canvas> </body> <!-- 顶点着色器源码 --> <script type="shader-source" id="vertexShader"> void main(){ //声明顶点位置 gl_Position = vec4(0.0, 0.0, 0.0, 1.0); //声明要绘制的点的大小。 gl_PointSize = 5.0; } </script> <!-- 片元着色器源码 --> <script type="shader-source" id="fragmentShader"> void main(){ //设置像素颜色为红色 gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); } </script> <script type="text/javascript"> var canvas = document.querySelector('#canvas'); var gl = canvas.getContext('webgl') || canvas.getContext("experimental-webgl"); // 获取顶点着色器源码 var vertexShaderSource = document.querySelector('#vertexShader').innerHTML; // 创建顶点着色器对象 var vertexShader = gl.createShader(gl.VERTEX_SHADER); // 将源码分配给顶点着色器对象 gl.shaderSource(vertexShader, vertexShaderSource); // 编译顶点着色器程序 gl.compileShader(vertexShader); // 获取片元着色器源码 var fragmentShaderSource = document.querySelector('#fragmentShader').innerHTML; // 创建片元着色器程序 var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); // 将源码分配给片元着色器对象 gl.shaderSource(fragmentShader, fragmentShaderSource); // 编译片元着色器 gl.compileShader(fragmentShader); //创建着色器程序 var program = gl.createProgram(); //将顶点着色器挂载在着色器程序上。 gl.attachShader(program, vertexShader); //将片元着色器挂载在着色器程序上。 gl.attachShader(program, fragmentShader); //链接着色器程序 gl.linkProgram(program) // 使用刚创建好的着色器程序。 gl.useProgram(program); //设置清空画布颜色为黑色。 gl.clearColor(0.0, 0.0, 0.0, 1.0); //用上一步设置的清空画布颜色清空画布。 gl.clear(gl.COLOR_BUFFER_BIT); //绘制点。 gl.drawArrays(gl.POINTS, 0, 1); </script> </html>
说明:
gl.drawArrays(mode, first, count);
mode,代表图元类型。
first,代表从第几个点开始绘制即顶点的起始位置
count,代表绘制的点的数量。
运行效果
(2)在鼠标点击过的位置绘制一个随机颜色的点
增加为着色器中变量进行赋值部分
绘制过程:
声明一个数组变量 points
,存储点击位置的坐标。
绑定 canvas 的点击事件。
触发点击操作时,把点击坐标添加到数组 points
中。
遍历每个点执行 drawArrays(gl.Points, 0, 1)
绘制操作
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> <title>WebGL</title> </head> <body style="background: #000"> <canvas id="canvas"></canvas> </body> <!-- 顶点着色器源码 --> <script type="shader-source" id="vertexShader"> //设置浮点数精度为中等精度 precision mediump float; //接收点在 canvas 坐标系上的坐标 (x, y) attribute vec2 a_Position; //接收 canvas 的宽高尺寸 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); //end 将屏幕坐标系转化为裁剪坐标(裁剪坐标系) //声明要绘制的点的大小。 gl_PointSize = 5.0; } </script> <!-- 片元着色器源码 --> <script type="shader-source" id="fragmentShader"> //设置浮点数精度为中等精度 precision mediump float; //接收 JavaScript 传过来的颜色值(RGBA)。 uniform vec4 u_Color; void main(){ //设置像素颜色为红色 vec4 color = u_Color / vec4(255, 255, 255, 1); gl_FragColor = color; } </script> <script type="text/javascript" src="helper.js"></script> <script type="text/javascript"> var canvas = getCanvas('#canvas'); resizeCanvas(canvas); var gl = getContext(canvas); //创建顶点着色器 var vertexShader = createShaderFromScript(gl, gl.VERTEX_SHADER,'vertexShader'); //创建片元着色器 var fragmentShader = createShaderFromScript(gl, gl.FRAGMENT_SHADER,'fragmentShader'); //创建着色器程序 var program = createSimpleProgram(gl ,vertexShader, fragmentShader); // 使用刚创建好的着色器程序。 gl.useProgram(program); //找到顶点着色器中的变量a_Position var a_Position = gl.getAttribLocation(program, 'a_Position'); //找到顶点着色器中的变量a_Screen_Size var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size'); //找到片元着色器中的变量u_Color var u_Color = gl.getUniformLocation(program, 'u_Color'); //为顶点着色器中的 a_Screen_Size 传递 canvas 的宽高信息 gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height); //存储点击位置的数组。 var points = []; canvas.addEventListener('click', e => { var x = e.pageX; var y = e.pageY; var color = randomColor(); points.push({ x: x, y: y, color: color }) render(gl); }) //绘制函数 function render(gl){ //清除屏幕 gl.clear(gl.COLOR_BUFFER_BIT); for(let i = 0; i<points.length;i++){ let 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); } } //设置清空画布颜色为黑色。 gl.clearColor(0.0, 0.0, 0.0, 1.0); //绘制 render(gl); </script> </html>
helper.js
var random = Math.random; function randomColor() { return { r: random() * 255, g: random() * 255, b: random() * 255, a: random() * 1 }; } function $$(str) { if (!str) return null; if (str.startsWith('#')) { return document.querySelector(str); } let result = document.querySelectorAll(str); if (result.length == 1) { return result[0]; } return result; } function getCanvas(id) { return $$(id); } function resizeCanvas(canvas, width, height) { if (canvas.width !== width) { canvas.width = width ? width : window.innerWidth; } if (canvas.height !== height) { canvas.height = height ? height : window.innerHeight; } } function getContext(canvas) { return canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); } function createSimpleProgram(gl, vertexShader, fragmentShader) { if (!vertexShader || !fragmentShader) { console.warn('着色器不能为空'); return; } let program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); let success = gl.getProgramParameter(program, gl.LINK_STATUS); if (success) { return program; } console.error(gl.getProgramInfoLog(program)); gl.deleteProgram(program); } function createShader(gl, type, source) { let shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); //检测是否编译正常。 let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (success) { return shader; } console.error(gl.getShaderInfoLog(shader)); gl.deleteShader(shader); } function createShaderFromScript(gl, type, scriptId) { let sourceScript = $$('#' + scriptId); if (!sourceScript) { return null; } return createShader(gl, type, sourceScript.innerHTML); }
说明:
向着色器中传递数据
getAttribLocation:找到着色器中的 attribute 变量
地址
getUniformLocation:找到着色器中的 uniform 变量
地址
vertexAttrib2f:给 attribute 变量
传递两个浮点数
uniform4f:给uniform变量
传递四个浮点数
相关文章