认识前端3D渲染

举报
百里登风 发表于 2022/05/09 15:20:50 2022/05/09
【摘要】 普通的页面,更强调文字信息的传递框架无论是 Angular, React或者 Vue(编码方式,理念不一样),但本质上底层都是一样的,基于Dom结构和Css样式进行展示布局,更注重文字,表格或者普通图片信息的呈现。地震解释页面对数据可视化要求更高简单的看一下地震解释页面,很明显右侧的这块3D交互显示区域是关键,对数据可视化要求很高。这个简单的Dom是做不了这种效果的。可行的方案就是Flash...

普通的页面,更强调文字信息的传递

框架无论是 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.01.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.创建缓存,传递数据(从CPUGPU

4.获取着色器attribute变量位置,绑定到缓存

5.按照模式进行绘制


需要关注的几个细节:

1.渲染流程还是比较复杂的,即使最简单的点也需要很多代码

2.坐标系,是一个从-11的立方体

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本身是没有相机,场景,几何体,材质这些概念的


总结

只要数学好,这个东西没什么难的。主要还是要经验,会涉及到很多的小技巧。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。