内卷年代,是该学学 WebGL 了

开发 前端
通过简单的webgl入门,已经有了初步的认知,大致的流程为:着色器初始化 -> 着色器程序对象 -> 控制变量 -> 绘制,为了更好的性能,后面会使用缓冲区来解决重复渲染的问题,这样我们的顶点不会一个一个设置而直接会被缓存,包括后面一些动态效果会涉及到矩阵的转换,如平移、缩放、旋转、复合矩阵。

​前言

大部分公司的都会有可视化的需求,但是用echarts,antv等图表库,虽然能快速产出成果,但是还是要知道他们底层其实用canvas或svg来做渲染,canvas浏览器原生支持,h5天然支持的接口,而svg相比矢量化,但是对大体量的点的处理没有canvas好,但是可以操作dom等优势。canvas和svg我们一般只能做2d操作,当canvas.getContext('webgl')我们就能获取webgl的3d上下文,通过glsl语言操作gpu然后渲染了。理解webgl,可以明白h5的很多三维的api底层其实都是webgl实现,包括对canvas和svg也会有新的认知。

canvas和webgl的区别

canvas和webgl都可以做二维三维图形的绘制。底层都会有对应的接口获取。cancvas一般用于二维canvas.getContext("2d")​,三维一般可以通过canvas.getContext('webgl')

窥探WebGL

理解建模

如果你有建模软件基础的话,相信3dmax、maya、su等软件你一定不会陌生,本质其实就是点、线、面来组成千变万化的事物。打个比方球体就是无数个点连成线然后每三根线形成面,当然有常见的四边形,其实也是两个三边形组成,为什么不用四边形,因为三边形更稳定、重心可计算、数据更容易测算。

所以核心也就是点、线、三角面

了解WebGL

WebGL可以简单理解为是openGL的拓展,让web端通过js可以有强大的图形处理能力。当然为了与显卡做交互你必须得会glsl语言。

GLSL

glsl着色器语言最重要的就是顶点着色器和片元着色器。简单理解为一个定位置一个添颜色。

简单绘制一个点

webgl会有大量的重复性前置工作,也就是创建着色器 -> 传入着色器源码 -> 编译着色器 -> 创建着色器程序 -> 绑定、连接、启用着色器 -> 可以绘制了!

一般而言我们是不会重复写这个东西,封装好了直接调用就行。

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;
}
<!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>Document</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>

绘制效果如下:

图片

相信看了上面有段代码会有疑惑

图片

gl_position代表坐标,vec4就一个存放个4个float的浮点数的容量,定义坐标, 分别对应x、y、z、w,也就是三维坐标,但是w就等于比例缩放xyz而已,一般在开发中,我们的浏览器的坐标要跟这个做个转换对应上,gl_POintSize是点的大小,注意是浮点数

图片

gl_flagColor渲染的像素是红色,是因为这类似于比例尺的关系需要做个转换, (R值/255,G值/255,B值/255,A值/1) ->(1.0, 0.0, 0.0, 1.0)

绘制动态点

<!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>Document</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绘制。但是这种方法,很明显有大量的重复渲染,每次遍历都要把之前渲染的重复执行。

大致效果

图片

总结

通过简单的webgl入门,已经有了初步的认知,大致的流程为:着色器初始化 -> 着色器程序对象 -> 控制变量 -> 绘制,为了更好的性能,后面会使用缓冲区来解决重复渲染的问题,这样我们的顶点不会一个一个设置而直接会被缓存,包括后面一些动态效果会涉及到矩阵的转换,如平移、缩放、旋转、复合矩阵。

责任编辑:武晓燕 来源: 前端YUE
相关推荐

2012-12-13 09:37:46

编程程序员

2022-06-07 14:25:23

数据团队数据仓库

2024-04-11 09:17:51

ArraysJava安全

2020-08-26 10:23:24

物联网数据IOT

2013-07-22 13:23:45

2021-09-27 22:40:13

比特币虚拟货币金融

2016-10-11 13:48:41

WebGLJavascriptWeb

2020-08-17 09:30:34

代码焦点程序员

2015-06-01 15:06:45

携程数据库灾备

2022-07-13 08:00:45

系统稳定性高可用

2021-05-09 23:37:56

IDEAJava 开发者

2021-06-07 08:26:35

P8员工公司

2020-11-26 06:38:14

分布式系统

2024-03-07 12:45:27

PyTorch

2023-03-31 09:09:16

OKRKPIKR

2021-04-07 14:50:14

计算机互联网 技术

2023-12-13 16:28:02

2010-07-08 10:32:38

唐骏“学历门”

2021-12-03 10:30:25

WOT技术峰会技术

2020-12-11 13:00:24

QQ电视移动应用
点赞
收藏

51CTO技术栈公众号