Por que glUseProgram é chamado de cada quadro com glUniform?

Sep 27 2020

Estou seguindo um tutorial OpenGL v3.3 que me instrui a modificar um atributo uniforme em um sombreador de fragmento usando glUniform4f (consulte o código abaixo). Pelo que entendi, OpenGL é uma máquina de estado, não desvinculamos o shaderProgram atual em uso, em vez disso, modificamos um atributo em um dos shaders anexados ao programa, então por que precisamos chamar glUseProgram em cada quadro?

Eu entendo que este não é o caso das versões posteriores do OpenGL, mas eu ainda gostaria de entender por que é o caso da v3.3

Programa OpenGL:

while (!glfwWindowShouldClose(window))
{
    processInput(window);

    glClearColor(0.2f, 1.0f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);


    glUseProgram(shaderProgram); // the function in question

    float redValue = (sin(glfwGetTime()) / 2.0f) + 0.5f;
    int colorUniformLocation = glGetUniformLocation(shaderProgram, "ourColor");
    glUniform4f(colorUniformLocation, redValue, 0.0f, 0.0f, 1.0f);

    std::cout << colorUniformLocation << std::endl;

    glBindVertexArray(VAO[0]);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glBindVertexArray(VAO[1]);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glfwSwapBuffers(window);
    glfwPollEvents();
}

Fragment Shader

#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;
void main() 
{
 FragColor = ourColor;
}

Edit: esqueci de apontar que glUniform4f define uma nova cor (de forma periódica) a cada quadro, a saída final do código são 2 triângulos com cor animada, removendo glUseProgram do loop while resultando em uma imagem estática, que não é não é o objetivo pretendido do código.

Respostas

4 jcoder Sep 27 2020 at 21:40

No seu caso, você provavelmente não precisa configurá-lo em todos os quadros. No entanto, em programas maiores, você usará vários sombreadores, portanto, precisará definir aquele que deseja antes de usá-lo a cada vez, e provavelmente os exemplos são escritos apenas para isso.

2 NicolBolas Sep 27 2020 at 22:31

Variáveis ​​globais mutáveis ​​(que são efetivamente o estado do OpenGL) são inerentemente perigosas. Um dos perigos mais importantes dos globais mutáveis ​​é fazer suposições sobre seu estado atual que se revelam erradas. Esses tipos de falhas tornam incrivelmente difícil entender se um trecho de código funcionará corretamente ou não, uma vez que seu comportamento depende de algo externo. Algo que é presumido sobre a natureza do mundo, e não definido pela função que o espera.

Seu código deseja emitir dois comandos de desenho que usam um sombreador específico. Ao vincular esse shader no ponto de uso, esse código não está vinculado a nenhuma suposição quanto ao shader atual. Não importa qual era o shader anterior quando você inicia o loop; você está definindo o que precisa ser .

Isso torna este código isolado para quaisquer alterações posteriores que você possa fazer. Se você deseja renderizar uma terceira coisa que usa um shader diferente, seu código continua a funcionar: você redefine o shader no início de cada loop. Se você tivesse apenas definido o sombreador fora do loop e não o redefinisse todas as vezes, seu código seria quebrado por quaisquer alterações de sombreador subsequentes.

Sim, em um pequeno programa de brinquedo como este, é provavelmente um problema fácil de rastrear e consertar. Mas quando você está lidando com código que abrange centenas de arquivos com dezenas de milhares de linhas de código, com relações de dependência espalhadas por bibliotecas de terceiros, tudo o que pode modificar qualquer estado OpenGL específico? Sim, provavelmente é melhor não presumir muito sobre a natureza do mundo.

Aprender bons hábitos desde cedo é uma coisa boa.

Agora, para ser justo, re-especificar um monte de estado OpenGL em cada ponto do programa também é uma má ideia. Fazer suposições / expectativas sobre a natureza do contexto OpenGL como parte de uma função não é ruim a priori. Se você tiver alguma função de renderização para uma malha, está OK para essa função assumir que o usuário limitou o sombreador que pretende usar. Não é o trabalho desta função especificar todos os outros estados que precisam ser especificados para renderização. E, de fato, seria uma classe / função de malha ruim se fizesse isso, já que você não seria capaz de renderizar a mesma malha com um estado diferente.

Mas no início de cada quadro, ou no início de cada parte principal do seu processo de renderização, especificar uma linha de base do estado do OpenGL é perfeitamente válido. Quando você faz um loop de volta ao início de um novo quadro, você não deve basicamente assumir nada sobre o estado do OpenGL. Não porque o OpenGL não se lembre, mas porque você pode estar errado .

A.Khaled Sep 27 2020 at 23:30

Como as respostas e comentários demonstraram, no exemplo declarado em minha pergunta, glUseProgram só pode ser escrito uma vez fora do loop while para produzir a saída pretendida, que é 2 triângulos com cores animadas periodicamente. O mal-entendido que tive é resultado do capítulo seguinte no e-book de learnopengl.comhttps://learnopengl.com/Getting-started/Shaders onde afirma:

"atualizar um uniforme exige que você primeiro use o programa (chamando glUseProgram), porque ele define o uniforme no programa de sombreador ativo no momento."

Eu pensei que toda vez que eu quisesse atualizar o uniforme via glUniform * eu deveria também emitir uma chamada para glUseProgram que é um entendimento incorreto.