You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

将GLM向量类型通过glUniform传入OpenGL的布尔类型适配问题

解决glUniform布尔类型的类型安全包装问题

我之前在做OpenGL的glUniform类型安全包装时,也碰到过布尔类型适配的坑——毕竟GLSL的bool系列和C++/OpenGL的布尔类型对应关系有点绕,不过梳理清楚底层逻辑后就好办了。下面是我总结的完整解决方案:

核心逻辑先理清楚

首先得明确:OpenGL本身没有专门的glUniform*b系列函数,GLSL的bool/bvecN类型在底层是用整数传递的(GL_TRUE对应1,GL_FALSE对应0)。所以我们的包装函数本质是把布尔类型(不管是C++的bool还是OpenGL的GLboolean)转换成整数,再调用对应的glUniform*iv接口。

单个布尔值的重载实现

先搞定单个布尔值的情况,我们需要同时支持GLboolean和C++原生bool,避免用户来回转换:

// 直接接收GLboolean类型,适配OpenGL原生布尔语义
void set_uniform(GLint location, GLboolean value) {
    glUniform1i(location, static_cast<GLint>(value));
}

// 接收C++ bool,自动映射到GL的布尔规则
void set_uniform(GLint location, bool value) {
    glUniform1i(location, value ? GL_TRUE : GL_FALSE);
}

这样用户不管传GL_TRUE还是true,都能正确设置GLSL的bool uniform,不用手动做类型转换。

GLM布尔向量的适配

接下来处理GLM的bvec2/bvec3/bvec4,这里有两种实现方式:

方式1:直接重载每个向量类型(简单直观)

适合不想用模板的场景,代码一目了然:

// 适配glm::bvec2
void set_uniform(GLint location, const glm::bvec2& value) {
    GLint data[2] = {
        value.x ? GL_TRUE : GL_FALSE,
        value.y ? GL_TRUE : GL_FALSE
    };
    glUniform2iv(location, 1, data);
}

// 适配glm::bvec3
void set_uniform(GLint location, const glm::bvec3& value) {
    GLint data[3] = {
        value.x ? GL_TRUE : GL_FALSE,
        value.y ? GL_TRUE : GL_FALSE,
        value.z ? GL_TRUE : GL_FALSE
    };
    glUniform3iv(location, 1, data);
}

// 适配glm::bvec4
void set_uniform(GLint location, const glm::bvec4& value) {
    GLint data[4] = {
        value.x ? GL_TRUE : GL_FALSE,
        value.y ? GL_TRUE : GL_FALSE,
        value.z ? GL_TRUE : GL_FALSE,
        value.w ? GL_TRUE : GL_FALSE
    };
    glUniform4iv(location, 1, data);
}

方式2:用模板函数减少重复代码(优雅高效)

如果觉得上面的代码重复,可以用C++17的if constexpr做编译期分支,一套模板搞定所有bvec类型:

#include <array>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>

template<size_t N>
void set_uniform(GLint location, const glm::tvecN<bool, glm::defaultp>& value) {
    std::array<GLint, N> data;
    // 把每个bool转换成GL的整数表示
    for (size_t i = 0; i < N; ++i) {
        data[i] = value[i] ? GL_TRUE : GL_FALSE;
    }
    // 编译期分支,只生成对应N的代码
    if constexpr (N == 2) {
        glUniform2iv(location, 1, data.data());
    } else if constexpr (N == 3) {
        glUniform3iv(location, 1, data.data());
    } else if constexpr (N == 4) {
        glUniform4iv(location, 1, data.data());
    }
}

这种方式既减少了重复代码,又不会有运行时开销,非常适合大型项目。

额外的类型安全加固

为了避免其他类型(比如int)被隐式转换成布尔类型导致的bug,可以把这些不安全的重载直接禁用:

// 禁止int隐式转换为bool调用set_uniform
void set_uniform(GLint location, int) = delete;
// 同理可以禁用其他可能混淆的类型
void set_uniform(GLint location, float) = delete;

这样如果用户不小心传了int或者float,编译器会直接报错,彻底杜绝隐式转换带来的意外。

实际使用示例

最后给个简单的使用例子,验证一下:

// 设置单个bool uniform
GLint enable_shadow_loc = glGetUniformLocation(shader_program, "enable_shadow");
set_uniform(enable_shadow_loc, true);
set_uniform(enable_shadow_loc, GL_FALSE);

// 设置bvec3 uniform
GLint light_mask_loc = glGetUniformLocation(shader_program, "light_mask");
glm::bvec3 light_mask = {true, false, true};
set_uniform(light_mask_loc, light_mask);

内容的提问来源于stack exchange,提问作者Fibbs

火山引擎 最新活动