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

本节完!

相关推荐
kyriewen4 小时前
写组件文档写到吐?我用AI自动生成Storybook,同事以后直接抄
前端·javascript·面试
五点六六六5 小时前
你敢信这是非Native页面写出来的渐变效果吗🌝(底层原理解析
前端·javascript·面试
吃西瓜的年年5 小时前
TypeScript
javascript·ubuntu·typescript
熊猫_豆豆8 小时前
一个模拟四轴飞行器在随机气流扰动下悬停飞行的交互式3D仿真网页,包含飞行器建模与PID控制算法
javascript·3d·html·四轴无人机模拟飞行
来恩10039 小时前
jQuery选择器
前端·javascript·jquery
前端繁华如梦9 小时前
树上挂苹果还是挂玻璃球?Three.js 程序化果实的完整实现指南
前端·javascript
CDwenhuohuo10 小时前
优惠券组件直接用 uview plus
前端·javascript·vue.js
川冰ICE10 小时前
TypeScript装饰器与元编程实战
前端·javascript·typescript
AI砖家11 小时前
Vue3组件传参大全,各种传参方式的对比
前端·javascript·vue.js
希望永不加班11 小时前
var局部变量类型推断的利弊
java·服务器·前端·javascript·html