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的基础图形绘制要复杂的多,得多花时间来加强学习。
个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容