shader入门教程二(语法篇)

这一节我们来熟悉 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中,有三个浮点数精度限定符:highpmediumplowp。它们分别表示高精度、中精度和低精度。我们一般指定为 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;

向量

向量 vec2vec3vec4 表示二、三、四维向量,shader中最常见的基本类型,表示一个点或者一种颜色值都需要使用向量。

c 复制代码
void main(){
    // 定义一个三维变量
    vec3 color = vec3(1.0,0.0,0.0);
    // 定义二维变量
    vec2 position = gl_FragCoord.xy;
}

访问向量的分量可以使用[]方式或者xyzwrgba简写格式

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提供的内置函数来绘制一些效果。

本节完!

相关推荐
她似晚风般温柔7891 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
Jiaberrr2 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy3 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
Ylucius3 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
200不是二百3 小时前
Vuex详解
前端·javascript·vue.js
LvManBa3 小时前
Vue学习记录之三(ref全家桶)
javascript·vue.js·学习
深情废杨杨4 小时前
前端vue-父传子
前端·javascript·vue.js
司篂篂5 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客5 小时前
pinia在vue3中的使用
前端·javascript·vue.js
Jiaberrr7 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选