Skip to content

前言 关于gpu和shader语言

在学习shader 语言的时候,经常会困惑为什么它会这样来设计?与我们接触的Java、javascript等其他语言没有什么共通之处,觉得难以理解这种设计。 这是因为shader语言是跑在GPU的,跟gpu硬件的发展息息相关,与其他的语言(跑在CPU上)不同,了解一些GPU的特性,对我们理解和掌握shader语言会更有帮助。 这篇文章讲解了gpu架构及运行机制,对我们了解有帮助,有兴趣可以详细看下,这里简单写自己粗浅的理解。

  • 为什么有顶点着色器/片元着色器的划分,因为gpu渲染图形的一个流程就是这样的
  • 为什么shader中要避免写条件判断,也是因为gpu架构原因

深入GPU硬件架构及运行机制

webgl基础概念

image.png

webgl (全写Web Graphics Library)是一种3D绘图协议,可以直接通过浏览器创建3D场景。 刚接触webgl的时候,会觉得学习了webgl可以做出很炫酷的3d效果,但其实,webgl仅仅只是一套光栅化引擎,它提供了很简单的api,而我们想要绘制出炫酷的3d效果,取决与我们如何组合这些api。

重要:几乎整个WebGL API都是关于如何设置这些成对方法(顶点/片元)的状态值以及运行它们。 对于想要绘制的每一个对象,都需要先设置一系列状态值,然后通过调用 gl.drawArrays 或 gl.drawElements 运行一个着色方法对,使得你的着色器对能够在GPU上运行。 这也体现了WEBGL是一种全局状态的API风格。

着色器

webgl最终是在电脑的gpu运行,所以最终需要在gpu上能运行的代码。这样的代码需要提供:顶点着色器/片元着色器

webgl在gpu上的工作基本分为两个部分:第一部分是将顶点(或数据流)转换到webgl坐标, 第二部分是基于第一部分的结果绘制像素点。

顶点着色器

顶点着色器:作用时计算顶点位置,通常是以下形式:

glsl
void main() {
  gl_Position = doMathToMakeClipspaceCoordinates
  }

每个顶点调用一次顶点着色器,每次调用都需要从gl_position(webgl内置的全局变量)中获取数据。

片元着色器

一个片段着色器的工作是为当前光栅化的像素提供颜色值,通常是以下的形式:

glsl
precision mediump float;

void main() {
  gl_FragColor = doMathToMakeAColor;
}

每个像素都将调用一次片段着色器,每次调用需要从webgl内置的特殊全局变量gl_FragColor中获取颜色信息。

OpenGL ES 着色器语言

我们知道webgl是基于opengl es发展而来,GLSL ES编程语言的语法与C语言较为类似。 它有一些不同于JavaScript的特性,主要目的是为栅格化图形提供常用的计算功能。

webgl 坐标系

WebGL采用三维坐标系统,面向屏幕时,如下图所示: image.png<canvas>绘图区坐标与WebGL坐标系是不同的,需要将WebGL坐标系映射到<canvas>绘图区坐标,默认情况下,如下图所示,WebGL坐标与<canvas>坐标的对应关系如下:

  • <canvas>中心点:(0.0,0.0,0.0)
  • <canvas>的上边缘和下边缘:(-1.0, 0.0, 0.0)和(1.0, 0.0, 0.0)
  • <canvas>的左
  • 边缘和右边缘: (0.0, -1.0, 0.0) 和 (0.0, 1.0, 0.0)

image.png

缓冲区

缓冲区顾名思义是一块临时的存储区域,通常用于提升程序的性能。webgl缓冲区对象是同样也是webgl系统中的一块存储区域。

目前的理解:缓冲区通常用于一次性向着色器代码传输多个数据,从而减少内存/CPU与GPU的交互次数,提高性能。

缓冲操作是在GPU上获取顶点和其他顶点数据的一种方式。 gl.createBuffer创建一个缓冲;gl.bindBuffer是设置缓冲为当前使用缓冲; gl.bufferData将数据拷贝到缓冲,这个操作一般在初始化完成。 一旦数据存到缓冲中,还需要告诉WebGL怎么从缓冲中提取数据传给顶点着色器的属性。

hello world!welgl 绘制一个点

通过最简单的绘制一个点的代码来学习webgl

javascript
//HelloPoint1.js
//顶点着色器程序
var VSHADER_SOURCE =
  'void main() {\n' +
  'gl_Position = vec4(0.5, 0.0, 0.0, 1.0);\n' + //设置坐标
  'gl_PointSize = 50.0;\n' + //设置尺寸
  '}\n';

//片元着色器程序
var FSHADER_SOURCE=
  'void main(){\n'+
  'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n'+ //设置颜色
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');

  var gl = getWebGLContext(canvas);
  if(!gl)
  {
    console.log('Failed to get the rendering context for WebGL');
    return ;
  }

  //初始化着色器
  if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
    console.log('Failed to initialize shaders.');
    return ;
  }

  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.drawArrays(gl.POINTS, 0, 1);
}

动态webgl

为什么我们需要从js中传值给着色器,着色器代码不能直接动态的?

  • 由于着色器代码是以“字符串”的形式嵌入在JavaScript代码中,需要预先编译好,所以在执行过程中无法动态修改;
  • 着色器代码是交给GPU去执行的,GPU执行if等逻辑判断的速度相较于CPU也比较慢,不适宜执行逻辑判断的代码,为了性能,我们应该直接把数据“喂给着色器代码”;

js -> webgl

前置知识:在GLSL语言中,有三种类型的数据存储类型(限定符),分别为attributeuniformsvaryings这三种类型的变量都必须是全局变量,其中attribute是为顶点着色器保留的,uniform在顶点着色器和片段着色器中均可使用,varying限定符则用于在顶点和片段着色器之间传递变量。个人理解:限定符不是指数据的类型(float/int)等,而是数据存储的一种方式。

image.png 在webgl系统中,有几种方式可以实现从javascript代码传值给着色器:

attrubute

  • attribute变量只能出现在顶点着色器中,只能被声明为全局变量,表示逐顶点的信息。
  • attribute变量类型只能是float、vec2、vec3......
glsl
// 顶点着色器
attribute vec4 a_positions; // 使用attribute 声明,并传给内置变量gl_position
void main() {
    gl_Position = a_positions;     // 点的位置
}
// js代码 
// 在js中传值
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');// 获取位置,类似于指针
gl.vertexAttrib3f(a_Position, 0.5, 0.0, 0.0);// 传值

uniform

  • uniform变量可以用在顶点着色器和片元着色器中,且必须是全局变量
  • uniform变量是只读的,它可以是除了数组或结构体之外的任意类型。
  • 如果顶点着色器和片元着色器中声明了同名的uniform变量,那么它就会被两种着色器共享。
  • uniform变量包含了**"一致"**(非逐顶点/逐片元的,各顶点或各片元共用)的数据,JavaScript应该向其传递此类数据。比如,变换矩阵就不是逐顶点的,而是所有顶点共用的,所以它在着色器中是uniform变量。

顶点着色器 -> 片元着色器

varying(可变量)

varying在 顶点着色器 中定义,用于从 顶点着色器向 片元着色器传递数据。 varying传递数据的方式是:在顶点着色器和片元着色器中声明同名、同类型varying变量。

注:varying从顶点着色器传递给片元着色器时并不是直接传递的,而是发生了“光栅化”的过程:根据绘制的图形,对顶点着色器的varying变量进行内插,然后再传递给片元着色器。

使用varying绘制一个变色三角形的代码(缩略代码)说明varing变量的使用: image.png

javascript
// 代码来自《webgl》编程指南一书中coloredTriangle.js
var VSHADER_SOURCE =
  'attribute vec4 a_Color;\n' + // attribute类型的a_color
  'varying vec4 v_Color;\n' + // 声明varying
  'void main() {\n' +
  // .....
  'v_Color = a_Color;\n' + // 把attribute类型的a_color赋给varying变量,
  '}\n';

var FSHADER_SOURCE=
  'precision mediump float;\n' +
  'varying vec4 v_Color;\n' +
  'void main(){\n'+
  // .....
  'gl_FragColor = v_Color;\n'+  // 传递给片元着色器
  '}\n';
// .....

// js代码
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE*5, FSIZE*2);
gl.enableVertexAttribArray(a_Color);

// .....

webgl 纹理

我们在三维场景中看到的精美的模型通常都具有的纹理材质,这些纹理材质是如何映射到三维空间的哪? 纹理映射就是用来解决这个问题的,纹理映射很简单,就是建立纹理图片与三维空间中坐标的映射关系,将纹理图像通过光栅化为对应的片元涂上合适的颜色。 纹理坐标: WebGL使用s和t命名纹理坐标(st坐标系统),如下图所示,不管图像的分辨率和尺寸如何,纹理图像的四个点的坐标如下: image.png

矩阵变换

  • 移动缩放平移
  • 相机矩阵

矩阵基础

向量长度写作 **向量点乘 **

webgl 动画的基础

实现一个不断旋转的三角形,需要不断的擦除和重绘三角形,