前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端新玩具——webGL简介

前端新玩具——webGL简介

作者头像
IMWeb前端团队
发布2019-12-04 10:49:27
2K0
发布2019-12-04 10:49:27
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

本文作者:IMWeb devinran 原文出处:IMWeb社区 未经同意,禁止转载 在最初的六天,我创造了天与地

webGL是基于OpenGL的Web3D图形规范,是一套JavaScript的API。简单来说,可以把它看成是3D版的canvas。恩,你会这样引入canvas对吧:

代码语言:javascript
复制
canvas = document.getElementById("xxx");
ctx = canvas.getContext("2d");

SO,3D版本的就酱:

代码语言:javascript
复制
canvas = document.getElementById("xxx");
gl = canvas.getContext("experimental-webgl");

是的,webGL直接使用canvas元素,只是引入一个不同的上下文“experimental-webgl”,方便吧。

这里的上下文实际上应该是.getContext("webgl"),但由于现在webgl标准尚未完善,所以多数浏览器采用一个“试验性”的上下文

让我们先啰嗦一些玩意儿

3D坐标系

这个玩意儿大家都认识吧不多啰嗦了

这里y轴跟canvas是逆向的,这是一个右手坐标系

网格、多边形和顶点

网格(Mesh)是绘制3D图形的一种方法,它是由一个或多个多边形组成的物体,每个顶点的坐标(x,y,z)定义了多边形在3D空间中的位置,这里的多边形通常是三角形和四边形。网格用来描述物体形状。恩,大概......也许......差不多......长这样:

材质、纹理和光源

贴个骷髅头什么的最嗨森了。但仅仅这样是然并卵的,为什么?因为现在毛都看不见

。诶不带丢鸡蛋的,诶卧槽你再丢!

为什么说看不见呢,因为视觉是光作用于视网膜细胞所产生的大脑认知,所以我们需要,还需要能反射光的表面。这样网格才能看得见

于是有:

  • 纹理映射(texture map) :物体表面对光的反射,颜色及光泽度等,常由位图来决定。
  • 光源(light) :顾名思义就是闪瞎你的那个东西。常用有环境光、点光源、平行光等,物体表面对光的反射还有环境反射、镜面反射和漫反射。
  • 材质(material) :网格表面的特性的统称。
变换和矩阵

网格的形状是由顶点决定的,而我们做的是动画,难道动画每一帧要重新定义所有网格的所有顶点?显然是不可取的,所以我们需要变换(transform)。变换是不需要遍历每个顶点就可以移动网格的操作,需要由矩阵(matrix)来操作。

类似介种:

相机、透视、视口和投影

我们生活在三维世界中,但是用眼睛只能看到二维的图像。同样的,三维的网格要能够看见,需要渲染成二维图像。

好多好多的概念:

  • 场景(scene) :容纳一切的容器
  • 相机(camera) :就是你在webGL世界里面的眼睛呐。
  • 视口(viewport) :想想浏览器的视口的概念,对,就是3D场景渲染的二维图像,也就是你从浏览器的canvas元素上看到的。
  • 视野(field of view) :相机可见范围左右边界的夹角。
  • 视锥体(view frustum) :物体可以被渲染到视口的空间,换句话说,只有处于视锥体空间内部的物体,才可以被看见。
  • 近裁剪面(near clipping plane) :视锥体靠近相机的一面,其实就是视口。
  • 远裁剪面(far clipping plane) :视锥体最远离相机的平面。

太君别开枪!我知道你最讨厌一大片的概念了,来看图:

这样清楚了吧~~~ ?(终于啰嗦完了......)

数学真难

概念讲完了是不是该开搞了呢?诸位看官别急,且听小生慢慢道来。

大家明白,模拟三维空间,需要非常多的计算,网格的坐标、大小、角度,网格的平移、旋转,相机观察网格的二维映射等等等等。

前方高能(学霸请无视这一行)

《线性代数》乱入:

前面说了,网格由N个多边形构成,实际上就是由多边形的顶点集合构成。顶点是一个向量,而向量可以用一个三维坐标(x, y, z)来表示。矢量之间存在加法、减法、点乘、叉乘运算。(作者抱着《线性代数》一顿狂翻......)

到这里有没有发现一个问题?就是向量和坐标的表示方法是一样的

。于是这里引入齐次坐标(w)来区分,w=0,则表示向量,否则表示点。于是我们的向量就长这样:(x, y, z, w)。值得一提的是,齐次坐标表示方法不唯一,(x, y, z, w)跟(x/w, y/w, z/w, 1)表示同一个点,后者为齐次坐标的正常化处理。eg: (15, 12, 9, 3)跟(5, 4, 3, 1)并没有什么区别,都表示三维点(5, 4, 3)。(作者抱着《线性代数》又是一顿狂翻......)

所谓齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示——百度百科 http://baike.baidu.com/link?url=qKIV6kEiCXE-MEdwSFvI3Ew6XkDwoOywt_6oYG8Nu6tdThO1EF9heuS8MDQpujJbXyDErMPsDYBiGNj3FqL9l_

向量坐标说完了,各位看官还记得向量运算的好朋友——矩阵么?什么?不记得?请跟作者一起抱着《线性代数》一顿狂翻......

具体矩阵计算就不细说了,大致有这几个:加法减法乘法求逆转置

Waring:矩阵的乘法 不满足 乘法交换律,所以还分 左乘右乘

我知道各位看官要丢鸡蛋了,讲这么半天线性代数到底有什么卵用啊?恩且慢动手。接下来我们要说重要的东西了。

仿射变换

仿射变换:大概就是对原坐标做一些羞羞的事情然后获取他们新坐标的值。

下面图略丑请凑合看

平移

平移矩阵长这样,tx, ty, tz为位移向量,比如(tx, ty, tz)=(3,2,0),则平移变换效果如下图:

旋转

旋转三个矩阵,分别对应x、y、z轴,这个坐标轴遵循右手法则,右手法则就是:

那么比如我们绕z轴旋转,使用上面的第三个矩阵,旋转90度,效果这样:

缩放

这个运算看上去简单多了对吧嘿嘿嘿

注:上述仿射变换均是用对应的仿射矩阵 左乘 齐次坐标得到结果

好了,讲了半天这个那个矩阵的,《线性代数》已经被学渣作者翻烂,不知道各位看官是什么感觉(学霸:so easy!)

那么问题来了,难道玩图形学的人们天天搞矩阵?不!这不科学!一定不是这样的!程序员是一类神奇的生物,凡是遇到觉得很烦躁很麻烦的东西,都会创造另外一些东西让他们不烦躁不麻烦。这个“另外一些东西”就是:

正经开搞

好了我们要开始创造天与地了,不要担心,我们不会去算矩阵的,难道肚子饿了还要先插秧吗?webGL已经有那么些封装很好的引擎了,这些引擎能够帮助开发者规避矩阵计算等复杂的操作,让你能够专注于天地的创造。这里我们使用Three.js。

Three.js 是一个js编写的第三方库,运行在浏览器中,提供场景、相机、光照、材质等各种对象——http://threejs.org/

首先我们创建一个渲染器并添加到页面上

antialias是一个抗锯齿参数,我们设置了渲染器的宽高,简单吧。

渲染器有了我们就可以渲染场景了,然后往里面丢各种东西,想想还有点小激动呢。建场景就一行

现在世界已经从一片混沌变成天地初开了,我们需要一双发现真善美的眼睛——相机

Three.js最主要的相机一个是正投影相机(OrthographicCamera),这个相机是“上帝视角”,为啥说是上帝视角,因为东西是啥样他看着就是啥样儿。恩,我这样说我知道你肯定没听懂。没事儿我们继续看。

另一个就是我们这里用到的了,透视投影相机(PerspectiveCamera) (并不能把穿了衣服的看成没穿衣服的)。透视投影有一个基本点,远处的物体比近处的物体小。这就是与正投影的区别。还记得前面讲透视时候的那个图吗?

看上面的几个参数,CONST.FIELD_OF_VIEW——视野角度,第二个是宽高比,然后远近裁剪面,想起来了吧~~~

最后把它放在(0, 0, 3)的地方,偶吼吼,盘古大婶睁眼了。

不过现在睁眼然并卵啊,上帝说:要有光!

这里我们创造一组平行光,因为照亮世界的是太阳,物理学角度来说通常把太阳光看成是平行光。

除此之外还有环境光、区域光、点光源和聚光灯。

万事俱备,我们要开始开天辟地的辟地了。

我们先创造一个几何球体(当然同理还有CubeGeometry等等),三个参数,第一个是球体半径,后两个分别是球体在两个方向上的几何精度(其实就是每条线上用多少个顶点描述),这里的横向和纵向都设置为64个顶点。

接下来是定义材质,为了效果更逼真,我们使用着色器来定义材质,需要三张贴图,分别是:

  • 漫反射贴图 :即颜色贴图
  • 法线贴图 :描述材质的凹凸程度
  • 高光贴图 :描述材质的反光效果

这里我们拿到网上有一套非常清晰的地球的图(从左往右依次是法线贴图、高光贴图、漫反射贴图)

只要有了漫反射贴图,我们就可以通过 PixPlant 软件来生成其法线贴图和高光贴图,效果嘛,还行。 我们拿两张来试试,分别是木星和金星的漫反射贴图

经过PixPlant的处理后得到下面几张。是不是很爽?

好我们开始把贴图做成纹理

通过读取图片做成纹理映射,然后把纹理映射给到着色器材质

最后用几何体跟材质生成网格,并倾斜一个小角度方便我们瞅着它

把网格添加到场景中

这样“辟地”就弄好了

是不是感觉跟平常看到的不太一样?

对啊卧槽云呢?咱们的星球那么漂亮,要有云哇!

相同的步骤,我们再做一个网格。只不过这里我们不再需要着色器材质了,因为云层不需要高光法线这些东西。我们使用兰伯特(Lambert)材质,这个材质的特点是无论观察者角度如何变化,它的表面亮度都一样。这个性质用来做我们的云层最棒了。然后我们还要把云层网格设为透明,让它“罩”在地球上,转动比地球快一丢丢,更接近真实。

好了,最后我们使用requestAnimationFrame()函数来让它转起来!

requestAnimationFrame函数是专为脚本动画创建的,使用它可以让浏览器来自动控制动画的最佳帧频,提升性能、节省电能。在这一点上它比setTimeout和setInterval函数更好。

最后大功告成

完整代码

代码语言:javascript
复制
/*
 *  build by rhj 2015/09/27
 */
function World(id){
    //各部件Obj
    this.container = document.getElementById(id);
    this.renderer  = null;
    this.scene     = null;
    this.camera    = null;
    this.light     = null;
    this.world     = null;
    this.cloud     = null;

    //常量
    this.constObj  = {
        ANGLE_INCLINED      : Math.PI / 6,
        ROTATION_WORLD_RATE : 0.001,
        ROTATION_CLOUD_RATE : 0.0012,
        FIELD_OF_VIEW       : 45,
        NEAR_CLIPPING_PLANE : 1,
        FAR_CLIPPING_PLANE  : 10,
        WORLD_SHININESS     : 15,

        //云层高约10km,地球半径6371km,云层球体半径R=(6371+10)/6371≈1.0016
        WORLD_RADIUS        : 1,
        CLOUD_RADIUS        : 1.0016,
        GLOBE_RESOLUTION    : 64
    }
}

World.prototype.initRender = function(){
    var container = this.container;
    var renderer  = null;

    renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setSize(container.offsetWidth, container.offsetHeight);

    this.renderer = renderer;
    this.container.appendChild( this.renderer.domElement );
}

World.prototype.initScene = function(){
    this.scene = new THREE.Scene();
}

World.prototype.initCamera = function(){
    var container = this.container;
    var CONST     = this.constObj;
    var camera    = null;

    camera = new THREE.PerspectiveCamera(
        CONST.FIELD_OF_VIEW, 
        container.offsetWidth/container.offsetHeight,
        CONST.NEAR_CLIPPING_PLANE, 
        CONST.FAR_CLIPPING_PLANE
    );
    //相机坐标
    camera.position.set(0, 0, 3);

    this.camera = camera;

    this.scene.add(this.camera);
}

World.prototype.initLight = function(){
    var light = null;

    light = new THREE.DirectionalLight(0xffffff, 1.5);
    //光源坐标
    light.position.set(0, 0, 1);

    this.light = light;

    this.scene.add(this.light);
}

World.prototype.initWorld = function(){
    var shader   = null;
    var uniforms = null;
    var material = null;
    var geometry = null;

    var CONST = this.constObj;

    var surfaceMap  = THREE.ImageUtils.loadTexture("images/earth_surface.jpg");
    var normalMap   = THREE.ImageUtils.loadTexture("images/earth_normal.jpg");
    var specularMap = THREE.ImageUtils.loadTexture("images/earth_specular.jpg");

    shader   = THREE.ShaderUtils.lib["normal"];
    uniforms = THREE.UniformsUtils.clone(shader.uniforms);

    //法线贴图、漫反射贴图、高光贴图
    uniforms["tNormal"].texture   = normalMap;
    uniforms["tDiffuse"].texture  = surfaceMap;
    uniforms["tSpecular"].texture = specularMap;

    uniforms["enableDiffuse"].value  = true;
    uniforms["enableSpecular"].value = true;

    //物体表面光滑度
    uniforms["uShininess"].value = CONST.WORLD_SHININESS;

    //着色器
    material = new THREE.ShaderMaterial({
        fragmentShader : shader.fragmentShader,
        vertexShader   : shader.vertexShader,
        uniforms       : uniforms,
        lights         : true
    });

    //球体网格(半径、纬线顶点数、经线顶点数)
    geometry = new THREE.SphereGeometry(CONST.WORLD_RADIUS, CONST.GLOBE_RESOLUTION, CONST.GLOBE_RESOLUTION);
    geometry.computeTangents();

    world = new THREE.Mesh(geometry, material);

    world.rotation.x = CONST.ANGLE_INCLINED;
    world.rotation.y = CONST.ANGLE_INCLINED;

    this.world = world;

    this.scene.add(this.world);
}

World.prototype.initCloud = function(){
    var cloudsMap      = null;
    var cloudsMaterial = null;
    var cloudsGeometry = null;

    var CONST = this.constObj;

    cloudsMap      = THREE.ImageUtils.loadTexture("images/earth_clouds.png");
    cloudsMaterial = new THREE.MeshLambertMaterial({color: 0xffffff, map: cloudsMap, transparent: true});

    //云层球体网格(半径、纬线顶点数、经线顶点数)
    cloudsGeometry = new THREE.SphereGeometry(CONST.CLOUD_RADIUS, CONST.GLOBE_RESOLUTION, CONST.GLOBE_RESOLUTION);
    cloud          = new THREE.Mesh(cloudsGeometry, cloudsMaterial);

    cloud.rotation.y = CONST.ANGLE_INCLINED;

    this.cloud = cloud;

    this.scene.add(this.cloud);
}

World.prototype.build = function(){
    this.initRender();
    this.initScene();
    this.initCamera();
    this.initLight();
    this.initWorld();
    this.initCloud();
}

World.prototype.rotate = function(self){
    var CONST = self.constObj;

    self.renderer.render(self.scene, self.camera);
    self.world.rotation.y += CONST.ROTATION_WORLD_RATE;
    self.cloud.rotation.y += CONST.ROTATION_CLOUD_RATE;
    requestAnimationFrame( function(){ self.rotate(self); } );
}

线上演示地址:http://rhj1122.github.io/webgl/

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015-09-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 让我们先啰嗦一些玩意儿
    • 3D坐标系
      • 网格、多边形和顶点
        • 材质、纹理和光源
        • 变换和矩阵
        • 相机、透视、视口和投影
      • 数学真难
        • 《线性代数》乱入:
        • 仿射变换
      • 正经开搞
      • 线上演示地址:http://rhj1122.github.io/webgl/
      相关产品与服务
      图像处理
      图像处理基于腾讯云深度学习等人工智能技术,提供综合性的图像优化处理服务,包括图像质量评估、图像清晰度增强、图像智能裁剪等。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档