0
\$\begingroup\$

I have written a simple OpenGL application using GLSL shaders. Actually, my program just displays a simple cube with a phong lighting effect like below :

enter image description here

Here's a peace of the client code :

pShaderProgram->BindAttribLocation(scene::VERTEX_POSITION, "VertexPosition");
pShaderProgram->BindAttribLocation(scene::VERTEX_TEXTURE, "VertexTexture");
pShaderProgram->BindAttribLocation(scene::VERTEX_NORMAL, "VertexNormal");

glm::mat3 normalMatrix = glm::mat3(glm::vec3(pMeshSceneNode->GetModelViewMatrix()[0]),
glm::vec3(pMeshSceneNode->GetModelViewMatrix()[1]), glm::vec3(pMeshSceneNode->GetModelViewMatrix()[2]));

pShaderProgram->SetUniform("ModelViewProjMatrix", pMeshSceneNode->GetModelViewProjectionMatrix());
pShaderProgram->SetUniform("ModelViewMatrix", pMeshSceneNode->GetModelViewMatrix());
pShaderProgram->SetUniform("NormalMatrix", normalMatrix);

pShaderProgram->SetUniform("LightCount", (int)this->m_LightSceneNodeList.size());
pShaderProgram->SetUniform("colorMapSampler", 0);

glm::mat4 viewMatrix = this->m_pCameraSceneNode->GetViewMatrix();

glm::vec4 lightPosition = viewMatrix * glm::vec4(this->m_LightSceneNodeList[0]->GetPosition(), 1.0f);
glm::vec3 lightAmbient = this->m_LightSceneNodeList[0]->GetLightMaterial(scene::AMBIENT);
glm::vec3 lightDiffuse = this->m_LightSceneNodeList[0]->GetLightMaterial(scene::DIFFUSE);
glm::vec3 lightSpecular = this->m_LightSceneNodeList[0]->GetLightMaterial(scene::SPECULAR);

pShaderProgram->SetUniform("LightInfos[0].Position", lightPosition);
pShaderProgram->SetUniform("LightInfos[0].La", lightAmbient);
pShaderProgram->SetUniform("LightInfos[0].Ld", lightDiffuse);
pShaderProgram->SetUniform("LightInfos[0].Ls", lightSpecular);

glm::vec3 materialAmbient = pMeshGeometry->GetMaterial()->GetAmbient();
glm::vec3 materialDiffuse = pMeshGeometry->GetMaterial()->GetDiffuse();
glm::vec3 materialSpecular = pMeshGeometry->GetMaterial()->GetSpecular();
float shininess = pMeshGeometry->GetMaterial()->GetShininess();

pShaderProgram->SetUniform("MaterialInfos.Ka", materialAmbient);
pShaderProgram->SetUniform("MaterialInfos.Kd", materialDiffuse);
pShaderProgram->SetUniform("MaterialInfos.Ks", materialSpecular);
pShaderProgram->SetUniform("MaterialInfos.Shininess", shininess);

pMeshGeometry->GetVertexArray().Lock();
{
    if (pMeshGeometry->GetMaterial()->HasTexture())
    {
        glActiveTexture(GL_TEXTURE0);
        pMeshGeometry->GetMaterial()->GetTexture()->Lock();
    }
    pMeshGeometry->GetIndexBuffer().Lock();
    {
        glDrawElements(pMeshGeometry->GetPrimitiveType(),
            pMeshGeometry->GetIndexBufferSize(), GL_UNSIGNED_INT, OFFSET_BUFFER(0));
    }
    pMeshGeometry->GetIndexBuffer().Unlock();

    if (pMeshGeometry->GetMaterial()->HasTexture())
        pMeshGeometry->GetMaterial()->GetTexture()->Unlock();
}
pMeshGeometry->GetVertexArray().Unlock();

Here's my vertex shader :

#version 440

/*
** Vertex attributes.
*/
layout (location = 0) in vec4 VertexPosition;
layout (location = 1) in vec2 VertexTexture;
layout (location = 2) in vec3 VertexNormal;

/*
** Uniforms.
*/
uniform mat3 NormalMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 ModelViewProjMatrix;

/*
** Outputs.
*/
out vec3 Position;
out vec2 TexCoords;
out vec3 Normal;

/*
** Vertex shader entry point.
*/
void main(void)
{
    Position = vec3(ModelViewMatrix * VertexPosition);
    Normal = NormalMatrix * VertexNormal;
    TexCoords = VertexTexture;

    gl_Position = ModelViewProjMatrix * VertexPosition;
}

And my fragment shader :

#version 440

#define MAX_LIGHT_COUNT 10

/*
** Output color value.
*/
layout (location = 0) out vec4 FragColor;

/*
** Inputs.
*/
in vec3 Position;
in vec2 TexCoords;
in vec3 Normal;

/*
** Uniforms.
*/
uniform sampler2D ColorMapSampler;

struct Light
{
    vec4 Position;
    vec3 La, Ld, Ls;
};

struct Material
{
    vec3 Ka, Kd, Ks;
    float Shininess;
};

uniform struct Light LightInfos[MAX_LIGHT_COUNT];
uniform struct Material MaterialInfos;

uniform int LightCount;

/*
** Basic phong shading.
*/
vec3 Basic_Phong_Shading(vec3 normalDir, vec3 lightDir, vec3 viewDir, int idx)
{
    vec3 Specular = vec3(0.0f);
    float lambertTerm = max(dot(lightDir, normalDir), 0.0f);

    vec3 Ambient = LightInfos[idx].La * MaterialInfos.Ka;
    vec3 Diffuse = LightInfos[idx].Ld * MaterialInfos.Kd * lambertTerm;

    if (lambertTerm > 0.0f)
    {
        vec3 reflectDir = reflect(-lightDir, normalDir);
        Specular = LightInfos[idx].Ls * MaterialInfos.Ks * pow(max(dot(reflectDir, viewDir), 0.0f), MaterialInfos.Shininess);
    }
    return (Ambient + Diffuse + Specular);
}

/*
** Fragment shader entry point.
*/
void main(void)
{
    vec3 LightIntensity = vec3(0.0f);

    vec4 texDiffuseColor = texture2D(ColorMapSampler, TexCoords);
    vec3 normalDir = (gl_FrontFacing ? -Normal : Normal);

    for (int idx = 0; idx < LightCount; idx++)
    {
        vec3 lightDir = vec3(LightInfos[idx].Position) - Position.xyz;
        vec3 viewDir = -Position.xyz;

        LightIntensity += Basic_Phong_Shading(
            -normalize(normalDir), normalize(lightDir), normalize(viewDir), idx
        );
    }
    FragColor = vec4(LightIntensity, 1.0f) * texDiffuseColor;
}

All works perfectly. But as you can see in the client code there is a lot of 'SetUniform' calls. I think it could be judicious to gather some variables like materials in a simple float array and send this unique variable with a single call of 'SetUniform' instead one for each material component.

Until here the idea was to declare in the fragment shader a structure like below (this is the same way for lights materials):

struct Material
{
    vec3 Ka, Kd, Ks;
    float Shininess;
};

uniform struct Material MaterialInfos;

And the corresponding client code :

glm::vec3 materialAmbient = pMeshGeometry->GetMaterial()->GetAmbient();
glm::vec3 materialDiffuse = pMeshGeometry->GetMaterial()->GetDiffuse();
glm::vec3 materialSpecular = pMeshGeometry->GetMaterial()->GetSpecular();
float shininess = pMeshGeometry->GetMaterial()->GetShininess();

pShaderProgram->SetUniform("MaterialInfos.Ka", materialAmbient);
pShaderProgram->SetUniform("MaterialInfos.Kd", materialDiffuse);
pShaderProgram->SetUniform("MaterialInfos.Ks", materialSpecular);
pShaderProgram->SetUniform("MaterialInfos.Shininess", shininess);

Do you think it's possible to avoid a data struture like this and use a uniform variable like the following one:

uniform float MaterialBuffer[9];

The 3 first indices should be allocated for the ambient light components, the 3 following one for the diffuse light components and finally the 3 lasts for the specular light components.

I tried to use the opengl function 'glUniform3fv' which takes in parameter a float * but without success. So, do you think it's possible to not use the structure in the shader and use a simple array of floats or not? (I know that matrices can be stored into a Texture Buffer Object (TBO) and can be used as an array of samplerBuffer in the fragment shader but for the materials I don't know if my proposition could be possible...).

What are your opinions ?

Thanks a lot in advance for your help !

\$\endgroup\$
2
\$\begingroup\$

You absolutely do not want to create an array of floating-point uniforms. Some GPUs are not capable of storing uniforms as scalars, so each one of those may potentially take up the same storage as a vec4. There is a limit to the number of uniform components GLSL programs have for any given stage, and you may be approaching it 4x quicker than you ought to be. You should really pack that into something like 3 vec4.

However, the real issue at hand is that what you actually want is either a Shader Storage Buffer Object or Uniform Buffer Object. You can use either in GLSL 4.40, earlier versions of GL only supported UBOs. I would honestly suggest using a simple UBO for this, you would not benefit terribly from an SSBO, but it is an option.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your answer. Effectively I will implement a UBO in my engine to share uniforms between several shader programs. It's a good idea! But for my example of materials in all cases I will have to declare in the client code 3 'SetUniform' calls to send my ambient, diffuse and specular material components. So I will keep my Material structure like above in my fragment shader or declare an array of vec4 with 3 components. Is it right ? Thanks again for your answer. \$\endgroup\$ – user1364743 May 12 '14 at 0:09
  • \$\begingroup\$ Yes, you can do that. You will have to use some swizzling to extract the extra components, but there is no reason you cannot store all of that information inside of 3 vec4s. \$\endgroup\$ – Andon M. Coleman May 12 '14 at 14:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.