Scanframe Modular Application 0.1.0
Loading...
Searching...
No Matches
OpenGL Shader

Shader Pipeline

The simple shader program pipeline looks like this.

Shader pipeline

Vertex Data

The input Vertex Data is generated by derived instances of the sf::xgl::TVertexArray template class which provides the creation of QOpenGLBuffer for drawing solid and wire models like sf::xgl::Tube.

The Vertex Shader program applies the projection, view (camera) and model matrices to the vertices.
The Fragment Shader program calculates the color depending on the vertex normal, shininess, light position and more.

Both programs run on the GPU normally.

Programs and Selector

The current program has 2 fragment subprograms:

  • Passthrough (default)
  • Spotlight

To select them use sf::xgl::ShaderProgram::Uniform::uProgram.

Program 'Passthrough'

This is the default program which passes through the color given from the

Program 'Spotlight'

The spotlight program has some specialized uniforms.

To set the angles of the spotlight use sf::xgl::ShaderProgram::Uniform::uCutoff and sf::xgl::ShaderProgram::Uniform::uOuterCutoff.
Position of the spotlight is given by sf::xgl::ShaderProgram::Uniform::uLightPosition and the direction by sf::xgl::ShaderProgram::Uniform::uLightDirection.

Spotlight parameters

Source

Vertex Program

#version 330 core
// Input vertex attributes:
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec4 aColor;
layout (location = 3) in vec2 aTexCoord;
// Output vertex attributes (passed to fragment shader):
out vec3 vNormal;
out vec3 vPosition;
out vec4 vColor;
out vec2 vTexCoord;
// Matrix to place the model at its spot in world space.
uniform mat4 uModelMatrix;
// View or camera position and orientation.
uniform mat4 uViewMatrix;
// Projection matrix:
uniform mat4 uProjectionMatrix;
// Optional offset matrix for the model view matrix.
uniform mat4 uOffsetMatrix;
// Color for when no color is provided in the vertex.
uniform vec4 uColor;
// Point size for when drawing GL_POINTS.
uniform float uPointSize;
bool isZero(vec4 color)
{
return !(color.r != 0.0 || color.g != 0.0 || color.b != 0.0 || color.a != 0.0);
}
bool isStandardColor(vec4 color)
{
return !(color.r != 0.0 || color.g != 0.0 || color.b != 0.0 || color.a != 1.0);
}
void main()
{
// Calculate the model-view matrix:
mat4 modelViewMatrix = uViewMatrix * uModelMatrix;
// When the W value is non-zero apply only then the matrix.
if (uOffsetMatrix[3][3] != 0)
modelViewMatrix *= uOffsetMatrix;
// Transform vertex position to clip space.
gl_Position = uProjectionMatrix * modelViewMatrix * vec4(aPosition, 1.0);
// Set the point size when the uniform is non-zerp and 'GL_PROGRAM_POINT_SIZE' is enabled.
if (uPointSize > 0)
{
gl_PointSize = uPointSize;
}
// Transform normal vector to world space:
vNormal = mat3(transpose(inverse(modelViewMatrix))) * aNormal;
// Pass the vertex position to the fragment shader:
vPosition = vec3(modelViewMatrix * vec4(aPosition, 1.0));
// When no color is given.
if (isStandardColor(aColor))
{
// Check if the given color is transparent.
// When the uniform uColor is not set value is {0,0,0,0}).
if (isZero(uColor))
{
// When transparent use a gray partial transparent color.
vColor = vec4(0.7, 0.7, 0.7, 0.7);
}
else
{
// Use the uniform color.
vColor = uColor;
}
}
else
{
// Propagate the given color.
vColor = aColor;
}
// Pass the texture coordinate to the fragment shader.
vTexCoord = aTexCoord;
}

Fragment Program

#version 330 core
// Input vertex attributes from the vertex shader:
in vec3 vNormal;
in vec3 vPosition;
in vec4 vColor;
in vec2 vTexCoord;
// Output fragment color:
out vec4 fragColor;
// Positioning the spotlight
uniform vec3 uLightPosition;
// Sets the direction of the spotlight
uniform vec3 uLightDirection;
// Cutoff angle in radians for the spotlight.
uniform float uCutoff;
// Outer cutoff angle in radians for the spotlight.
uniform float uOuterCutoff;
// The light color in general.
uniform vec3 uLightColor;
// The ambient light strength.
uniform float uAmbientStrength;
// The diffused light strength.
uniform float uDiffuseStrength;
// The specular light strength.
uniform float uSpecularStrength;
// The surface reflection light strength.
uniform float uShininess;
// View or camera position and orientation.
uniform mat4 uViewMatrix;
//
uniform sampler2D uTexture;
// Texture switch, int typed since bool is not working.
uniform int uTextureEnable;
// Selects a fragment program. where the default is 'passthrough' and 1 'spotlight'.
uniform int uProgram;
vec4 getColor()
{
// Get the color.
vec4 color;
switch (uTextureEnable)
{
default :
return vColor;
case 1:
return texture(uTexture, vTexCoord);
}
}
vec4 spotLightColor()
{
// Return color value.
vec4 returnColor;
// Get the color.
vec4 color = getColor();
// Normalize normal once to avoid redundant calls
vec3 normal = normalize(vNormal);
// Get the view position from the matrix translation which is the 4th column.
vec3 viewPosition = vec3(uViewMatrix[3][0], uViewMatrix[3][1], uViewMatrix[3][2]);
// Ambient lighting
vec3 ambient = uAmbientStrength * uLightColor;
// Initialize world space position.
vec3 worldPosition = vPosition;
// Transform vPosition back to world space
worldPosition = vec3(inverse(uViewMatrix) * vec4(vPosition, 1.0));
// Compute light direction
vec3 lightDir = normalize(uLightPosition - worldPosition);
// Cosine of angle between light direction and fragment direction
float cosTheta = dot(lightDir, normalize(-uLightDirection));
float cosCutoff = cos(uCutoff);
float cosOuterCutoff = cos(uOuterCutoff);
//
if (cosTheta > cosCutoff)
{
// Diffuse component
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = uDiffuseStrength * diff * uLightColor;
// Specular component
vec3 viewDir = normalize(viewPosition - worldPosition);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), uShininess);
vec3 specular = uSpecularStrength * spec * uLightColor;
// Final color
returnColor.rgb = color.rgb * (ambient + diffuse + specular);
}
else if (cosTheta > cosOuterCutoff)
{
// Smooth transition using smoothstep
float intensity = smoothstep(cosOuterCutoff, cosCutoff, cosTheta);
// Diffuse component
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = uDiffuseStrength * diff * uLightColor;
// Specular component
vec3 viewDir = normalize(viewPosition - worldPosition);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), uShininess);
vec3 specular = uSpecularStrength * spec * uLightColor;
// Apply smooth attenuation
returnColor.rgb = color.rgb * (ambient + intensity * (diffuse + specular));
}
else
{
// Outside the spotlight cone, only ambient lighting
returnColor.rgb = color.rgb * ambient;
}
// Apply the alpha value of the set color(not sure if this has any effect.)
returnColor.a = color.a;
// Return the modified color.
return returnColor;
}
void main()
{
switch (uProgram)
{
case 1:
// Create a spotlight effect.
fragColor = spotLightColor();
break;
default :
// No effects and a simple passthrough.
fragColor = getColor();
break;
}
}

Tube Rendering

Generating Data

void Tube::generate()
{
auto radius = parameters.radius;
//
// Generate vertices for the cylinder
//
for (int stack = 0; stack <= parameters.stacks; ++stack)
{
auto height = parameters.height * static_cast<float>(stack) / parameters.stacks;
for (int sector = 0; sector <= parameters.sectors; ++sector)
{
auto sectorAngle = static_cast<float>(sector) / parameters.sectors * 2.0f * numbers::pi_v<float>;
auto x = radius * std::cos(sectorAngle);
auto y = radius * std::sin(sectorAngle);
// Calculate the normal vector. For a cylinder, the normal is simply the
// direction from the center of the cylinder to the point on the circumference.
QVector3D normal(x, y, 0.0f);
normal.normalize();
_vertices.push_back({{x, y, height}, {normal.x(), normal.y(), normal.z()}});
}
}
// Check if a wires are rendered.
if (getRenderOptions().testFlag(roWires))
{
//
// Generate indices for the cylinder wires (GL_LINES).
//
for (int stack = 0; stack <= parameters.stacks; ++stack)
{
for (int sector = 0; sector < parameters.sectors; ++sector)
{
_indices.push_back(stack * (parameters.sectors + 1) + sector);
_indices.push_back(stack * (parameters.sectors + 1) + sector + 1);
}
// Connect the last sector to the first sector to close the loop
_indices.push_back(stack * (parameters.sectors + 1) + parameters.sectors);
_indices.push_back(stack * (parameters.sectors + 1));
}
//
// Generate indices for the stacks
//
for (int sector = 0; sector <= parameters.sectors; ++sector)
{
for (int stack = 0; stack < parameters.stacks; ++stack)
{
_indices.push_back(stack * (parameters.sectors + 1) + sector);
_indices.push_back((stack + 1) * (parameters.sectors + 1) + sector);
}
}
_modes.push_back({GL_LINES, _indices.size()});
}
else
{
//
// Generate indices for the cylinder solid (GL_QUADS).
//
for (int sector = 0; sector < parameters.sectors; ++sector)
{
for (int stack = 0; stack < parameters.stacks; ++stack)
{
_indices.push_back(stack * (parameters.sectors + 1) + sector + 1);
_indices.push_back((stack + 1) * (parameters.sectors + 1) + sector + 1);
_indices.push_back((stack + 1) * (parameters.sectors + 1) + sector);
_indices.push_back(stack * (parameters.sectors + 1) + sector);
}
}
_modes.push_back({GL_QUADS, _indices.size()});
}
}