Skip to content

Latest commit

 

History

History
341 lines (166 loc) · 8.07 KB

07 WGSL基础之表达式.md

File metadata and controls

341 lines (166 loc) · 8.07 KB

07 WGSL基础之表达式

WGSL 除了一些常规的表达式外,还有一些自己独特的表达式。


我们先回顾一下之前学习的一个知识点:着色器的4个生命周期

详细的可查阅之前的文章:WGSL基础之着色器的4个生命周期节点

  1. 着色器模块创建阶段(Shader module creation):

    当 WebGPU createShaderModule 方法被调用后发生,此时将开始解析 WGSL 的源码。

  2. 管线创建阶段(Pipeline creation):

    当 WebGPU createComputePipeline 或 createRenderPipeline 方法被调用后发生,他们分别创建 计算管线 和 渲染管线。

    特别补充:官方更加推荐使用上述 2 个方法的异步版函数,即 createComputePipelineAsync 和 createRenderPipelineAsync,这样可以避免阻塞。

  3. 着色器开始执行阶段(Shader execution start):

    当 GPUQueue 通过 submit() 将命令缓冲区 数据提交给 GPU ,开始执行管线,并调用相对应的入口函数后发生。

    特别补充:在 submit() 之前发生的事情是——渲染通道编码器(GPURenderPassEncoder) 调用 draw() 或 end(),然后 命令编码器(GPUCommandEncoder) 调用了 finish() 方法,得到了命令缓冲区(GPUCommandBuffer)。

  4. 着色器执行完成阶段(Shader execution end):

    当所有着色器中的工作都被完成时发生。


我们在 WGSL 中所使用、编写的各种表达式,都会在上述第 1 个生命周期中被解析。


下面按照 WGSL 官方文档,我们快速得过一遍各种表达式。

千万不要被某些表达式的名字给吓到,实际上这些表达式都很好理解。


创建时表达式

额~,英文是 create-time expressions,直译是 创建时间表达式,我感觉非常别扭,这里我姑且暂时将它翻译为 创建时表达式

名字很绕口,但实际就是指 表达式中那些具体的数字。

可能我理解的不对,但目前我就是这样理解的。


在 WGSL 中定义了 2 个 “抽象数值类型”:

  1. AbstractInt:整数集,即 -2 的 63 次方 到 2 的 63 次方 之间的整数

    abstract 是抽象的意思,而 int 是整数的意思

  2. AbstractFloat:有限浮点数,即 IEEE-754 binary64

    IEEE-754 是二进制浮点数算术标准,而 binary64 是 64 位浮点数


文字表达式

就是用文字来表达某些标量,例如 true、false、AbstractInt、AbstractFloat、i32、u32、f32、f16


括号表达式

就是在运算中,括号的用法。

我都不知道该说点什么


类型构造函数表达式

在声明向量、矩阵、数组时用到的 套路形式,例如:vec2<f32>mat2x3<i32>array<i32,3>

所谓 类型构造函数表达式 是指在上面那些 套路形式中 如何去设置初始值。

例如:vec3<i32>(0,1,0)

可以把上面的 (0,1,0) 看作是 “构造函数(实例对象)初始化时所传的参数”。


假设我并不填写参数,那么 WGSL 会用 “零” 来作为参数的默认值。

不传参数 对应的 “零” 值
bool() false
i32() 0
u32() 0
f32 0.0
f16 0.0

如果是 向量、数组、矩阵 ,它们的元素为上面表格中的某一种类型,那么也遵循这套原则。

例如:

  1. vec2<f32>() 等同于 vec2<f32>(0.0,0.0,0.0)
  2. vec3<i32>() 等同于 vec3<0,0,0>
  3. array<bool,2>() 等同于 array<bool,2>(false,false)

我个人建议 无论在什么情况下都使用明确的值 来填充到表达式中,这样有利于代码阅读。

在 WGSL 中没有 JS 中的那种 “隐式转换”,不同类型的值如果需要转换,则必须使用目标类型的构造函数形式来强制转换。

例如:把 某类型的值强制转换为 布尔值 ,其代码形式为 bool(xxx)


类型值的类型转化(重新解释)

在 WGSL 中可以通过 bitcast 关键词,按照 bitcast<T>(e):T 这种形式,将某个 表达式类型 转化(解释) 成另外一种类型。

例如:bitcast<vec2<f32>>(e):vec2<i32>,将 vec2 每个元素的类型由 f32 转化为 i32。


复合值分解表达式

就是使用 xxx.xx 的形式去获取 xxx 中的某些元素(分量)值以及类型。


某个元素的分量有 N 种表达形式:

  1. a,b,c,d,e...
  2. r,g,b,a
  3. x,y,z,w
  4. IJKL
  5. 数组索引形式,即 [x]

请注意:上述分量的顺序是固定的,例如 b 是指 rgba 中的第 3 个分量。


假设,我们先定义一个数组:var a: vec3<f32> = <vec3><f32>(1.0, 2.0, 3.0)

此时,我们就可以利用上面 4 种形式去 表达 a 的某些元素的值和类型。

var a: vec3<f32> = <vec3><f32>(1.0, 2.0, 3.0);
var b:f32 = a.y; // b = 2.0
var c:vec2<f32> = a.bb // c = (3.0, 3.0)
var d: vec3<f32> = a.zyx // d = (3.0, 2.0, 1.0)
var e:f32 = a[1] // e = 2.0

对于矩阵、数组、结构对象,他们也有自己对应的分量代表。

套路基本相同,本文就不多讲了,实际遇到代码时看一样就明白了。


逻辑表达式

逻辑表达式几个关键词用法和 JS 中的一模一样,例如:

  1. 逻辑否定(取反):假设 e:bool,那么 !e 表示 e 的相反布尔值

    上面的属于 一元逻辑运算符

    下面的都属于 二元逻辑运算符

  2. 短路 或:e1 || e2

  3. 短路 与:e1 && e2

    所谓 短路 是指 根据前面 e1 的值来取决定是否有必要去计算 e2

  4. 逻辑 或:e1 | e2

  5. 逻辑 与:e1 & e2


算术表达式

一类是比较基础的数据加减乘除(+-*/)、取余运算(%),与 JS 中的一模一样,就不再多说了。

另外一类就是 矩阵 相关的几个计算:矩阵相加、相减、相乘、缩放、转置等。


比较表达式

就是 ==!=<<=>>= 这几个比较运算符。

请注意,由于 WGSL 本身属于强类型语言,没有自动的隐式转换,所以不会,也不需要出现 JS 中的 ===


位表达式

按位运算平时在 JS 中并不常用,不过 WGSL 中的 位表达式用法和一般的程序语言没有什么区别。

  1. ~
  2. |
  3. &
  4. ^
  5. <<
  6. >>

这些位运算符的含义自己百度吧。


函数调用表达式

暂时先不讲这个,因为在后面会专门有一篇用来讲解如何声明函数。

实际也没有啥,就是跟 TypeScript 中定义函数的形式相似,即 参数类型、返回值类型等等


变量标识符表达式

我没看懂官方文档关于这个的描述,暂时先忽略。


形式参数表达式

就是参数的类型,例如 a:T


寻址表达式

寻址(address-of) 操作符将一个引用转换为其对应的指针。

r.ref<S,T,A> 转化为 &r.ptr<S,T,A>


间接寻址表达式

间接寻址运算符(indirection) 将指针转化为相应的引用。

和 寻址表达式 刚好是相反的操作

p:ptr<S,T,A> 转化为 *p:ref<S,T,A>


我相信对于绝大多数前端而言,不知道什么是 指针,什么是 引用。

我在查阅了一些 C++ 文章后,简单说一下:

  1. 所谓 “指针” 是指 指向创建真实值(内存)的“变量”
  2. 所谓 “引用” 是指对于 指针 的再次声明变量,换句话说 可以把 引用 当做成 指针的别名

以 JS 举个示例:

let num = 2
let mynum = num

// num 就是指针(这只是个比喻)
// mynum 就是引用 (换句话说:mynum 是指针 num 的别名)

常量标识符表达式

这里的常量实际就是指 let 声明变量时给变量设定的类型。


关于表达式,本文大概过了一遍,如果有不理解的地方,暂时不用去想,等后期 WGSL 代码看的多了,自然就明白了。

下一篇,我们将学习 控制语句,说直白点就是 if、wsitch、while 等。