GLSL OpenGL Each Light Added Get's Darker - opengl

I have a scene that works perfectly with one light. However, when I add two more - each new addition becomes dimmer until it is almost unseen. Is the attenuation factors wrong or could it be something else?
int i = 0;
for(i=0; i<3; i++){
if (lights[i].enabled == 1.0){
//Lighting Attributes
vec4 light_position = vec4(lights[i].position,1.0);
vec4 light_ambient = lights[i].ambient;
vec4 light_diffuse = lights[i].diffuse;
vec4 light_specular = lights[i].specular;
float light_att_constant = 1.0;
float light_att_linear = 0.0;
float light_att_quadratic = 0.01;
float light_shine = 1.0;
//Object Attributes
vec3 obj_position = n_vertex;
vec3 obj_normals = n_normal;
vec4 obj_color = n_colors;
//Calc Distance
vec3 distance_LO = (obj_position - light_position.xyz);
float distance = length(distance_LO);
//Normalize some attributes
vec3 n_light_position = normalize(distance_LO);
//Apply ambience
finalColor *= light_ambient * global_ambient;
//Calc Cosine of Normal and Light
float NdotL = max(dot(obj_normals, n_light_position),0.0);
//Calc Eye Vector (negated position)
vec3 eye_view = -obj_position;
//Check if Surface is facing the Light
if (NdotL > 0){
//Apply lambertian reflection
finalColor += obj_color * light_diffuse * NdotL;
//Calc the half-vector
vec3 half_vector = normalize(light_position.xyz + eye_view);
//Calc angle between normal and half-vector
//See the engine notebook for a diagram.
float NdotHV = max(dot(obj_normals, half_vector), 0.0);
//Apply Specularity
finalColor += obj_color * light_specular * pow(NdotHV, light_shine);
}
//Calc Attenuation
float attenuation = light_att_constant / ((1 + light_att_linear * distance) *
1 + light_att_quadratic * distance * distance);
//Apply Attenuation
finalColor = finalColor * attenuation;
}
}
color = vec4(finalColor.rgb, 1.0);

You multiply in your colours. This means that shadows will get darker.
If you have an area around some relative brightness 1/2, then you multiply it by 1/2 (contribution from that light), you'll get 1/4.
If you have Photoshop or Gimp you can test this yourself with the Multiply blending mode, and three circles, pure red, pure green and pure blue and overlap them. Compare Multiply to Linear Dodge (the plus operation in Photoshop.)
Here's an example.
You'll most certainly want an additive effect, that is, add the terms together.

Related

What is the best OpenGL self-shadowing technique?

I chose the quite old, but sufficient method of shadow mapping, which is OK overall, but I quickly discovered some self-shadowing problems:
It seems, this problem appears because of the bias offset, which is necessary to eliminate shadow acne artifacts.
After some googling, it seems that there is no easy solution to this, so I tried some shader tricks which worked, but not very well.
My first idea was to perform a calculation of a dot multiplication between a light direction vector and a normal vector. If the result is lower than 0, the angle between vectors is >90 degrees, so this surface is pointing outward at the light source, hence it is not illuminated. This works good, except shadows may appear too sharp and hard:
After I was not satisfied with the results, I tried another trick, by multiplying the shadow value by the abs value of the dot product of light direction and normal vector (based on the normal map), and it did work (hard shadows from the previous image got smooth transition from shadow to regular diffuse color), except it created another artifact in situations, when the normal map normal vector is pointing somewhat at the sun, but the face normal vector does not. It also made self-shadows much brighter (but it is fixable):
Can I do something about it, or should I just choose the lesser evil?
Shader shadows code for example 1:
vec4 fragPosViewSpace = view * vec4(FragPos, 1.0);
float depthValue = abs(fragPosViewSpace.z);
vec4 fragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0);
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// transform to [0,1] range
projCoords = projCoords * 0.5 + 0.5;
// get depth of current fragment from light's perspective
float currentDepth = projCoords.z;
// keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
if (currentDepth > 1.0)
{
return 0.0;
}
// calculate bias (based on depth map resolution and slope)
float bias = max(0.005 * (1.0 - dot(normal, lightDir)), 0.0005);
vec2 texelSize = 1.0 / vec2(textureSize(material.texture_shadow, 0));
const int sampleRadius = 2;
const float sampleRadiusCount = pow(sampleRadius * 2 + 1, 2);
for(int x = -sampleRadius; x <= sampleRadius; ++x)
{
for(int y = -sampleRadius; y <= sampleRadius; ++y)
{
float pcfDepth = texture(material.texture_shadow, vec3(projCoords.xy + vec2(x, y) * texelSize, layer)).r;
shadow += (currentDepth - bias) > pcfDepth ? ambientShadow : 0.0;
}
}
shadow /= sampleRadiusCount;
Hard self shadows trick code:
float shadow = 0.0f;
float ambientShadow = 0.9f;
// "Normal" is a face normal vector, "normal" is calculated based on normal map. I know there is a naming problem with that))
float faceNormalDot = dot(Normal, lightDir);
float vectorNormalDot = dot(normal, lightDir);
if (faceNormalDot <= 0 || vectorNormalDot <= 0)
{
shadow = max(abs(vectorNormalDot), ambientShadow);
}
else
{
vec4 fragPosViewSpace = view * vec4(FragPos, 1.0);
float depthValue = abs(fragPosViewSpace.z);
...
}
Dot product multiplication trick code:
float shadow = 0.0f;
float ambientShadow = 0.9f;
float faceNormalDot = dot(Normal, lightDir);
float vectorNormalDot = dot(normal, lightDir);
if (faceNormalDot <= 0 || vectorNormalDot <= 0)
{
shadow = ambientShadow * abs(vectorNormalDot);
}
else
{
vec4 fragPosViewSpace = view * vec4(FragPos, 1.0);
float depthValue = abs(fragPosViewSpace.z);
...

How to make normal map shader with limited range?

I have simple normal map shader for 7 lights and it work on entire screen. How the hell to make it work only on limited distance? I tried calculate distance between light and pixel, and simple 'if' if distance is to big but this don't work for me.
varying vec4 v_color;
varying vec2 v_texCoords;
uniform vec3 lightColor[7];
uniform vec3 light[7];
uniform sampler2D u_texture;
uniform sampler2D u_normals;
uniform vec2 resolution;
uniform bool useNormals;
uniform bool useShadow;
uniform float strength;
uniform bool yInvert;
uniform bool xInvert;
uniform vec4 ambientColor;
void main() {
// sample color & normals from our textures
vec4 color = texture2D(u_texture, v_texCoords.st);
vec3 nColor = texture2D(u_normals, v_texCoords.st).rgb;
// some bump map programs will need the Y value flipped..
nColor.g = yInvert ? 1.0 - nColor.g : nColor.g;
nColor.r = xInvert ? 1.0 - nColor.r : nColor.r;
// this is for debugging purposes, allowing us to lower the intensity of our bump map
vec3 nBase = vec3(0.5, 0.5, 1.0);
nColor = mix(nBase, nColor, strength);
// normals need to be converted to [-1.0, 1.0] range and normalized
vec3 normal = normalize(nColor * 2.0 - 1.0);
vec3 sum = vec3(0.0);
for ( int i = 0; i < 7; ++i ){
vec3 currentLight = light[i];
vec3 currentLightColor = lightColor[i];
// here we do a simple distance calculation
vec3 deltaPos = vec3( (currentLight.xy - gl_FragCoord.xy) / resolution.xy, currentLight.z );
vec3 lightDir = normalize(deltaPos * 1);
float lambert = clamp(dot(normal, lightDir), 0.0, 1.0);
vec3 result = color.rgb;
result = (currentLightColor.rgb * lambert);
result *= color.rgb;
sum += result;
}
vec3 ambient = ambientColor.rgb * ambientColor.a;
vec3 intensity = min(vec3(1.0), ambient + sum); // don't remember if min is critical, but I think it might be to avoid shifting the hue when multiple lights add up to something very bright.
vec3 finalColor = color.rgb * intensity;
//finalColor *= (ambientColor.rgb * ambientColor.a);
gl_FragColor = v_color * vec4(finalColor, color.a);
}
edit:
my map editor screen
close-up of details
You need to measure the length of the light delta vector and use that to attenuate.
Right after the lightDir line, you can put something like this, but you'll have to adjust the FALLOFF constant to get the distance you want. FALLOFF must be greater than 0. As a starting point, a value of 0.1 will give you a light radius of about 4 units. Smaller values enlarge the radius. You might even want to define it as a parameter of each light (make them vec4s).
float distance = length(deltaPos);
float attenuation = 1.0 / (1.0 + FALLOFF * distance * distance);
float lambert = attenuation * clamp(dot(normal, lightDir), 0.0, 1.0);
This attenuation formula has a bell curve. If you want the curve to have a pointy tip, which is maybe more realistic (though probably pointless for 2D lighting), you can add a second parameter (which you can initially give a value of 0.1 and increase from there):
float attenuation = 1.0 / (1.0 + SHARPNESS * distance + FALLOFF * distance * distance);
Someone on this question posted this helpful chart you can play with to visually see how the parameters change the curve.
Also, don't multiply by an integer. This will cause the shader to fail to compile on some devices:
vec3 lightDir = normalize(deltaPos * 1); // The integer 1 is unsupported.

Cook-Torrance shader with more than one point light

I am trying to implement the Cook-Torrance lighting mode with four point lights. While I am getting nice results by using just only one point light, I can't understand which is the correct way to sum up the specular term inside my light loop.
I am defining the materials as follows:
struct material {
vec3 ambient; /* ambient color */
vec3 diffuse; /* diffuse color */
vec3 specular; /* speculr color */
float metallic;
float roughness;
};
...whereby my lights only have one color/intensity property,
struct light {
vec3 position;
vec3 color;
bool enabled;
};
Here is the function inside my fragment shader with the fragment color computation:
vec3 lighting() {
vec3 color = vec3(0.0,0.0,0.0);
float r0 = pow(material.metallic - 1.0,2.0)/pow(material.metallic + 1.0,2.0);
vec3 V = normalize(-v_viewpos);
vec3 N = normalize(v_normal);
for (int i = 0; i < 4; i++) {
if (light[i].enabled) {
vec3 L = normalize(light[i].position - v_viewpos);
// Half-way vector
vec3 halfVector = normalize(L + V);
float NdotL = max(dot(N, L),0.0);
float NdotV = max(dot(N, V),0.0);
if (NdotL > 0.001 && NdotV > 0.001) {
float NdotH = max(0.0, dot(N, halfVector));
float HdotV = max(0.0, dot(halfVector, V));
// Beckmann
float tanAlpha = sqrt(1.0-NdotH*NdotH)/NdotH;
float D = exp(-pow(tanAlpha/material.roughness,2.0))/(4.0*pow(material.roughness,2.0)*pow(NdotH,4.0));
// Shadowing-masking term
float G1 = (2.0 * NdotH * NdotV) / HdotV;
float G2 = (2.0 * NdotH * NdotL) / HdotV;
float G = min(1.0, min(G1, G2));
// Fresnel reflection, Schlick approximation
float F = r0 + (1.0 - r0) * pow(1.0 - NdotL, 5.0);
float R = (F*G*D) / (3.14159 * NdotL * NdotV);
color += light[i].color * R * NdotL;
}
color += material.diffuse * light[i].color;
}
}
return color;
}
I believe the key point here is my wrong computation inside the light loop:
color += light[i].color * R * NdotL;
Here is an example of what I mean, the resulting fragment color is either too dark, or too bright. I am not able to sum up each light contribution to get a nice smooth color gradient among the specular term and the material colors.
I am reading here about gamma correction, but I can't understand if this applies to my question or not.
How should I sum up each light.color with the diffuse, ambient and specular colors of the material, to calculate the final fragment color, by correctly including the total amount of specular highlight contribution of each light?
vec3 V should be the normalized vector starting from the fragment position to the camera position.
vec3 L should be the normalized vector starting from the fragment position to the light position.
One of those vectors is wrong in your shader, depending on the actual value of v_viewpos.
The fresnel should be based on HoV not NoL:
pow(1.0 - HoV, 5.0)
For the diffuse part, you consider your light like ambiant light and not point light.
color += material.diffuse * light[i].color;
should be (for simple Lambertian)
color += material.diffuse * light[i].color * NoL;
Most of your computations look good (including the directions of V and L and the Fresnel term). The only thing is that you might have mixed up how to combine the individual lighting components. For specular and diffuse, you have
color += light[i].color * R * NdotL;
R corresponds to the specular part and NdotL to the diffuse part. Both are additive, however. Therefore, the equation should be (plus considering material parameters):
color += light[i].color * (material.specular * R + material.diffuse * NdotL);
For the ambient term you have
color += material.diffuse * light[i].color;
Replace material.diffuse with material.ambient and this should be correct.
And be sure that your lights are not too bright. The screen cannot display anything brighter than white (or fully saturated red).

GLSL calculating normal on a sphere mesh in vertex shader using noise function by sampling is producing strange graphical errors

I'm trying to calculate the normals on surface of a sphere in the vertex shader because I defer my noise calculation to the vertex shader. The normal results are good when my theta (sampling angle) is closer to 1 but for more detailed terrain & a smaller theta my normals become very incorrect. Here's what I mean:
Accurate normals
More detailed noise with a higher theta
Zoomed in onto surface of last image. Red indicates ridges, blue indicates incorrect shadows
The code I use to calculate normals is:
vec3 calcNormal(vec3 pos)
{
float theta = .1; //The closer this is to zero the less accurate it gets
vec3 vecTangent = normalize(cross(pos, vec3(1.0, 0.0, 0.0))
+ cross(pos, vec3(0.0, 1.0, 0.0)));
vec3 vecBitangent = normalize(cross(vecTangent, pos));
vec3 ptTangentSample = getPos(pos + theta * normalize(vecTangent));
vec3 ptBitangentSample = getPos(pos + theta * normalize(vecBitangent));
return normalize(cross(ptTangentSample - pos, ptBitangentSample - pos));
}
I call calcNormal with
calcNormal(getPos(position))
where getPos is the 3D noise function that takes and returns a vec3 and position is the original position on the sphere.
Thanks to #NicoSchertler the correct version of calcNormal is
vec3 calcNormal(vec3 pos)
{
float theta = .00001;
vec3 vecTangent = normalize(cross(pos, vec3(1.0, 0.0, 0.0))
+ cross(pos, vec3(0.0, 1.0, 0.0)));
vec3 vecBitangent = normalize(cross(vecTangent, pos));
vec3 ptTangentSample = getPos(normalize(pos + theta * normalize(vecTangent)));
vec3 ptBitangentSample = getPos(normalize(pos + theta * normalize(vecBitangent)));
return normalize(cross(ptTangentSample - pos, ptBitangentSample - pos));
}

WebGL Normal calculations from position texture

Iam trying to create a procedural water puddle in webGL with "water ripples" by vertex displacement.
The problem I'm having is that I get a noise I can't explain.
Below is the first pass vertex shader where I calculate the vertex positions that i later render to a texture that i then use in the second pass.
void main() {
float damping = 0.5;
vNormal = normal;
// wave radius
float timemod = 0.55;
float ttime = mod(time , timemod);
float frequency = 2.0*PI/waveWidth;
float phase = frequency * 0.21;
vec4 v = vec4(position,1.0);
// Loop through array of start positions
for(int i = 0; i < 200; i++){
float cCenterX = ripplePos[i].x;
float cCenterY = ripplePos[i].y;
vec2 center = vec2(cCenterX, cCenterY) ;
if(center.x == 0.0 && center.y == 0.0)
center = normalize(center);
// wave width
float tolerance = 0.005;
radius = sqrt(pow( uv.x - center.x , 2.0) + pow( uv.y -center.y, 2.0));
// Creating a ripple
float w_height = (tolerance - (min(tolerance,pow(ripplePos[i].z-radius*10.0,2.0)) )) * (1.0-ripplePos[i].z/timemod) *5.82;
// -2.07 in the end to keep plane at right height. Trial and error solution
v.z += waveHeight*(1.0+w_height/tolerance) / 2.0 - 2.07;
vNormal = normal+v.z;
}
vPosition = v.xyz;
gl_Position = projectionMatrix * modelViewMatrix * v;
}
And the first pass fragment shader that writes to the texture:
void main()
{
vec3 p = normalize(vPosition);
p.x = (p.x+1.0)*0.5;
p.y = (p.y+1.0)*0.5;
gl_FragColor = vec4( normalize(p), 1.0);
}
The second vertex shader is a standard passthrough.
Second pass fragmentshader is where I try to calculate the normals to be used for light calculations.
void main() {
float w = 1.0 / 200.0;
float h = 1.0 / 200.0;
// Nearest Nieghbours
vec3 p0 = texture2D(rttTexture, vUV).xyz;
vec3 p1 = texture2D(rttTexture, vUV + vec2(-w, 0)).xyz;
vec3 p2 = texture2D(rttTexture, vUV + vec2( w, 0)).xyz;
vec3 p3 = texture2D(rttTexture, vUV + vec2( 0, h)).xyz;
vec3 p4 = texture2D(rttTexture, vUV + vec2( 0, -h)).xyz;
vec3 nVec1 = p2 - p0;
vec3 nVec2 = p3 - p0;
vec3 vNormal = cross(nVec1, nVec2);
vec3 N = normalize(vNormal);
float theZ = texture2D(rttTexture, vUV).r;
//gl_FragColor = vec4(1.,.0,1.,1.);
//gl_FragColor = texture2D(tDiffuse, vUV);
gl_FragColor = vec4(vec3(N), 1.0);
}
The result is this:
The image displays the normalmap and the noise I'm refering to is the inconsistency of the blue.
Here is a live demonstration:
http://oskarhavsvik.se/jonasgerling_water_ripple/waterRTT-clean.html
I appreciate any tips and pointers, not only fixes for this problem. But the code in genereal, I'm here to learn.
After a brief look it seems like your problem is in storing x/y positions.
gl_FragColor = vec4(vec3(p0*0.5+0.5), 1.0);
You don't need to store them anyway, because the texel position implicitly gives the x/y value. Just change your normal points to something like this...
vec3 p2 = vec3(1, 0, texture2D(rttTexture, vUV + vec2(w, 0)).z);
Rather than 1, 0 you will want to use a scale appropriate to the size of your displayed quad relative to the wave height. Anyway, the result now looks like this.
The height/z seems to be scaled by distance from the centre, so I went looking for a normalize() and removed it...
vec3 p = vPosition;
gl_FragColor = vec4(p*0.5+0.5, 1.0);
The normals now look like this...