这一节我们来熟悉 shader 语法规则以及一些常用的函数。
首先我们把顶点着色器的两个三角形拓展到整个canvas
区域,这样方便我们将位置进行转换与计算,
js
const data = new Float32Array([
-1.0,1.0,
-1.0,-1.0,
1.0,-1.0,
-1.0,1.0,
1.0,1.0,
1.0,-1.0,
])
后续我们所有的片元着色器绘画都是在这个两个三角形组成的完整画布上进行,所以后续学习我们基本上可以不用关注顶点着色器这部分了。
我们只需要关注片元着色器了,这样我们就可以绘制出许多令人惊艳的效果。
js
precision mediump float;
void main(){
gl_FragColor = vec4(0.0,0.0,0.0,0.0);
}
precision mediump float
用于指定浮点数的精度。在GLSL ES中,有三个浮点数精度限定符:highp
、mediump
和lowp
。它们分别表示高精度、中精度和低精度。我们一般指定为 mediump 即可。
void main
函数是程序的入口,程序从main
函数开始执行,返回类型一般为void
,其实 shader 语言特性基本和C语言保持一致。
gl_FragColor
为 shader 的内置变量,代表最终渲染的颜色值,需要接受一个四维向量,即rgba
颜色值。例如红色 rgba(255,0,0,1)
js
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
细心的你已经发现即使是颜色值也需要表示为[0,1]的范围内。是的,webgl使用标准化的坐标体系来进行计算,这样可以兼容更多的硬件设备。
还有你会发现如果是整数1,我也写成了1.0,这是为了规避一些进行数字计算时候,例如整数+浮点数可能造成程序运行失败问题,而shader程序又难以调试,很难找到出问题所在的地方,所以我建议你写代码保持使用浮点数的好习惯。
下面开始学习一些基础语法。
注释
单行注释//
多行注释/*注释*/
分号使用
每行代码需要以;
结尾,否则报错。
条件分支
可以使用if-else
, for
, 以及while
,discard
if else
js
void main(){
if(gl_FragCoord.x > 100.0){
gl_FragColor = vec4(0.0,1.0,0.0,1.0); // 绿色
}else{
gl_FragColor = vec4(1.0,0.0,0.0,1.0); // 红色
}
}
while
js
precision mediump float;
void main() {
// 初始化颜色为红色
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
// 初始化循环变量
float i = 0.0;
// 循环,改变颜色的绿色分量
while (i < 1.0) {
color.g = i;
i += 0.01; // 增加循环变量
}
// 将最终颜色传递给片段输出
gl_FragColor = color;
}
for
js
void main(){
int a = 0;
for(int i = 0;i<10;i++){
a = a+i/10
}
}
discard
片段着色器中使用的关键字,用于丢弃当前片段,即在片段处理过程中直接放弃该片段,不进行进一步的渲染和处理。这可以用于实现一些条件下的片段丢弃,例如根据特定条件决定是否渲染当前片段。
js
void main(){
if(gl_FragCoord.x > 100.0){
gl_FragColor = vec4(0.0,1.0,0.0,1.0); // 绿色
}else{
discard;
}
}
数据类型
shader像c和java一样,是属于强类型语言。整数和浮点数是有区别的。
基本数据类型包括 int
(整数)、float
(浮点数)、bool
(布尔值)等。
c
init a = 1;
float b = 1.0;
bool c = true;
向量
向量 vec2
、vec3
、vec4
表示二、三、四维向量,shader中最常见的基本类型,表示一个点或者一种颜色值都需要使用向量。
c
void main(){
// 定义一个三维变量
vec3 color = vec3(1.0,0.0,0.0);
// 定义二维变量
vec2 position = gl_FragCoord.xy;
}
访问向量的分量可以使用[]
方式或者xyzw
或rgba
简写格式
c
void main(){
// 定义一个四维变量
vec4 color = vec4(1.0,0.0,0.0,1.0);
// 方式1
float a = color[0];
// 方式2
float b = color.x;
// 方式3
float c = color.r;
// 返回一个新的向量
vec3 d = color.xyz;
}
向量的元素可以简写
c
void main(){
// 定义一个3维变量
vec3 color = vec3(1.0); // 等价于vec3 color = vec3(1.0,1.0,1.0);
}
向量之间可以进行加减乘除运算
c
void main() {
vec2 a = vec2(1.0, 2.0);
vec2 b = vec2(3.0, 4.0);
// 加法
vec2 sum = a + b; // 结果:(4.0, 6.0)
// 减法
vec2 difference = a - b; // 结果:(-2.0, -2.0)
// 乘法(标量乘以向量)
vec2 scaled = 2.0 * a; // 结果:(2.0, 4.0)
// 除法(向量除以标量)
vec2 quotient = b / 2.0; // 结果:(1.5, 2.0)
// 向量之间的乘法(对应分量相乘)
vec2 componentProduct = a * b; // 结果:(3.0, 8.0)
// 向量之间的除法(对应分量相除)
vec2 componentQuotient = a / b; // 结果:(1.0/3.0, 2.0/4.0)
}
矩阵
mat
是用于表示矩阵的数据类型的前缀。矩阵是一种二维数组,通常用于表示变换或坐标变换矩阵。mat
前缀可以与数字组合,表示矩阵的行和列的数量
mat2
表示2x2矩阵。mat3
:表示3x3矩阵。mat4
:表示4x4矩阵。
c
// 定义一个2x2矩阵
mat2 matrix2x2 = mat2(1.0, 2.0, 3.0, 4.0);
注意上述矩阵元素顺序,上述代码用数学表示为
矩阵支持运算
矩阵乘法
c
mat4 matrixA = mat4(...);
mat4 matrixB = mat4(...);
mat4 resultMatrix = matrixA * matrixB;
矩阵的逆
矩阵的逆操作通常用于求解线性方程组或进行反变换。在GLSL中,可以使用 inverse()
函数来获取矩阵的逆矩阵
c
mat4 matrixA = mat4(...);
mat4 inverseMatrixA = inverse(matrixA);
矩阵转置
矩阵的转置操作是将矩阵的行和列互换的过程。可以使用 transpose()
函数来获取矩阵的转置。
c
mat4 matrixA = mat4(...);
mat4 transposedMatrixA = transpose(matrixA);
矩阵在webgl中非常重要,后面我会出一章专门介绍。
内置变量
内置变量 | 作用 |
---|---|
gl_Position | 顶点着色器中用于指定顶点位置的内建变量 |
gl_FragColor | 片段着色器中用于指定片段颜色的内建变量 |
gl_FragCoord | 片段着色器中表示当前片段在帧缓冲中的坐标 |
gl_FragCoord.xy
返回的向量坐标原点位于左下角,x 轴正方向从左到右递增,y 轴正方向从下到上递增
预处理指令
预处理指令允许开发者在编译前对代码进行一些自定义操作,使得代码更具灵活性和可维护性。
#define
用于定义宏,可以将标识符替换为特定的文本,例如下面定义一个PI 在代码地方出现PI就会被替换成3.14159
c
#define PI 3.14159
void main(){
float a = PI;
}
#ifdef
与 #endif
用于条件编译
c
#define USE_A
precision mediump float;
void main(){
float a = 1.0;
#ifdef USE_A
a = 0.5;
#endif
gl_FragColor = vec4(vec3(a),1.0);
}
上述代码 ifdef 也可以写成#if defined(USE_A)
#if
#elif
#else
c
#if defined(ENABLE_FEATURE) // 包含某些特性的代码
#elif defined(OTHER_FEATURE) // 包含其他特性的代码
#else // 默认代码
#endif
定义函数
shader为强类型语言,必须指定函数的返回类型。函数的返回类型必须在函数名之前声明,如果函数不返回任何值,则使用 void
c
// 定义一个函数,计算两个浮点数的和
float add(float a, float b) {
return a + b;
}
shader中内置了许多非常实用的函数,下一节会结合本节语法和shader提供的内置函数来绘制一些效果。
本节完!