前言 关于gpu和shader语言
在学习shader 语言的时候,经常会困惑为什么它会这样来设计?与我们接触的Java、javascript等其他语言没有什么共通之处,觉得难以理解这种设计。 这是因为shader语言是跑在GPU的,跟gpu硬件的发展息息相关,与其他的语言(跑在CPU上)不同,了解一些GPU的特性,对我们理解和掌握shader语言会更有帮助。 这篇文章讲解了gpu架构及运行机制,对我们了解有帮助,有兴趣可以详细看下,这里简单写自己粗浅的理解。
- 为什么有顶点着色器/片元着色器的划分,因为gpu渲染图形的一个流程就是这样的
- 为什么shader中要避免写条件判断,也是因为gpu架构原因
webgl基础概念
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坐标, 第二部分是基于第一部分的结果绘制像素点。
顶点着色器
顶点着色器:作用时计算顶点位置,通常是以下形式:
void main() {
gl_Position = doMathToMakeClipspaceCoordinates
}
每个顶点调用一次顶点着色器,每次调用都需要从gl_position
(webgl内置的全局变量)中获取数据。
片元着色器
一个片段着色器的工作是为当前光栅化的像素提供颜色值,通常是以下的形式:
precision mediump float;
void main() {
gl_FragColor = doMathToMakeAColor;
}
每个像素都将调用一次片段着色器,每次调用需要从webgl内置的特殊全局变量gl_FragColor中获取颜色信息。
OpenGL ES 着色器语言
我们知道webgl是基于opengl es发展而来,GLSL ES编程语言的语法与C语言较为类似。 它有一些不同于JavaScript的特性,主要目的是为栅格化图形提供常用的计算功能。
webgl 坐标系
WebGL
采用三维坐标系统,面向屏幕时,如下图所示: 而<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)
缓冲区
缓冲区顾名思义是一块临时的存储区域,通常用于提升程序的性能。webgl缓冲区对象是同样也是webgl系统中的一块存储区域。
目前的理解:缓冲区通常用于一次性向着色器代码传输多个数据,从而减少内存/CPU与GPU的交互次数,提高性能。
缓冲操作是在GPU上获取顶点和其他顶点数据的一种方式。 gl.createBuffer创建一个缓冲;gl.bindBuffer是设置缓冲为当前使用缓冲; gl.bufferData将数据拷贝到缓冲,这个操作一般在初始化完成。 一旦数据存到缓冲中,还需要告诉WebGL怎么从缓冲中提取数据传给顶点着色器的属性。
hello world!welgl 绘制一个点
通过最简单的绘制一个点的代码来学习webgl
//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语言中,有三种类型的数据存储类型(限定符),分别为
attribute
、uniforms
、varyings
。这三种类型的变量都必须是全局变量,其中attribute是为顶点着色器保留的,uniform在顶点着色器和片段着色器中均可使用,varying限定符则用于在顶点和片段着色器之间传递变量。个人理解:限定符不是指数据的类型(float/int)等,而是数据存储的一种方式。
在webgl系统中,有几种方式可以实现从javascript代码传值给着色器:
attrubute
attribute
变量只能出现在顶点着色器中,只能被声明为全局变量,表示逐顶点的信息。attribute
变量类型只能是float、vec2、vec3......
// 顶点着色器
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变量的使用:
// 代码来自《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坐标系统),如下图所示,不管图像的分辨率和尺寸如何,纹理图像的四个点的坐标如下:
矩阵变换
- 移动缩放平移
- 相机矩阵
矩阵基础
向量长度写作 **向量点乘 **
webgl 动画的基础
实现一个不断旋转的三角形,需要不断的擦除和重绘三角形,