I’m thinking about how to optimize the lighting shaders in a game I’m working on. At the moment I’m getting fairly consistent 59/ 60 FPS on an iPad Air, with the occasional dip. I’ve not had a chance to test on older hardware yet, but I’m conscious that performance might take a dive. As per the Apple shader optimization guidelines, I have lots of shaders targeted for specific uses, ie depending on whether the mesh has a texture or not. At the moment I’m just using ambient + diffuse directional lighting (AD), but for certain, shall we say, “money shot” objects (ie important things that the player will spend a lot of time looking at), specular lighting would look really cool (ADS). My experience in the past though has been that adding specular lighting can be pretty expensive.
Whilst comparing various shader examples though, I came across this one, which does the specular calculation in the vertex shader and just does pixel interpolation in the fragment shader, whereas most others do the expensive specular calculations in the fragment shader, ie on a per-pixel basis:
I thought I’d try porting it to Codea, but before I did I wanted to ask the 3D/ Open GL ES experts on this forum what they thought of this shader. I guess it would only really look good with mid to hi-poly objects (an eight vert cube would be no good). Would it represent a saving in performance terms though?
[EDIT you can ignore this paragraph, I just realised that in the other examples, although the specular function is defined outside of the main fragment function, it is called in main, so the specular calculation is being done for every pixel. So rephrasing my question, has anyone else tried doing the specular calculation in the vert shader, and then just using interpolation in the frag shader, as in the code below?] I guess what I’m really asking is, how often is the specular function executed when it is in the fragment shader, but outside the main
function (the approach that most take), versus here, where it is in the vertex shader. I know that code inside the fragment main
function is executed for every single pixel, but what about for outside the main
function?
Here is the shader from the above link. A one-line fragment main function is pretty tempting! (you’d have to add more if you wanted textures of course)
Vert:
#version 130
// Our Model, View and Projection matrices
// we need them to transform the incoming vertices and for lighting
// calculation
uniform mat4 mModel;
uniform mat4 mView;
uniform mat4 mProjection;
// Position of the "Camera" and the Light
// in Model space
uniform vec4 vEyePosition;
uniform vec4 vLightPosition;
// The Colors of the material
uniform vec4 vAmbientMaterial;
uniform vec4 vDiffuseMaterial;
uniform vec4 vSpecularMaterial;
// Vertex properties
in vec4 vVertexPosition;
in vec3 vVertexNormal;
// Final color of the vertex we pass on to the next stage
smooth out vec4 vVaryingColor;
// Returns the specular component of the color
vec4 GetSpecularColor()
{
// Transform the Vertex and corresponding Normal into Model space
vec4 vTransformedNormal = mModel * vec4( vVertexNormal, 1 );
vec4 vTransformedVertex = mModel * vVertexPosition;
// Get the directional vector to the light and to the camera
// originating from the vertex position
vec4 vLightDirection = normalize( vLightPosition - vTransformedVertex );
vec4 vCameraDirection = normalize( vEyePosition - vTransformedVertex );
// Calculate the reflection vector between the incoming light and the
// normal (incoming angle = outgoing angle)
// We have to use the invert of the light direction because "reflect"
// expects the incident vector as its first parameter
vec4 vReflection = reflect( -vLightDirection, vTransformedNormal );
// Calculate specular component
// Based on the dot product between the reflection vector and the camera
// direction.
//
// hint: The Dot Product corresponds to the angle between the two vectors
// hint: if the angle is out of range (0 ... 180 degrees) we use 0.0
float spec = pow( max( 0.0, dot( vCameraDirection, vReflection )), 32 );
return vec4( vSpecularMaterial.r * spec, vSpecularMaterial.g * spec, vSpecularMaterial.b * spec, 1.0 );
}
// Ambient color component of vertex
vec4 GetAmbientColor()
{
return vAmbientMaterial * vec4( 0.2, 0.2, 0.2, 1.0 );
}
// Diffuse Color component of vertex
vec4 GetDiffuseColor()
{
// Transform the normal from Object to Model space
// we also normalize the vector just to be sure ...
vec4 vTransformedNormal = normalize( mModel * vec4( vVertexNormal, 1 ));
// Get direction of light in Model space
vec4 vLightDirection = normalize( vLightPosition - vTransformedNormal );
// Calculate Diffuse intensity
float fDiffuseIntensity = max( 0.0, dot( vTransformedNormal, vLightDirection ));
// Calculate resulting Color
vec4 vDiffuseColor;
vDiffuseColor.xyz = vDiffuseMaterial.rgb * fDiffuseIntensity;
vDiffuseColor.a = 1.0;
return vDiffuseColor;
}
void main(void)
{
vec4 ambientColor = GetAmbientColor();
vec4 diffuseColor = GetDiffuseColor();
vec4 specularColor = GetSpecularColor();
// Combine into final color
vVaryingColor = ambientColor + diffuseColor + specularColor;
// Transform the vertex to MVP Space
gl_Position = mProjection * mView * mModel * vVertexPosition;
}
Frag:
#version 130
out vec4 vFragColor;
in vec4 vVaryingColor;
void main(void)
{
// Just use the interpolated color as our output
// (Colors between vertices are interpolated)
vFragColor = vVaryingColor;
}