環(huán)境光(固有色)
The ambient component of the Phong reflection model basically specifies a minimum brightness. Even if there is no light hitting a surface directly, the ambient component will light up the surface a little bit to stop it from being pure black. The ambient brightness is constant for all surfaces.
We will calculate the ambient component using a percentage of the original intensities of the light source. We will store this ambient percentage as a float with a value between zero (0%) and one (100%), in a variable named ambientCoefficient. For example if ambientCoefficient is 0.05 (5%) and the reflected light intensities are (1,0,0)
, which is pure red light, then the ambient component will be (0.05,0,0)
, which is very dim red light.
vec3 ambient = light.ambientCoefficient * surfaceColor.rgb * light.intensities;
The specular component is what makes a surface look shiny. The word "specular" means "like a mirror," and it is used here because the shiny patches (a.k.a. specular highlights) are fake reflections of light, like a mirror would reflect light.
R is new, and it represents the angle of reflection (AoR).
Notice how when the topcoat reflects light, the rays don't hit the paint layer. Normally the paint layer would change the color of the light by absorbing some of the intensities, but that can't happen if the light doesn't hit the paint. This means that the specular component is usually a different color to the diffuse component. Most specular surfaces don't absorb anything, they just reflect all of the light, which means the specular color would be white. This is why the shiny parts of a car are white, even though the paint is red.
To apply the specular exponent, we take cos(angle) and raise it to the power of the specular exponent. This produces the "specular coefficient", which is the brightness of the reflection.
vec3 incidenceVector = -surfaceToLight; //a unit vector
vec3 reflectionVector = reflect(incidenceVector, normal); //also a unit vector
vec3 surfaceToCamera = normalize(cameraPosition - surfacePosition); //also a unit vector
float cosAngle = max(0.0, dot(surfaceToCamera, reflectionVector));
float specularCoefficient = pow(cosAngle, materialShininess);
vec3 specularComponent = specularCoefficient * materialSpecularColor * light.intensities;
光線距離衰減
float attenuation = 1.0 / (1.0 + light.attenuation * pow(distanceToLight, 2));
The GLSL to combine the ambient, diffuse and specular components, including attenuation, looks like this:
vec3 linearColor = ambient + attenuation*(diffuse + specular);
This is almost the final color for the fragment/pixel. The last step is to do gamma correction.
之前的顏色計算是在線性顏色空間計算的
For example, the 100% red color (1,0,0)
should be twice as bright as the 50% red color (0.5,0,0)
.
但是顯示器的顏色空間不是線性的楔壤;
The 100% red is actually about 4.5 times brighter than the 50% red, which makes the brightness in the 3D scene look wrong.
Gamma correction is pretty simple to implement. You take each of the RGB values and raise them to the power of gamma. Some games give the user a brightness setting which allows them to change the gamma value, but we will just use the constant value 1/2.2
in this article, which is the correct value for CRT monitors.
vec3 gamma = vec3(1.0/2.2);
finalColor = pow(linearColor, gamma);
vec3 gamma = vec3(1.0/2.2, 1.0/2.2, 1.0/2.2);
finalColor = vec3(pow(linearColor.r, gamma.r),
pow(linearColor.g, gamma.g),
pow(linearColor.b, gamma.b));
#version 150
uniform mat4 model;
uniform vec3 cameraPosition;
// material settings
uniform sampler2D materialTex;
uniform float materialShininess;
uniform vec3 materialSpecularColor;
uniform struct Light {
vec3 position;
vec3 intensities; //a.k.a the color of the light
float attenuation;
float ambientCoefficient;
} light;
in vec2 fragTexCoord;
in vec3 fragNormal;
in vec3 fragVert;
out vec4 finalColor;
void main() {
vec3 normal = normalize(transpose(inverse(mat3(model))) * fragNormal);
vec3 surfacePos = vec3(model * vec4(fragVert, 1));
vec4 surfaceColor = texture(materialTex, fragTexCoord);
vec3 surfaceToLight = normalize(light.position - surfacePos);
vec3 surfaceToCamera = normalize(cameraPosition - surfacePos);
//ambient
vec3 ambient = light.ambientCoefficient * surfaceColor.rgb * light.intensities;
//diffuse
float diffuseCoefficient = max(0.0, dot(normal, surfaceToLight));
vec3 diffuse = diffuseCoefficient * surfaceColor.rgb * light.intensities;
//specular
float specularCoefficient = 0.0;
if(diffuseCoefficient > 0.0)
specularCoefficient = pow(max(0.0, dot(surfaceToCamera, reflect(-surfaceToLight, normal))), materialShininess);
vec3 specular = specularCoefficient * materialSpecularColor * light.intensities;
//attenuation
float distanceToLight = length(light.position - surfacePos);
float attenuation = 1.0 / (1.0 + light.attenuation * pow(distanceToLight, 2));
//linear color (color before gamma correction)
vec3 linearColor = ambient + attenuation*(diffuse + specular);
//final color (after gamma correction)
vec3 gamma = vec3(1.0/2.2);
finalColor = vec4(pow(linearColor, gamma), surfaceColor.a);
}
There are new material uniforms:
uniform sampler2D materialTex;
uniform float materialShininess;
uniform vec3 materialSpecularColor;
struct ModelAsset {
tdogl::Program* shaders;
tdogl::Texture* texture;
GLuint vbo;
GLuint vao;
GLenum drawType;
GLint drawStart;
GLint drawCount;
GLfloat shininess; //new this article
glm::vec3 specularColor; //new this article
};
struct Light {
glm::vec3 position;
glm::vec3 intensities;
float attenuation; //new this article
float ambientCoefficient; //new this article
};