利用 C++ 模拟 ShaderLanguage 的 swizzle

[复制链接]
发表于 昨天 16:45 | 显示全部楼层 |阅读模式
swizzle 语法

经常编写着色器的同砚应该对 swizzle(重排)语法非常熟悉,方便又机动,可以说是用过一次便回味无穷。
代码
  1. vec4 color = vec4(1.0, 0.5, 0.0, 1.0);
  2. vec3 rgb = color.rgb;        // { 1.0, 0.5, 0.0 }
  3. vec2 xy = color.xy;          // { 1.0, 0.5 }
  4. vec4 bgra = color.bgra;      // { 0.0, 0.5, 1.0, 1.0 }
复制代码
痛惜的是,C++ 中并不存在如许的语法,但是可以利用语法特性来模拟它,根本的思绪是利用一个署理类来存储被操纵点的引用以及须要操纵的位置信息。
着名的 swizzle 实现

GLM

作为图形编程中的常客,GLM 提供了一套和 GLSL 相似的 swizzle 语法,只须要在利用前界说宏 GLM_FORCE_SWIZZLE 即可在向量类中利用了:
代码
  1. #define GLM_FORCE_SWIZZLE
  2. #include <glm/glm.hpp>
  3. glm::vec3 v{1.0f, 2.0f, 3.0f};
  4. v.xy = v.yz;
  5. glm::vec3 reverse = v.zyx;
复制代码
GLM 的实现方式是在类的未定名 union 内部界说一系列预界说的 swizzle 组合署理类,这些类只存储一个标记 vec 类内存起始位置的 char _buffer[1],而须要操纵的位置信息则以模板参数情势编译进范例信息本身。
当一个 vec 类被构造时,这些署理类的 _buffer 即被初始化为 vec 实例的内存起始位置,当须要访问署理类的数据时,将 _buffer 转换为 vec 实例化时的数值范例指针,再取出位置信息作为索引即可实现对 vec 数据举行特定模式的访问。
GLM 的 swizzle 实现可以说黑白常优雅,在情势和作用上是最还原 GLSL swizzle 语法的。
然而这种实现方式有一个缺点:全部的 swizzle 组合都是预界说的。GLM 的 vec 支持 2,3,4 维度的 swizzle,以 glm::vec3 来举例,它有 3 个元素,则可以或许构成的 swizzle 组合的总数为:
$$\begin{aligned}N=\sum_{i=2}^{4} 3^i=117 \\\end{aligned}$$也就是说在 glm::vec3 的类界说中会有 117 个类似于 xx, xy, xxx, xyz, xxxx, zyzw 如许的成员(位于未定名 union 内)。固然它们共用同一块内存,不会增长类的巨细,但是代码编辑器的智能补全会将它们逐一枚举出来,这会让其他的成员变量、函数沉没在这些符号之间,体验上多少有点欠好:
留意到 GLM 的绝大多数向量的盘算操纵都是利用外部函数比方 glm::normalize(v); 而没有将它们写成成员函数,是否也跟这个题目有点关系?
Eigen

Eigen 并没有直接提供 swizzle 语法,但是它的 IndexedView 提供类似的功能
代码
  1. Eigen::Vector3f v{ 1.0f, 2.0f, 3.0f };
  2. //swz 类型是 Eigen::IndexedView<Eigen::Vector3f, Eigen::Array<int, 2, 1>, Eigen::internal::SingleRange<0>>
  3. auto swz = v({1, 0});
  4. swz = Eigen::Vector2f{4.0f, 5.0f};
  5. v({0, 1, 2}) = v({2, 0, 1}); //相当于 v.xyz = v.zxy
复制代码
我没有细看 Eigen 的源码,但是外貌上推测,IndexedView 类的实现思绪根本上也是一种署理的头脑,而且它应该将绑定命据的引用和位置信息都生存在了类的数据成员中:
代码
  1. Eigen::Vector3f v{ 1.0f, 2.0f, 3.0f };
  2. constexpr int swz2_size = sizeof(v({0, 1}));                //24 byte
  3. constexpr int swz3_size = sizeof(v({0, 1, 2}));             //24 byte
  4. constexpr int swz4_size = sizeof(v({0, 0, 1, 2}));          //48 byte
  5. constexpr int swz5_size = sizeof(v({0, 0, 1, 1, 2}));       //32 byte
  6. constexpr int swz6_size = sizeof(v({0, 0, 1, 1, 2, 2}));    //40 byte
复制代码
差别长度的 IndexedView 类的巨细是差别的,这阐明 IndexedView 类确实将位置信息也生存成为了数据成员。可以看到差别的长度对应的类巨细增长很符合 8 字节对齐的特性,但风趣的是长度为 4 时比力反常,颠末我的实行,长度为 4 的倍数的 IndexedView 的巨细都比力反常,估计是 Eigen 内部的针对性优化导致的。
总的来说,Eigen 的 IndexedView 完全可以满意 swizzle 的功能,但它的重要目的是通用和高效,没有须要为特定的语法作封装。
我的实现

我在编写 point 类时并不知道 GLM 的 swizzle 模块,更不知道 Eigen 的 IndexedView,但是终极实现出来的代码用的思绪都类似:用一个署理类作为中央层来举行数据的间接访问。
署理类 exchanger 将位置编译进范例信息中,并生存一个操纵数据对象的指针,通过自界说的赋值运算符和范例转换运算符与其他的数据范例举行数据互换,逻辑相称简朴。
为了实现利用 xyzw, rgba, stpq 这些标签指定位置信息,我借用 boost preprocessor 库修改每个 swizzle 函数的调用参数为它对应的 index。对于左值操纵对象,swizzle 返回一个 exchanger(或 const_exchanger) 类的实例,而如果操纵对象是右值,则直接返回一个对应长度的 point 实例(数据拷贝而非引用,制止野指针)。
终极实现的 point 类支持恣意长度(实际受限于 boost preprocessor 和编译器限定)和恣意位置(代码中支持 0-255,但可以通过在 point_swizzler.hpp 的 POINT_SWIZZLE_CONVERT_PREFIX_255 之后继承添加条目支持更多的位置)的 swizzle.
代码在这里,可以在 test.cpp 中检察利用示例,现在只是提供一种 swizzle 的实现,尚未颠末严格测试。
总结

在实现完 point 类之后,我才发现 GLM 和 Eigen 中的类似功能实现,又是一次重复造轮子。但是照旧颇有劳绩的,想当初入门 C++ 时看到模板代码就头疼,现在不管多复杂的库代码也能渐渐剖开分析实现思绪,此中许多本领都是在一次次造轮子中深入把握的。
总结一下各个实现版本的特点吧:
GLM: 美满还原着色器的 swizzle 语法,但组合是固定的;
Eigen: 支持恣意的重排操纵,但没有语法上的封装;
我的实现:支持恣意的重排操纵,但没有完全还原着色器语法。

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表