带你快速入门webgl与shader着色器渲染基础

14 篇文章 0 订阅
1 篇文章 0 订阅

webgl的初见(二维)

你是谁?

WebGL经常被当成3D API,人们总想“我可以使用WebGL和一些神奇的东西做出炫酷的3D作品”。 事实上WebGL仅仅是一个光栅化引擎,它可以根据你的代码绘制出点,线和三角形。 想要利用WebGL完成更复杂任务,取决于你能否提供合适的代码,组合使用点,线和三角形代替实现。
WebGL在电脑的GPU中运行。因此你需要使用能够在GPU上运行的代码。 这样的代码需要提供成对的方法。每对方法中一个叫顶点着色器, 另一个叫片断着色器,并且使用一种和C或C++类似的强类型的语言 GLSL。 (GL着色语言)。 每一对组合起来称作一个 program(着色程序)。——资料来源

前端的角度

对于前端而言,我们一般不太需要直接使用webgl的原生API,主流的框架就足以应对我们的开发了,但是如果我们能够了解webgl的原理的话,对于我们使用框架会更加容易上手。

知根知底

webgl特别的地方

  1. WebGL在电脑的GPU中运行。
  2. WebGL是一个“中央空调”,不仅可以在浏览器使用,通过一定的方法,也可以直接在OpenGL使用。
  3. 降低了浏览器的依赖:可以实现一些更加酷炫的效果,同时又不增加浏览器的负担。

简单的流程图

在这里插入图片描述

交个“朋友”吧

shader

  1. 顶点着色器
    作用:计算顶点的位置

  2. 片断着色器
    作用:计算出当前绘制图元中每个像素的颜色值

    还是不知道这2个是什么东东?
    在这里插入图片描述

GPU的翻译:GLSL语言

GLSL全称是 Graphics Library Shader Language (图形库着色器语言),是着色器使用的语言。 它有一些不同于JavaScript的特性,主要目的是为栅格化图形提供常用的计算功能。

  • 顶点着色器

    • 从JS获取的数据方式
    1. Attributes 属性 (从缓冲中获取的数据)
    2. Uniforms 全局变量 (在一次绘制中对所有顶点保持一致值)
    3. Textures 纹理 (从像素或纹理元素中获取的数据,来自于图片的数据)
    4. Varyings 可变量(一种顶点着色器给片断着色器传值的方式)
    • 把最终渲染的位置返回给GPU

      		gl_Position = Vec4(0,0,0,0);
      
  • 片断着色器

    • 从JS获取的数据方式
    1. Attributes 属性 (从缓冲中获取的数据)
    2. Uniforms 全局变量 (在一次绘制中对所有顶点保持一致值)
    3. Varyings 可变量(一种顶点着色器给片断着色器传值的方式)
    • 把最终渲染的位置返回给GPU

      		gl_FragColor = Vec4(r,g,b,a);
      

其他语法:
更多详细请点击链接了解
在这里插入图片描述
在这里插入图片描述

JS层级的操作

渲染前的准备
  1. 着色器(shader)程序创建
  2. 缓存区创建与绑定
  3. 渲染配置设置与渲染
结合代码来看
着色器(shader)程序创建
1. 读取shader

在JS代码中,不管你怎么样引入,GLSL在JS代码中存在的方式是字符串。

  • HTML标签引入

    	<!-- vertex shader -->
    	<script  id="vertex-shader-2d" type="x-shader/x-vertex">
    	attribute vec2 a_position;
    	uniform mat3 u_matrix;
    	varying vec4 v_color;
    	void main() {
    	  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
    	  v_color = gl_Position * 0.5 + 0.5;
    	}
    	</script>
    	<!-- fragment shader -->
    	<script  id="fragment-shader-2d" type="x-shader/x-fragment">
    	precision mediump float;
    	varying vec4 v_color;
    	void main() {
    	  gl_FragColor = v_color;
    	}
    	</script>
    	<script>
    		var vertexShaderSource = document.querySelector("#vertex-shader-2d").text;
    		var fragmentShaderSource = document.querySelector("#fragment-shader-2d").text;
    	</script>
    
  • 文件引入
    在这里插入图片描述 在这里插入图片描述
    在这里插入图片描述

  • 内置字符串

    getFragmentShaderSourece() {
    	 return `void main() {
    		 gl_FragColor = vec4(0, 0.5, 0.5, 1);
    		 }`;
    }
    
2. 编译shader
	const type = gl.VERTEX_SHADER// 顶点着色器  gl.FRAGMENT_SHADER是片断着色器
    let shader = gl.createShader(type); // 创建着色器对象
    // 将引入的shader
    const source = ``// 引入的glsl
    gl.shaderSource(shader, source); // 提供数据源
   	shader = gl.compileShader(shader); // 编译 -> 生成着色器

可以看出来不管是片断还是顶点,流程都是一样的,只是数据源和类型不一样,所以我们封装为一个方法。

  // 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
  createShader(gl, type, source) {
    var shader = gl.createShader(type); // 创建着色器对象
    gl.shaderSource(shader, source); // 提供数据源
    gl.compileShader(shader); // 编译 -> 生成着色器
    var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (success) {
      return shader;
    }
    gl.deleteShader(shader);
  }
3. shader的Program程序创建
  • 着色器引入之后,就需要让JS识别shader,这样才能将数据与shader着色器进行绑定。所以,webgl有一个program对象,只有通过它才能联动shader着色器。

    创建program

    const vertrexShader=this.createShader(this.gl, this.gl.VERTEX_SHADER, vertrexShader),
    const fragmentShader=this.createShader(this.gl, this.gl.FRAGMENT_SHADER, fragmentShader),
    var program = gl.createProgram();
    // 把编译的shader添加到program
    gl.attachShader(program, vertrexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);// 连接program
    gl.useProgram(program);// 使用program
    
缓存区创建与数据绑定

我们来分类型讲各自的数据绑定的方法

  1. attribute绑定
    首先是需要在shader上声明好变量。
    以顶点着色器举例

    attribute vec2 b_position;
      void main() {
        gl_Position = vec4(b_position, 0, 1);
      }
    

    1. 绑定program与JS层
    申明了一个b_position的二维变量。
    在代码中我们只需要这样就可以绑定了。

    const positionAttributeLocation = gl.getAttribLocation(program, 'b_position');
    

    program是我们上面绑定好着色器的程序。
    绑定好之后,并不代表这个数据就完成了,还需启用这个变量的状态。

    gl.enableVertexAttribArray(positionAttributeLocation);
    

    2. 绑定缓存区与JS层
    绑定好了与着色器的通信管道,我们就要开始传数据了,数据一般都是使用缓存区来保存。
    首先是创建

    const positionBuffer = gl.createBuffer();
    

    然后告诉webGL我们接下来要传输的缓存区的数据与格式。

    // WebGL内部的全局变量。 首先绑定一个数据源到绑定点,然后可以引用绑定点指向该数据源。
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    

    绑定之后,我们只需要往里面传数据就可以了。

     // 三个二维点坐标
    var positions = [0, 0, 0, 1, 1, 1];
    // 指定缓存的绑定点, 缓存区大小, 缓存的usage类型
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
    

    3. 通过JS层 打通program与缓存区层
    这样我们与shader的连接与缓存区的数据与连接都搞定了,最后就是打通着色器程序program与缓存区之间的隔阂了。

     var size = 2; // 每次迭代运行提取两个单位数据
    var type = gl.FLOAT; // 每个单位的数据类型是32位浮点型
    var normalize = false; // 不需要 整型数据值在转换为浮点数归一化数据
    var stride = 0; // 0 = 移动单位数量 * 每个单位占用内存(sizeof(type))
    // 每次迭代运行运动多少内存到下一个数据开始点
    var offset = 0; // 从缓冲起始位置开始读取
    // 操作GUI渲染的方式
    gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
    

    完成上面的步骤之后,就意味着我们已经把一个顶点的数组传给program的顶点着色器里去了。

  2. uniform全局变量传递
    首先是shader肯定需要一个变量来接受数据。

     uniform vec2 u_resolution;
     attribute vec2 b_position;
        void main() {
          vec2 zeroToOne = b_position / u_resolution;
          vec2 zeroToTwo = zeroToOne * 2.0;
          vec2 clipSpace = zeroToTwo - 1.0;
          gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
        }
    

    1. 绑定program与JS层
    首先JS层肯定要知道绑定到什么变量。

     var resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
    

    2. JS层传递数据
    由于顶点着色器代码里是一个二维的数据,所以绑定的方法是uniform2f,如果是三维的则是uniform3f,四维的是uniform4f

    gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height)
    

    不需要缓存区?
    uniform这个保存的是一个变量,所以并不需要想attribute那样是一组的顶点数据,这种才需要缓存区来绑定。当然attribute也支持单个传递数据,而不是一组数据:gl.vertexAttrib[1234]f[v]()

  3. textrue纹理
    纹理可以认为是现实生活中的贴纸,我们将一个图片贴在我们画好的形状上。我们首先要获取到贴纸(纹理的数据),然后把它对准形状的位置贴好(纹理地址)。
    纹理一般是用在图片上读取到数据,图片上的数据既有顶点的数据也有颜色的数据,webgl已经提供了API来进行自动处理,我们不需要把它们进行拆分。
    我们需要先传给顶点着色器,再传给片断着色器。这是因为webgl只提供了绑定顶点着色器上attribute的方法:
    在这里插入图片描述
    我们来看看简单的着色器代码。

    • 顶点着色器
      attribute vec2 a_position;
      attribute vec2 a_texCoord;
      varying vec2 v_texCoord;
      void main() {
         gl_Position = vec4(a_position , 0, 1);
         v_texCoord = a_texCoord;
      }
      
    • 片断着色器
      precision mediump float;
      uniform sampler2D u_image;
      varying vec2 v_texCoord;
      void main() {
      gl_FragColor = texture2D(u_image, v_texCoord).bgra;
      }
      

    1.读取图片
    前面我们绑定的顶点位置的数据不在赘述,我们之间看纹理如何绑定。
    引入图片:

      laodImage(imgURL) {
        return new Promise((resolve) => {
          const image = new Image();
          image.src = imgURL; // 必须在同一域名下
          image.onload = function () {
            resolve(image);
          };
        }).then((res) => res);
      }
     const image = await this.laodImage('/img,jpg');
    

    2.创建纹理缓存

      // 创建纹理
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    // 设置参数,让我们可以绘制任何尺寸的图像
    // webgl 1.0
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    // 纹理缩小过滤器
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    

    3.把贴纸(纹理数据)递给program

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);// 加载的图片
    

    4.图片贴的位置(纹理地址)
    很显然,贴的地址和画的形状一样,需要传入一组数据才能进行对应。
    所以着色器是通过attribute来接受。
    所以一组数据需要缓存区来保存。

    const texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
     // 给矩形提供纹理坐标
    var texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(texCoordLocation);
    gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
    

    由于我们画的是正方形,所以一共需要画2次的三角形来进行拼接,同样,也需要一一对应顶点的位置,告诉shader如何把贴纸贴在“正确的地方”。

渲染
  1. 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0); // 指定清空canvas的颜色
gl.clear(gl.COLOR_BUFFER_BIT); // 清空canvas
  1. 画出形状
gl.drawArrays(gl.TRIANGLES, 0, n);

图片的形式来了解上述的过程

  1. 创建program着色程序
    在这里插入图片描述
    2.初始化shader与编译
    在这里插入图片描述
    3.编译之后
    在这里插入图片描述
    4.创建缓冲区并绑定数据
    在这里插入图片描述
    5.渲染
    在这里插入图片描述

总结

至此,我们已经了解如何使用webgl来画二维形状的过程,看到这里相信你已经步入了webgl的大门。相信你看到这里,也知道了,其实webgl并不难懂,无非就是多了一个GLSL语言以及如何使用应用程序(JS、UNITY、cocos2d…等)把数据传给GPU进行渲染。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值