Shader Pipeline
The simple shader program pipeline looks like this.
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
.
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;
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);
QVector3D normal(x, y, 0.0f);
normal.normalize();
_vertices.push_back({{x, y, height}, {normal.x(), normal.y(), normal.z()}});
}
}
if (getRenderOptions().testFlag(roWires))
{
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);
}
_indices.push_back(stack * (parameters.sectors + 1) + parameters.sectors);
_indices.push_back(stack * (parameters.sectors + 1));
}
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
{
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()});
}
}