使用 C++ 模拟 ShaderLanguage 的 swizzle
经常编写着色器的同学应该对 swizzle重排语法非常熟悉方便又灵活可以说是用过一次便回味无穷。代码vec4 color vec4(1.0, 0.5, 0.0, 1.0); vec3 rgb color.rgb; // { 1.0, 0.5, 0.0 } vec2 xy color.xy; // { 1.0, 0.5 } vec4 bgra color.bgra; // { 0.0, 0.5, 1.0, 1.0 }可惜的是C 中并不存在这样的语法但是可以利用语法特性来模拟它基本的思路是使用一个代理类来存储被操作点的引用以及需要操作的位置信息。知名的 swizzle 实现GLM作为图形编程中的常客GLM 提供了一套和 GLSL 相似的 swizzle 语法只需要在使用前定义宏 GLM_FORCE_SWIZZLE 即可在向量类中使用了:代码#define GLM_FORCE_SWIZZLE #include glm/glm.hpp glm::vec3 v{1.0f, 2.0f, 3.0f}; v.xy v.yz; 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 支持 234 维度的 swizzle以 glm::vec3 来举例它有 3 个元素则能够组成的 swizzle 组合的总数为∑243117也就是说在 glm::vec3 的类定义中会有 117 个类似于 xx, xy, xxx, xyz, xxxx, xyzw 这样的成员位于未命名 union 内。虽然它们共用同一块内存不会增加类的大小但是代码编辑器的智能补全会将它们一一列举出来这会让其他的成员变量、函数淹没在这些符号之间体验上多少有点不好注意到 GLM 的绝大多数向量的计算操作都是使用外部函数例如glm::normalize(v);而没有将它们写成成员函数是否也跟这个问题有点关系EigenEigen 并没有直接提供 swizzle 语法但是它的 IndexedView 提供类似的功能代码Eigen::Vector3f v{ 1.0f, 2.0f, 3.0f }; //swz 类型是 Eigen::IndexedViewEigen::Vector3f, Eigen::Arrayint, 2, 1, Eigen::internal::SingleRange0 auto swz v({1, 0}); swz Eigen::Vector2f{4.0f, 5.0f}; v({0, 1, 2}) v({2, 0, 1}); //相当于 v.xyz v.zxy我没有细看 Eigen 的源码但是表面上猜测IndexedView 类的实现思路基本上也是一种代理的思想并且它应该将绑定数据的引用和位置信息都保存在了类的数据成员中代码Eigen::Vector3f v{ 1.0f, 2.0f, 3.0f }; constexpr int swz2_size sizeof(v({0, 1})); //24 byte constexpr int swz3_size sizeof(v({0, 1, 2})); //24 byte constexpr int swz4_size sizeof(v({0, 0, 1, 2})); //48 byte constexpr int swz5_size sizeof(v({0, 0, 1, 1, 2})); //32 byte 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 时看到模板代码就头疼现在不管多复杂的库代码也能慢慢剖开分析实现思路其中很多技巧都是在一次次造轮子中深入掌握的。总结一下各个实现版本的特点吧
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2465670.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!