图形学工程师的范畴
0.1 计算机图形学研究内容
引子: https://dragonir.github.io/3d/#/olympic
● 图形数据结构及点,线,圆,多边形等基本图元的生成,进一步地,如何生成曲线和曲面 =》三维世界
● 基本图元的几何变换,投影变换,窗口裁剪 => 投影
● 图形的实时显示和并行计算 =>动画
● 插值算法、光影变换和粒子系统 【光照】=》材质
0.2 相关应用
什么是图元装配呢?
简单理解就是说将我们设置的顶点、颜色、纹理等内容组装称为一个可渲染的多边形的过程。
组装的类型取决于: 你最后绘制选择的图形类型
gl.drawArrays(gl.TRIANGLES, 0, 3)
1.2 光栅化 Rasterization
光栅化:把顶点数据转换为片元的过程(简单理解就是找到图形并转换所覆盖的像素)
图形的绘制过程
1.3 片元着色器 Fragment Shader
光珊化后,每一个像素点都包含了 颜色 、深度 、纹理数据, 这个我们叫做片元 接收光栅化阶段生成的片元,在光栅化阶段中,已经计算出每个片元的颜色信息,这一阶段会将片元做逐片元挑选的操作,处理过的片元会继续向后面的阶段传递。 片元着色器运行的次数由图形有多少个片元决定的。
2. 前端与图形学的结合
2.1 webgl是什么:
webgl 是在网页上绘制和渲染三维图形的技术,可以让用户与其进行交互。
图1 webGL及其起源
我们首先来看一个简单的webgl 着色器的例子:[用背景色来清空canvas标签的绘图区域] 更多例子详见gitub:
https://github.com/buglas/webgl-lesson
2.2 WebGL的坐标系
webgl画布的建立和获取,和canvas 2d是一样的。一旦我们使用canvas.getContext()方法获取了webgl 类型的上下文对象,那这张画布就不再是以前的canvas 2d 画布。当然,它也不会变成三维的,因为我们的电脑屏幕始终是平的。那这张画布有什么不一样了呢?它的坐标系变了。canvas 2d 画布和webgl 画布使用的坐标系都是二维直角坐标系,只不过它们坐标原点、y 轴的坐标方向,坐标基底都不一样了。
2.2.1 canvas 2d画布的坐标系
canvas 2d 坐标系的原点在左上角。canvas 2d 坐标系的y 轴方向是朝下的。canvas 2d 坐标系的坐标基底有两个分量,分别是一个像素的宽和一个像素的高,即1个单位的宽便是1个像素的宽,1个单位的高便是一个像素的高。如下图,下图两个方块表示两个像素:
2.2.2 webgl的坐标系
webgl坐标系的坐标原点在画布中心。webgl坐标系的y 轴方向是朝上的。webgl坐标基底中的两个分量分别是半个canvas的宽和canvas的高,即1个单位的宽便是半个个canvas的宽,1个单位的高便是半个canvas的高。如下图:
参考文献: https://github1s.com/buglas/webgl-lesson https://juejin.cn/post/6981444627270205453
2.2.3 Canvas和webGL绘图的差异
在讲解canvas和webGL绘图的差异之前,我们要搞清楚着色器这个概念:着色器是为了帮助我们描述顶点,变换,材质,光源和与我们相机之间关系的重要的存在,在代码中是以字符串的形式嵌在js文件中的。
分为顶点着色器和片元着色器。
● 顶点着色器(Vertex shader):描述顶点的特征,如位置等,即网格点。所有的顶点信息是放在着色器当中的,基于着色器语言 OPENGL ES,vec4 【-1.0 - 1.0】
● 片元着色器(Fragment shader):进行逐片元处理,如颜色,纹理,即网格表面的特性。
表1 canvas和webGL绘图的差异
canvas webGL
2D上下文
js 3D上下文
ELSL ES语言
1. 获取canvas元素
2. 获取渲染上下文
3. 绘制样式 红色
1. 浏览器里内置的webgl 渲染引擎,负责渲染webgl 图形,只认GLSL ES语言。
2.3 WEBGL高级变换与动画基础-矩阵复合变换
3D网格的形状是由顶点位置确定的,变换包含对渲染模型的缩放、旋转和位移等操作,而变换通常是由矩阵来进行操作的。
2.3.1 位移-平移矩阵
2.3.2 缩放-缩放矩阵
旋转-旋转矩阵
2.4 矩阵复合变换
2.4.1 动画基础
机制一:在每一个时间t时反复调用同一个函数绘制;
机制二:在每次绘制之前,清楚上次绘制的内容,并做出相应的变换;
requestAnimationFrame & cancelAnimationFrame
以上开发框架就是对webGL进行封装的一个渲染器,类似于原生js和jquery的区别;Three.js库可简化WebGL的开发,它封装了底层的图形接口,能够在无需掌握繁冗的图形学知识的情况下,也能用简单的代码实现三维场景的渲染。https://github.com/mrdoob/three.js
我们来看几个例子:three.js 实现元素周期表:https://threejs.org/examples/css3d_periodictable.html
甜甜圈:https://stemkoski.github.io/Three.js/graphulus-Surface.html
function init() {
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 3000;
scene = new THREE.Scene();
// table
for ( let i = 0; i < table.length; i += 5 ) {
const element = document.createElement( 'div' );
element.className = 'element';
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';
const number = document.createElement( 'div' );
number.className = 'number';
number.textContent = ( i / 5 ) + 1;
element.appendChild( number );
const symbol = document.createElement( 'div' );
symbol.className = 'symbol';
symbol.textContent = table[ i ];
element.appendChild( symbol );
const details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = table[ i + 1 ] + '
' + table[ i + 2 ];
element.appendChild( details );
const objectCSS = new CSS3DObject( element );
objectCSS.position.x = Math.random() * 4000 - 2000;
objectCSS.position.y = Math.random() * 4000 - 2000;
objectCSS.position.z = Math.random() * 4000 - 2000;
scene.add( objectCSS );
objects.push( objectCSS );
//
const object = new THREE.Object3D();
object.position.x = ( table[ i + 3 ] * 140 ) - 1330;
object.position.y = - ( table[ i + 4 ] * 180 ) + 990;
targets.table.push( object );
}
// sphere
const vector = new THREE.Vector3();
for ( let i = 0, l = objects.length; i < l; i ++ ) {
const phi = Math.acos( - 1 + ( 2 * i ) / l );
const theta = Math.sqrt( l * Math.PI ) * phi;
const object = new THREE.Object3D();
object.position.setFromSphericalCoords( 800, phi, theta );
vector.copy( object.position ).multiplyScalar( 2 );
object.lookAt( vector );
targets.sphere.push( object );
}
// helix
for ( let i = 0, l = objects.length; i < l; i ++ ) {
const theta = i * 0.175 + Math.PI;
const y = - ( i * 8 ) + 450;
const object = new THREE.Object3D();
object.position.setFromCylindricalCoords( 900, theta, y );
vector.x = object.position.x * 2;
vector.y = object.position.y;
vector.z = object.position.z * 2;
object.lookAt( vector );
targets.helix.push( object );
}
// grid
for ( let i = 0; i < objects.length; i ++ ) {
const object = new THREE.Object3D();
object.position.x = ( ( i % 5 ) * 400 ) - 800;
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;
targets.grid.push( object );
}
//
renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );
//
controls = new TrackballControls( camera, renderer.domElement );
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener( 'change', render );
const buttonTable = document.getElementById( 'table' );
buttonTable.addEventListener( 'click', function () {
transform( targets.table, 2000 );
} );
const buttonSphere = document.getElementById( 'sphere' );
buttonSphere.addEventListener( 'click', function () {
transform( targets.sphere, 2000 );
} );
const buttonHelix = document.getElementById( 'helix' );
buttonHelix.addEventListener( 'click', function () {
transform( targets.helix, 2000 );
} );
const buttonGrid = document.getElementById( 'grid' );
buttonGrid.addEventListener( 'click', function () {
transform( targets.grid, 2000 );
} );
transform( targets.table, 2000 );
//
window.addEventListener( 'resize', onWindowResize );
}
function transform( targets, duration ) {
TWEEN.removeAll();
for ( let i = 0; i < objects.length; i ++ ) {
const object = objects[ i ];
const target = targets[ i ];
new TWEEN.Tween( object.position )
.to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
new TWEEN.Tween( object.rotation )
.to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration )
.easing( TWEEN.Easing.Exponential.InOut )
.start();
}
new TWEEN.Tween( this )
.to( {}, duration * 2 )
.onUpdate( render )
.start();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}
function animate() {
requestAnimationFrame( animate );
TWEEN.update();
controls.update();
}
function render() {
renderer.render( scene, camera );
}
如何学习Three.js 源码:
参考文献
webGL入门:https://zhuanlan.zhihu.com/p/470401759 学习博客:http://yxyy.name/blog/
链接:https://juejin.cn/post/6979624309870460935
链接:https://juejin.cn/post/6979624309870460935
webGL的渲染管线:https://dev.opera.com/articles/introduction-to-webgl-part-1/
各种好玩的3d项目:dragonir.github.io/3d/ git地址:https://github.com/dragonir/3d
webgl之着色器:https://zhuanlan.zhihu.com/p/360310507
前端可视化从0-1: https://zhuanlan.zhihu.com/p/407120451
WebGL着色器基础和说明:https://zhuanlan.zhihu.com/p/157340686
webgl性能优化:https://zhuanlan.zhihu.com/p/154425898
webGL光照:https://zhuanlan.zhihu.com/p/357443394 https://zhuanlan.zhihu.com/p/380850482