WebGL-绘制正方形

随着三维地图的越来越流行,作为一个giser不会WebGL都不好意思说自己是做webgis的了。实现一些基本的图形可以用canvasAPI来完成,但如果数据量一多,canvasAPI就力不从心了,所以需要学习一下WebGL的知识。WebGL (Web图形库) 是一种JavaScript API,用于在任何兼容的Web浏览器中呈现交互式3D和2D图形,而无需使用插件。WebGL通过引入一个与OpenGL ES 2.0紧密相符合的APIcanvas元素可被用来通过脚本(通常是JavaScript)绘制图形。比如,它可以被用来绘制图形,制作图片集合,甚至用来实现动画效果。目前支持 WebGL 的浏览器有:Firefox4+,Google Chrome9+, Opera12+, Safari 5.1+Internet Explorer 11+;然而, WebGL一些特性也需要用户的硬件设备支持。

1.创建WebGL上下文

动手之前先介绍两个学习网站,对学习WebGL很有帮忙,WebGL教程MDN

首先合建一个DIV用于WebGL的容器,然后再通过getContext来获取WebGL的上下文。具体代码如下所示:

function main(){
    //获取canvas
    const canvas = document.querySelector("#glcanvas");
    //获取webgl上下文
    const gl = canvas.getContext('webgl');
    if(!gl){
        alert('该浏览器不支持webgl');
    }
    //设置颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    //将设置的颜色作为背景色
    gl.clear(gl.COLOR_BUFFER_BIT);
}

2.渲染场景

开始学习的时候,先从简单的正文形开始绘制,并且开始绘制的是二维图形,图形虽然是二维的正方形,但是会将二维图形绘制在三维场景中,在三维场景中绘制图形比直接在canvas上调用二维图形的API要复杂得多,在三维场景中需要创建着色器,通过它来渲染简单场景并画出正方形。

2.1着色器介绍

着色器是使用GLSL语言来编写的程序,它记录着像素点的位置和颜色。他有两种不同类型的着色器,分别为顶点着色器片段着色器

GLSL是什么?
下面引用百度百科上的说明什么是GLSL
OpenGL着色语言(OpenGL Shading Language)是用来在OpenGL中着色编程的语言,也即开发人员写的短小的自定义程序,他们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。比如:视图转换、投影转换等。GLSL(GL Shading Language)的着色器代码分成2个部分:Vertex Shader(顶点着色器)和Fragment(片断着色器),有时还会有Geometry Shader(几何着色器)。负责运行顶点着色的是顶点着色器。它可以得到当前OpenGL 中的状态,GLSL内置变量进行传递。GLSL其使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。

下面就来介绍一下什么是顶点着色器片段着色器

2.1.1顶点着色器

顶点着色器是一种作用于顶点的着色器,它的工作是将输入顶点从原始坐标系转换到WebGL所使用的缩放空间坐标系。每个轴的坐标范围是从-1.01.0之间。并且不考虑纵横比,实际尺寸或任何其他因素。以下的顶点着色器接收一个我们定义的属性(aVertexPosition)的顶点位置值。之后这个值与两个4x4的矩阵(uProjectionMatrix和uModelMatrix)相乘; 乘积赋值给gl_Position

const vsSource = `
    attribute vec4 aVertexPosition;
    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    void main() {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
    }
  `;

2.1.2片段着色器

片段着色器是指在顶点着色器处理完成图形的顶点后,会被需要绘制的图形的每个像素点调用一次。它的职责是确定像素的颜色,在获取到颜色值后,将该颜色绘制到图形对应像素的对应位置。如下所示为创建了一个片段着色器

const fsSource = `
    void main() {
      gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
  `;

2.2着色器初始化

在创建完顶点颜色器和片段着色器后,需要将它们传递给WebGL,通过编译,并将他们连接在一起。

着色器初始化流程:

  • 使用gl.createShader创建着色器
  • 使用gl.shaderSource将源代码发送到着色器
  • 编译源码
  • 返回编译好的着色器
export class WebGLSource {
    constructor(){

    }
    //初始化着色器程序,让WebGL知道如何绘制我们的数据
    initShaderProgram(gl, vsSource, fsSource) {
        const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsSource);
        const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
      
        // 创建着色器程序
        const shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);
      
        // 创建失败, alert
        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
          alert('创建失败 ' + gl.getProgramInfoLog(shaderProgram));
          return null;
        }
      
        return shaderProgram;
    }

    //创建指定类型的着色器,上传source源码并编译
    loadShader(gl, type, source) {
        //创建一个新的着色器。
        const shader = gl.createShader(type);
        //将源代码发送到着色器。
        gl.shaderSource(shader, source);
        //编译源码
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
          alert('编译失败!');
          gl.deleteShader(shader);
          return null;
        }
        //返回编译的着色器
        return shader;
      }
}

在创建着色器程序之后,我们需要查找WebGL返回分配的输入位置。在上述情况下,我们有一个属性和两个uniforms。属性从缓冲区接收值。顶点着色器的每次迭代都从分配给该属性的缓冲区接收下一个值。uniforms类似于`JavaScript``全局变量。它们在着色器的所有迭代中保持相同的值。由于属性和统一的位置是特定于单个着色器程序的,因此我们将它们存储在一起以使它们易于传递。

2.3创建对象

在画正方形前,我们需要创建一个缓冲器来存储它的顶点,如下:

//创建缓冲器来存储顶点
    initBuffers(gl) {
        //创建缓冲器
        const positionBuffer = gl.createBuffer();
        //绑定上下文。
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        
        const positions = [
            1.0,  1.0,
           -1.0,  1.0,
            1.0, -1.0,
           -1.0, -1.0,
         ];
       
         gl.bufferData(gl.ARRAY_BUFFER,
            new Float32Array(positions),
            gl.STATIC_DRAW);
        return {
            position: positionBuffer,
          };
    }

2.4 绘制场景

当着色器和物体都创建好后,可以开始渲染这个场景了。因为这个例子不会产生动画,所以 drawScene() 方法非常简单,如下所示:

//绘制场景
      drawScene(gl, programInfo, buffers) {
        //用背景色擦除画布
        gl.clearColor(0.0, 0.0, 0.0, 1.0);  
        gl.clearDepth(1.0);  
        gl.enable(gl.DEPTH_TEST); 
        gl.depthFunc(gl.LEQUAL);  
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      
        //建立摄像机透视矩阵
        const fieldOfView = 45 * Math.PI / 180; //设置45度的视图角度
        //设置一个适合实际图像的宽高比
        const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
        //指定在摄像机距离0.1到100单位长度的范围内的物体可见。
        const zNear = 0.1;
        const zFar = 100.0;
        const projectionMatrix = mat4.create();
      
        mat4.perspective(projectionMatrix,
                         fieldOfView,
                         aspect,
                         zNear,
                         zFar);
      
        const modelViewMatrix = mat4.create();
      

        //加载特定位置,并把正方形放在距离摄像机6个单位的的位置
        mat4.translate(modelViewMatrix,    
                       modelViewMatrix, 
                       [-0.0, 0.0, -6.0]);
      
        {
          const numComponents = 2; 
          const type = gl.FLOAT;  
          const normalize = false; 
          const stride = 0;       
          const offset = 0;     
          gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
          gl.vertexAttribPointer(
              programInfo.attribLocations.vertexPosition,
              numComponents,
              type,
              normalize,
              stride,
              offset);
          gl.enableVertexAttribArray(
              programInfo.attribLocations.vertexPosition);
        }
        gl.useProgram(programInfo.program);
        gl.uniformMatrix4fv(
            programInfo.uniformLocations.projectionMatrix,
            false,
            projectionMatrix);
        gl.uniformMatrix4fv(
            programInfo.uniformLocations.modelViewMatrix,
            false,
            modelViewMatrix);
      
        {
          const offset = 0;
          const vertexCount = 4;
          gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
        }
    }

绘制后的正文形如下所示:

正方形.png

整个流程下来,绘制一个正方形就需要那么多的代码,可见比起canvas的基础图形绘制要复杂的多,得多花时间来加强学习。
个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容