// -*- mode: c -*- #extension GL_NV_shadow_samplers_cube : enable struct Material { vec3 ambient; vec3 diffuse; vec3 specular; float shininess; }; struct Light { bool enabled; int type; vec3 position; vec3 direction; vec4 color; float intensity; float cutOff; }; #define MAX_LIGHTS 4 #ifdef GLSL120 varying vec3 fragWorldPos; varying vec3 fragNorm; varying vec2 fragTex; #else in vec3 fragWorldPos; in vec3 fragNorm; in vec2 fragTex; #endif #ifdef GLSL330 out vec4 fragColor; #endif uniform samplerCube skybox; uniform sampler2D ambientMap; uniform sampler2D diffuseMap; uniform sampler2D specularMap; uniform sampler2D normalMap; uniform Material material; uniform Light lights[MAX_LIGHTS]; uniform vec3 cameraPosition; const float GAMMA = 2.2; #ifndef GLSL330 // Compatibility shim for older GLSL versions. vec4 texture(sampler2D tex, vec2 coord) { return texture2D(tex, coord); } vec4 texture(samplerCube tex, vec3 coord) { return textureCube(tex, coord); } #endif vec3 gammaCorrect(vec3 color) { return pow(color, vec3(1.0 / GAMMA)); } vec3 toneMap(vec3 color) { return color / (color + vec3(1.0)); } vec3 lightDirection(Light light) { if(light.type == 0 || light.type == 2) { // point and spot lights return normalize(light.position - fragWorldPos); } else if(light.type == 1) { // directional light return normalize(-light.direction); } return vec3(0.0); // should never be reached. } vec3 lightAttenuate(Light light) { float distance = length(light.position - fragWorldPos); float attenuation = 1.0 / (distance * distance); return light.color.rgb * light.intensity * attenuation; } vec3 lightRadiance(Light light, vec3 direction) { if(light.type == 0) { // point light return lightAttenuate(light); } else if(light.type == 1) { // directional light return light.color.rgb * light.intensity; } else if(light.type == 2) { // spot light float theta = dot(direction, normalize(-light.direction)); // Spot lights only shine light in a specific conical area. // They have no effect outside of that area. if(theta > light.cutOff) { // Feather out the light as it approaches the edge of its cone. float intensity = (theta - light.cutOff) / (1.0 - light.cutOff); return lightAttenuate(light) * intensity; } else { return vec3(0.0); } } return vec3(0.0); // should never be reached. } vec3 materialAmbient() { return texture(ambientMap, fragTex).rgb * material.ambient; } vec3 materialDiffuse() { vec4 color = texture(diffuseMap, fragTex); // discard transparent fragments. if(color.a == 0.0) { discard; } return color.rgb * material.diffuse; } vec3 materialSpecular() { return texture(specularMap, fragTex).rgb * material.specular; } vec3 materialNormal() { // Compute tangent space using fragment data rather than relying // on tangent attributes. See: // http://www.thetenthplanet.de/archives/1180 vec3 tangentNormal = normalize(texture(normalMap, fragTex).xyz * 2.0 - 1.0); vec3 q1 = dFdx(fragWorldPos); vec3 q2 = dFdy(fragWorldPos); vec2 st1 = dFdx(fragTex); vec2 st2 = dFdy(fragTex); vec3 N = normalize(fragNorm); vec3 T = normalize(q1 * st2.t - q2 * st1.t); vec3 B = -normalize(cross(N, T)); mat3 TBN = mat3(T, B, N); return normalize(TBN * tangentNormal); } void main() { vec3 viewDir = normalize(cameraPosition - fragWorldPos); vec3 ambientOcclusion = materialAmbient(); vec3 diffuseColor = materialDiffuse(); vec3 specularColor = materialSpecular(); vec3 normal = materialNormal(); vec3 reflection = reflect(-viewDir, normal); vec3 color = vec3(0.0); // Apply direct lighting. for(int i = 0; i < MAX_LIGHTS; ++i) { Light light = lights[i]; if(!light.enabled) { continue; } vec3 lightDir = lightDirection(light); vec3 halfVector = normalize(lightDir + viewDir); vec3 radiance = lightRadiance(light, lightDir); float lambert = clamp(dot(normal, lightDir), 0.0, 1.0); vec3 diffuseLight = radiance * lambert; float specularFactor = clamp(dot(halfVector, normal), 0.0, 1.0) * float(lambert > 0.0); vec3 specularLight = radiance * pow(specularFactor, material.shininess); color += diffuseLight * diffuseColor + specularLight * specularColor; } // Apply image based ambient lighting. float fresnel = pow(1.0 - clamp(dot(viewDir, normal), 0.0, 1.0), 5); float roughness = 1.0 - (material.shininess / 1000.0); vec3 ambientDiffuse = texture(skybox, normal).rgb * diffuseColor; vec3 ambientSpecular = textureLod(skybox, reflection, roughness * 7.0).rgb * fresnel; vec3 ambientColor = (ambientDiffuse + ambientSpecular) * ambientOcclusion; color += ambientColor; // Apply gamma correction and HDR tone mapping to get the final // color. vec4 finalColor = vec4(toneMap(gammaCorrect(color)), 1.0); #ifdef GLSL330 fragColor = finalColor; #else gl_FragColor = finalColor; #endif }