【openGLES】着色器和程序对象

openGLES中每个程序对象必须连接到一个顶点着色器和一个片段着色器。程序对象被链接为用于渲染的最后“可执行程序”。获得连接后的着色器对象的一般过程包括六个步骤:

  1. 创建一个顶点着色器对象和一个片段着色器对象;
  2. 将源代码连接到每个着色器对象;
  3. 编译着色器对象;
  4. 创建一个程序对象;
  5. 将编译后的着色器对象连接到程序对象;
  6. 链接程序对象

1. 创建和编译一个着色器

GLuint compileShader(GLenum type, const char* source) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &source, nullptr);
    glCompileShader(shader);

    // 检查编译状态
    int success;
    char infoLog[512];
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(shader, 512, nullptr, infoLog);
        std::cerr << "Shader Compile Error (" << (type == GL_VERTEX_SHADER ? "Vertex" : "Fragment") << "):\n" 
                  << infoLog << std::endl;
        glDeleteShader( shader );
        return 0;
    }
    return shader;
}

2. 创建和链接程序

 	const char* vertexShaderSource = R"glsl(...)glsl"; // 替换为上述顶点着色器代码
    const char* fragmentShaderSource = R"glsl(...)glsl"; // 替换为上述片段着色器代码
    GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
    GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);

    // 链接着色器程序
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    // 检查链接状态
    int success;
    char infoLog[512];
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
        std::cerr << "Program Link Error:\n" << infoLog << std::endl;
    }

    // 清理着色器对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

	// ......执行着色器程序
	glUseProgram(shaderProgram);

3. 程序二进制码

可以预先将shader编译好的二进制码保存起来,使用时直接加载,跳过在线编译过程。

void SaveShaderBinary(const std::string& filePath, GLuint program) {
    GLint length = 0;
    glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &length); // 获取二进制数据长度[1,6](@ref)
    
    std::vector<GLubyte> buffer(length);
    GLenum format = 0;
    glGetProgramBinary(program, length, nullptr, &format, buffer.data()); // 获取二进制数据[2,6](@ref)
    
    std::ofstream out(filePath, std::ios::binary);
    out.write(reinterpret_cast<char*>(&format), sizeof(GLenum)); // 先写入格式标识[1](@ref)
    out.write(reinterpret_cast<char*>(buffer.data()), length);   // 再写入二进制数据
    out.close();
}

GLuint LoadShaderBinary(const std::string& filePath) {
    std::ifstream in(filePath, std::ios::binary);
    if (!in) return 0;

    GLenum format;
    in.read(reinterpret_cast<char*>(&format), sizeof(GLenum)); // 读取格式标识
    
    std::vector<GLubyte> buffer(
        (std::istreambuf_iterator<char>(in)),
        std::istreambuf_iterator<char>()
    );
    in.close();

    GLuint program = glCreateProgram();
    glProgramBinary(program, format, buffer.data(), buffer.size()); // 加载二进制[2,6](@ref)
    
    // 验证加载结果
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        glDeleteProgram(program);
        return 0;
    }
    return program;
}

4. 统一变量和属性

4.1 动态获取 Uniform 变量的名称、类型、大小等信息

在 OpenGL/OpenGL ES 中,glGetActiveUniform 是一个关键的函数,用于查询着色器程序中已激活的 Uniform 变量的详细信息。它的核心作用是帮助开发者动态获取 Uniform 变量的名称、类型、大小等信息,尤其适用于需要动态处理着色器变量的场景(如通用材质系统或动态着色器加载)。以下是详细解析和具体用法:

void glGetActiveUniform(
    GLuint program,      // 着色器程序对象ID
    GLuint index,        // Uniform变量的索引(从0到glGetProgramiv返回的GL_ACTIVE_UNIFORMS-1)
    GLsizei bufSize,     // 名称缓冲区的最大长度
    GLsizei *length,     // 实际返回的名称长度(可传NULL)
    GLint *size,         // 返回数组长度(非数组变量为1)
    GLenum *type,        // 返回变量类型(如GL_FLOAT_VEC4)
    GLchar *name         // 存储变量名称的缓冲区
);

//---------------------------------------
GLuint program = ...; // 已链接的着色器程序
GLint numUniforms = 0;
glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numUniforms);

for (GLuint i = 0; i < numUniforms; ++i) {
    GLchar name[64];
    GLint size;
    GLenum type;
    glGetActiveUniform(program, i, sizeof(name), NULL, &size, &type, name);
    
    // 示例输出:u_MVP (type=GL_FLOAT_MAT4, size=1)
    printf("Uniform %d: %s (type=0x%X, size=%d)\n", i, name, type, size);
}

4.2 Uniform的高级用法

// 获取Uniform位置(需在glUseProgram之后调用)
GLint uColorLoc = glGetUniformLocation(program, "u_Color");
glUniform4f(uColorLoc, 1.0f, 0.0f, 0.0f, 1.0f); // 设置红色

// 获取Attribute位置(OpenGL ES 2.0必须获取,3.0+可用layout指定)
GLint aPosLoc = glGetAttribLocation(program, "a_Position");
glEnableVertexAttribArray(aPosLoc);
glVertexAttribPointer(aPosLoc, 3, GL_FLOAT, GL_FALSE, stride, offset);

在 OpenGL/GLSL 中,显式指定 layout(location = X) 主要用于 ​顶点属性(Attribute)​​ 的定位,而 ​Uniform 变量​ 的绑定通常通过 glGetUniformLocation 动态获取位置。但 OpenGL 4.3+ 或 GLSL 330+ 也支持显式为 Uniform 指定位置(需结合 Uniform Buffer Object, UBO)。以下是具体使用方法:

#version 430 core
layout(location = 0) uniform mat4 u_Model;  // 显式指定位置为 0
layout(location = 1) uniform vec3 u_Color;   // 显式指定位置为 1

// 在代码中直接通过位置设置 Uniform 值,无需调用 glGetUniformLocation:
glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(modelMatrix)); // 直接使用位置 0
glUniform3f(1, 1.0f, 0.0f, 0.0f);          // 直接使用位置 1

5. 统一变量缓冲区对象

UBO是一种GPU缓冲区,用于存储着色器中声明的Uniform变量集合(称为Uniform Block)。它允许将多个Uniform变量(如变换矩阵、光照参数)打包成一个数据块,一次性传输到GPU,避免逐变量设置的开销

5.1着色器中定义uniform block

#version 330 core
layout(std140) uniform Matrices { // 使用std140布局
    mat4 u_ViewProj;
    vec3 u_LightDir;
    float u_Intensity;
};

在C/C++中定义UBO数据时,可使用 alignas 或 #pragma pack 强制对齐:

// C++示例
struct alignas(16) Data { // 强制16B对齐
    glm::vec3 b;         // 12B + 4B填充
    glm::mat2 c;         // 16B
    float a;
};

或通过编译器指令调整:

#pragma pack(push, 16) // 设置16B对齐
struct Data { /* 成员定义 */ };
#pragma pack(pop)

5.2 创建并绑定UBO

GLuint ubo;
glGenBuffers(1, &ubo);
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferData(GL_UNIFORM_BUFFER, sizeof(MatricesData), &matricesData, GL_DYNAMIC_DRAW);

5.3 将UBO绑定到特定索引,与着色器建立关联

GLuint blockIndex = glGetUniformBlockIndex(program, "Matrices");	// 获取Uniform Block的位置
glUniformBlockBinding(program, blockIndex, 0); // 绑定到索引0,索引0是自定义的绑定点(类似插槽编号),后续操作都通过这个索引访问数据。
glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);  // UBO绑定到索引0

5.4 动态更新数据

glMapBuffer会返回一个指针(ptr),这个指针直接指向UBO在GPU中的内存。glMapBuffer的操作目标由当前绑定的缓冲区类型决定,而非通过索引指定。当调用glMapBuffer时,OpenGL会直接映射当前通过glBindBuffer绑定的缓冲区对象。例如:

void* ptr = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY);
memcpy(ptr, &newData, sizeof(newData));
glUnmapBuffer(GL_UNIFORM_BUFFER); //告诉OpenGL:“我改完数据了,你可以收回指针权限了。”解除后,ptr指针会失效,不能再使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值