在openGL中重新编译着色器

时间:2016-04-12 14:09:31

标签: opengl 3d glsl

我正在编写自己的OpenGL-3D-Application并遇到了一个小问题:

我希望光源的数量是动态的。为此,我的着色器包含我的灯光结构数组:uniform PointLight pointLights[NR_POINT_LIGHTS];

变量NR_POINT_LIGHTS由预处理器设置,其命令由我的应用程序代码(Java)生成。因此,在创建着色器程序时,我会传递所需的PintLights启动量,使用预处理器命令完成源文本,编译,链接和使用。这很有效。

现在我要更改此变量。我重新构建了shader-source-string,重新编译并重新链接 new shaderProgram并继续使用此onoe。看来旧程序中设置的所有制服都在进展中迷失了(当然,我曾经将它们设置为旧程序)。

我对如何解决此问题的看法:

  • 不要编译新程序,而是以某种方式更改当前运行的着色器的源数据并以某种方式重新编译它们,以继续使用具有正确统一值的程序
  • 将旧程序中的所有统一数据复制到新生成的数据

这样做的正确方法是什么?我该怎么做呢?我还不是很有经验,也不知道我的想法是否有可能。

2 个答案:

答案 0 :(得分:2)

您正在寻找统一缓冲区或(仅限4.3+)着色器存储缓冲区。

struct Light {
    vec4 position;
    vec4 color;
    vec4 direction;
    /*Anything else you want*/
}

统一缓冲区:

const int MAX_ARRAY_SIZE = /*65536 / sizeof(Light)*/;
layout(std140, binding = 0) uniform light_data {
    Light lights[MAX_ARRAY_SIZE];
};

uniform int num_of_lights;

统一缓冲区的主机代码:

glGenBuffers(1, &light_ubo);
glBindBuffer(GL_UNIFORM_BUFFER, light_ubo);
glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat) * static_light_data.size(), static_light_data.data(), GL_STATIC_DRAW); //Can be adjusted for your needs
GLuint light_index = glGetUniformBlockIndex(program_id, "light_data");
glBindBufferBase(GL_UNIFORM_BUFFER, 0, light_ubo);
glUniformBlockBinding(program_id, light_index, 0);
glUniform1i(glGetUniformLocation(program_id, "num_of_lights"), static_light_data.size() / 12); //My lights have 12 floats per light, so we divide by 12.

着色器存储缓冲区(仅限4.3+):

layout(std430, binding = 0) buffer light_data {
    Light lights[];
};

/*...*/

void main() {
    /*...*/
    int num_of_lights = lights.length();
    /*...*/
}

着色器存储缓冲区的主机代码(仅限4.3+):

glGenBuffers(1, &light_ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, light_ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat) * static_light_data.size(), static_light_data.data(), GL_STATIC_DRAW); //Can be adjusted for your needs
light_ssbo_block_index = glGetProgramResourceIndex(program_id, GL_SHADER_STORAGE_BLOCK, "light_data");
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, light_ssbo);
glShaderStorageBlockBinding(program_id, light_ssbo_block_index, 0);

两者之间的主要区别在于Uniform Buffers:

  • 与旧的OpenGL3.x硬件兼容,
  • 大多数系统仅限于每个缓冲区65kb
  • 数组需要在着色器的编译时静态声明[最大]大小。

而着色器存储缓冲器:

  • 要求硬件不超过5年
  • API规定的最小允许大小为16Mb(大多数系统允许最多25%的总VRAM)
  • 可以动态查询存储在缓冲区中的任何数组的大小(虽然这可能是旧AMD系统上的错误)
  • 可以比着色器侧的Uniform Buffers慢(大致相当于纹理访问)

答案 1 :(得分:0)

  

不要编译新程序,而是以某种方式更改当前运行的着色器的源数据并以某种方式重新编译它们,以继续使用具有正确统一值的程序

如果我理解正确(意味着您可以更改已编译着色器程序的着色器代码),则在运行时无法执行此操作,但如果您修改着色器源文本,则可以编译新的着色器程序。事实是,你的场景中灯光的变化频率是多少?因为这是一个相当昂贵的过程。

如果您不介意限制并且仅使用已填充信息的着色器中的灯光,可以 指定最大数量的灯光,从而节省了调整源的任务文本和重新编译一个全新的着色器程序,但这会让你对灯的数量有所限制(如果你没有计划在你的场景中有绝对的灯光,但是 计划让你拥有灯的数量相对经常变化然后这可能最适合你)

但是,如果你真的想沿着这里看到的路线走下去:

  

将旧程序中的所有统一数据复制到新生成的数据

你可以看一下使用Uniform Block。如果您要使用具有相似或共享制服的着色器程序,Uniform Blocks是在您的阴影程序中管理这些“通用”统一变量的好方法,或者在您的情况下,随着您增加数量而移动到的着色器着色器中的灯光。这是一个关于统一块here

的好教程

最后,根据您使用的OpenGL版本,您仍然可以实现动态数组大小。 OpenGL 4.3引入了使用缓冲区并具有未绑定数组大小的功能,您可以使用glBindBufferRange将灯数组的长度发送到。您会在this问题和this wiki参考中看到更多关于该功能的讨论。

最后一个可能是我的偏好,但这取决于你是否针对支持旧版OpenGL的硬件。