认识前端3D渲染
普通的页面,更强调文字信息的传递
框架无论是 Angular, React或者 Vue(编码方式,理念不一样),但本质上底层都是一样的,基于Dom结构和Css样式进行展示布局,更注重文字,表格或者普通图片信息的呈现。
地震解释页面对数据可视化要求更高
简单的看一下地震解释页面,很明显右侧的这块3D交互显示区域是关键,对数据可视化要求很高。这个简单的Dom是做不了这种效果的。可行的方案就是Flash,或者webGL技术(其实现在可选的方案基本就一种),我们选的是后面的。优点:浏览器原生技术,技术新,无需插件(前段时间刚试过,现在平板手机支持的也还行,只是拖动操作什么的需要做一些事件的适配(有些API支持的程度不一样))。
1.webGL还值不值得学?
可能用库很快就能创一个场景玩一下,但是一旦深入,就觉得有太多细节被隐藏了。所以说如果想要深入,这个是绕不过去的,而且这个有个优点:浏览器,桌面应用,甚至安卓什么的,如果使用 OPENGL,那他们的渲染流程都是一样的, 只是js换成c++什么的(即使微软自己的HLSL,基本流程还是相近的)。见过几个做这个很厉害的,原来都是做游戏的。学好了,前景还是不错的。
2.难不难?
这个地方有人家总结的前端技能总结。https://f2e.tech/ ,很明显,3D可视化属于高阶,还是有一定的难度的,尤其是坐标转换牵扯到各种空间变换,需要点线性代数的基础,会涉及不少游戏开发的技巧。Matrix4主要是做位置变换,缩放什么的,四元数主要是用来做旋转。非常麻烦的一点,无法debug调试.
3.还有没有前景?:
可以这样说,上层库或许会变,但是底层的绘图API层面的目前看暂时几年都不会出现大的变动(目前看WebGL2.0和1.0相比,基本还是兼容的,webGPU 虽然更加先进,但是目前标准还没有最终发布),这就是浏览器3D渲染上未来几年唯一的标准。不用担心它像其它的前端库angular什么的,几年,直接就淘汰了。最主要的游戏,目前感觉最大的瓶颈在js性能,网速(加载模型),单纯的webGL已经可以支持渲染一些效果很好的游戏了。Babylon.js就是个很好的前端游戏引擎。
4.需要的基础?:
需要在js基础上,再加图形学,线性代数两门基础。最重要的认识:在我们的视角下,矩阵的本质就是对位置的空间变换。
webGL特点
webGL简介:
OpenGL是本地应用接口,OpenGL ES是嵌入式系统例如安卓机的接口定义,webGL是浏览器对OpenGL ES的实现。其实就是实现了OpenGL的一个最小子集。它会和openGL存在一个代差。所以有些OpenGL的新特性是无法在浏览器使用的。
使用开源引擎three.js ,而不是直接使用webGL 。
webGL是一个非常底层的接口实现,难于使用,所以一般是基于这个接口进行封装的3D引擎去进行开发,设计。我们选用的是开源的3D引擎:three.js。优点:开源,使用广泛,社区活跃。
webGL点线面的绘制
点绘制:
function initShader(gl, vertexShaderSource, fragmentShaderSource) {
//创建顶点着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
//创建片元着色器对象
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
//编译顶点、片元着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
}
let canvas = document.getElementById('webgl');
let gl = canvas.getContext('webgl');
var vertexShaderSource = `
void main() {
//给内置变量gl_PointSize赋值像素大小
gl_PointSize=10.0;
//顶点位置,位于坐标原点
gl_Position =vec4(0.0, 0.0, 0.0, 1.0);
}`;
var fragShaderSource = `
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}`;
var program = initShader(gl, vertexShaderSource, fragShaderSource);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.drawArrays(gl.POINTS, 0, 1);
线绘制:
function initShader(gl, vertexShaderSource, fragmentShaderSource) {
//创建顶点着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
//创建片元着色器对象
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
//编译顶点、片元着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
}
var canvas = document.getElementById('webgl');
var gl = canvas.getContext('webgl');
var vertexShaderSource = `
attribute vec3 a_Position;
void main() {
gl_Position = vec4(a_Position,1.0); //位置
}`;
var fragShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1); //RBGA值
}`;
let program = initShader(gl, vertexShaderSource, fragShaderSource);
//定义线段数组
var vertices = [
-0.7, -0.1, 0,
-0.3, 0.6, 0,
-0.3, -0.3, 0,
0.2, 0.6, 0,
]
var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
//关联着色器程序到缓冲对象
var a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
//画线
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.drawArrays(gl.LINES, 0, 4);
面绘制:
function initShader(gl, vertexShaderSource, fragmentShaderSource) {
//创建顶点着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
//创建片元着色器对象
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
//编译顶点、片元着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
}
var canvas = document.getElementById('webgl');
var gl = canvas.getContext('webgl');
var vertexShaderSource = `
attribute vec3 a_Position;
void main() {
gl_Position = vec4(a_Position,1.0); //位置
}`;
var fragShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1); //RBGA值
}`;
let program = initShader(gl, vertexShaderSource, fragShaderSource);
//定义线段数组
var vertices = [
-0.7, -0.1, 0,
-0.3, 0.6, 0,
-0.3, -0.3, 0,
0.2, 0.6, 0,
]
var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
//关联着色器程序到缓冲对象
var a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 4);
通过demo点线面的绘制流程可以总结如下:
1.获取画笔上下文。
2.编译顶点和片元着色器
3.创建缓存,传递数据(从CPU到GPU)
4.获取着色器attribute变量位置,绑定到缓存
5.按照模式进行绘制
需要关注的几个细节:
1.渲染流程还是比较复杂的,即使最简单的点也需要很多代码
2.坐标系,是一个从-1到1的立方体
3.画不了宽线条。
webGL渐变三角形的绘制
function initShader(gl, vertexShaderSource, fragmentShaderSource) {
//创建顶点着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
//创建片元着色器对象
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
//编译顶点、片元着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
}
var canvas = document.getElementById('webgl');
var gl = canvas.getContext('webgl');
var vertexShaderSource = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}`;
var fragShaderSource = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}`;
let program = initShader(gl, vertexShaderSource, fragShaderSource);
var verticesColors = new Float32Array([
-0.7, -0.1, 0, 1.0, 0.0, 0.0,
-0.3, 0.6, 0, 0.0, 1.0, 0.0,
-0.3, -0.3, 0, 0.0, 0.0, 1.0,
]);
var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
let FSIZE = verticesColors.BYTES_PER_ELEMENT;
let a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
gl.enableVertexAttribArray(a_Position);
let a_color = gl.getAttribLocation(program, 'a_Color');
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_color); // 开启缓冲区分配
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 3);
观察demo,可以看出WebGL渲染的颜色设置秘密:
只需要给顶点设置各自的颜色,系统会自动进行线性插值。
查看对比demo,可以看出所有demo一直在变化的2块代码:整个系统最重要的,也就是可以做文章的就是2个着色器。其实不严格的说这两个分别对应了游戏引擎最重要的两个概念:几何体和材质(其实材质和片元着色器比较类似,但是顶点着色器的功能远比单纯的几何点功能复杂)。而且webGL本身是没有相机,场景,几何体,材质这些概念的。
总结
只要数学好,这个东西没什么难的。主要还是要经验,会涉及到很多的小技巧。
- 点赞
- 收藏
- 关注作者
评论(0)