Reputation: 197
I´m doing my own engine for my game and I have had a lot of problems with shaders. One of the most annoying is that I can't calculate the lights because a condition in the fragment shader is never meet, so never sums the color of the different materials/lights. The piece of code where I found the problem is, is:
// Compute lights
vec4 totalColorLighting = vec4(0.0);
for (int indexComputeLights = 0; indexComputeLights < MAX_LIGHTS; indexComputeLights++)
{
if (indexComputeLights < numLights) {
numLights is an uniform passed to the fragment shader. If I write totalColorLight = vec4(1,0);
outside the if statement, my models get drawn in white color. If I put it inside, like I want, the screen remains black...
In short, the code that shows the white model is:
for (int indexComputeLights = 0; indexComputeLights < MAX_LIGHTS; indexComputeLights++)
{
totalColorLight = vec4(1,0); // model is white, so here it´s entering
if (indexComputeLights < numLights) {
// code that never get executed
}
}
And the code is not working:
for (int indexComputeLights = 0; indexComputeLights < MAX_LIGHTS; indexComputeLights++)
{
if (indexComputeLights < numLights) {
totalColorLight = vec4(1,0); // model is black, so here it´s NOT entering
}
}
Other ways to get a "workarround" is to change "numLights" to, ie, a constant number like 2 or 1. I revised the vars where I pass the uniform value to the shaders via eclipse debugger, and it´s 1, so it should be entering in the if statement.
The way I upload the data to this int uniform is:
int[] vecNumLights = new int[1];
vecNumLights[0] = numLights;
GLES20.glUniform1iv(gl_numLights_Uniform_Locator, 1, vecNumLights, 0);
Giving a quick look, does somebody knows what I´m down wrong? Do you need more code to be copied ?
By the way, I´m using the 4.4.2 Api 19 SDK
EDIT 1:
I noticed that indexComputeLights is not zero from the beginning. If I write a condition like this:
if (indexComputeLights > 0) totalColorLight = totalColorLight + vec4(0.05);
the model is less white the higher the condition, I mean, if the condition is
if (indexComputeLights > 6)
the color ends being more transparent and black, just in the reverse order I figured it should be. What´s happend here with the for-unroll ?
EDIT2:
I think my problem is very similar to this: http://www.opengl.org/discussion_boards/showthread.php/171366-problem-with-uniform-int-and-for-loop
The differences is that I didn´t notice that the for loop was endless
EDIT3:
I have discovered that numLights (which is an int uniform) is always zero. I can´t understand whats failing, because all the other uniforms looks to load fine. I´m sharing the uniform name between vertex and fragment shaders, but in vertex shaders looks to be working fine.
EDIT4:
I would like to share with you the full vertex and fragment code.
Vertex shader:
#pragma glsl
attribute vec3 position;
attribute vec3 normal;
attribute vec2 texCoord;
// matrices we'll need
uniform mat4 inversedTrasposedModelViewMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
// other uniforms, constant or data we are going to use
const int MAX_LIGHTS = 8;
struct LightSourceParameters {
mediump vec3 ambient;
mediump vec3 lightColor;
mediump vec4 position;
mediump float spotExponent;
mediump float spotCutoff; // (range: [0.0,90.0], 180.0)
mediump vec3 spotDirection;
mediump float constantAttenuation;
mediump float linearAttenuation;
mediump float quadraticAttenuation;
};
uniform LightSourceParameters LightSource[MAX_LIGHTS];
uniform lowp int numLights;
// out parameters to fragment shader: varyings
varying vec3 outNormal;
varying vec2 outTextCoord;
varying vec3 outViewVector;
varying vec3 outLightVector[MAX_LIGHTS];
void main(){
// Calculate view-space position coordinate
vec4 P = modelViewMatrix * vec4(position,1.0);
// Calculate the normal in view-space
outNormal = vec3(inversedTrasposedModelViewMatrix * vec4(normal ,0.0));
// Calculate the view vector in view-space space coordinate
outViewVector = -P.xyz;
// Assign the texture coordinate
outTextCoord = texCoord;
// Calculate clip-space position of each vertex
gl_Position = projectionMatrix * P;
// Calculate light vector for all light source
for (int indexLightVector = 0; indexLightVector < MAX_LIGHTS; indexLightVector++){
if (indexLightVector < numLights) {
/* Si no es ambiental: la unica luz que no lleva asociada vector */
if ((length(LightSource[indexLightVector].ambient) == 0.0) /* no ambiental */
&& (LightSource[indexLightVector].position.w != 0.0)){ /* no directional */
/* La luz es o point o spotLight */
outLightVector[indexLightVector] = vec3(modelViewMatrix*LightSource[indexLightVector].position) - P.xyz;
}
else if (length(LightSource[indexLightVector].ambient) == 0.0) { /* no ambiental */
/* La luz es directional: position es un vector,
* lo transformamos con inversedTransposedModelViewMatrix
* y lo negamos para que vaya desde el punto a la luz y no al revés
*/
outLightVector[indexLightVector] = - vec3(inversedTrasposedModelViewMatrix*LightSource[indexLightVector].position);
}
}
}
}
Fragment shader:
#pragma glsl
precision mediump float;
const int MAX_LIGHTS = 8;
struct LightSourceParameters {
vec3 ambient;
vec3 lightColor;
vec4 position;
float spotExponent;
float spotCutoff; // (range: [0.0,90.0], 180.0)
vec3 spotDirection;
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
};
uniform LightSourceParameters LightSource[MAX_LIGHTS];
struct MaterialParameters {
vec4 emission;
vec4 ambient;
vec4 diffuse;
sampler2D diffuseTexture;
int hasDiffuseTexture;
vec4 specular;
sampler2D specularTexture;
int hasSpecularTexture;
float shininess;
};
uniform MaterialParameters Material;
uniform lowp int numLights;
varying vec3 outNormal;
varying vec2 outTextCoord;
varying vec3 outViewVector;
varying vec3 outLightVector[MAX_LIGHTS];
/* Declaramos cabecera de funcion, necesaria para que GLSL no diga que la funcion no existe, al definirse despues de main */
vec4 computeLight(in MaterialParameters material, in LightSourceParameters lightSource, in vec3 normal, in vec2 textCoord, in vec3 lightVector, in vec3 halfVector);
void main(){
// Normalize the incoming vectors
vec3 normal = normalize(outNormal);
vec3 viewVector = normalize(outViewVector);
vec3 lightVector[MAX_LIGHTS];
vec3 halfVector[MAX_LIGHTS];
// normalize lightvector, compute half vectors and lights
vec4 totalColorLighting = vec4(0.0);
int indexComputeLights = 0;
for (indexComputeLights; indexComputeLights < MAX_LIGHTS; indexComputeLights++){
if (indexComputeLights < numLights) {
totalColorLighting = totalColorLighting + vec4(0.05);
if (length(LightSource[indexComputeLights].ambient) == 0.0 ){ /* no es ambiental, que no tienen vector */
lightVector[indexComputeLights] = normalize(outLightVector[indexComputeLights]);
}
if (length(LightSource[indexComputeLights].ambient) == 0.0 ){ /* no es ambiental, que no tienen half vector */
halfVector[indexComputeLights] = normalize(outLightVector[indexComputeLights] + outViewVector);
}
LightSourceParameters light = LightSource[indexComputeLights];
vec3 currentLightVector = lightVector[indexComputeLights];
vec3 currentHalfVector = halfVector[indexComputeLights];
/* Si la luz es ambiental, halfVector y lightVector son
* indefinidos para esa luz, pero da igual porque no son
* utilizados en el algoritmo que calcula las luces
*/
totalColorLighting = totalColorLighting + computeLight(Material, light, normal, outTextCoord, currentLightVector, currentHalfVector);
indexComputeLights = indexComputeLights + 1;
}
}
vec4 emission = Material.emission;
// vec4 emission = vec4(0.5);
// totalColorLighting = vec4(0.0);
// Compute emission material
if (length(emission) != 0.0) { /* El material tiene un termino emisivo, es decir, emite luz. Lo andimos al total de color calculado */
totalColorLighting = totalColorLighting + emission;
}
/* Devolvemos el color de fragmento calculado para almacenarlo en el framebuffer */
gl_FragColor = totalColorLighting;
//gl_FragColor = vec4(1.0);
}
vec4 computeLight(in MaterialParameters material, in LightSourceParameters lightSource,
in vec3 normal, in vec2 textCoord, in vec3 lightVector, in vec3 halfVector){
float attenuation = 1.0; // no attenuation
vec4 totalLightingColor = vec4(0.0); // no color
if (length(lightSource.ambient) > 0.0){ // es luz ambiente
totalLightingColor = vec4(lightSource.ambient, 1.0) * material.ambient;
}
else { // Is not ambiental light
if (lightSource.position.w == 0.0) { // es un vector, por lo tanto es una luz direccional
attenuation = 1.0; // no attenuation
}
else { // Is a point light or a spot light
float distanceToLight = length(lightVector);
attenuation = 1.0 / (lightSource.constantAttenuation +
(lightSource.linearAttenuation * distanceToLight) +
(lightSource.quadraticAttenuation * distanceToLight * distanceToLight));
if (lightSource.spotCutoff <= 90.0){ /* Is a spot light */
vec3 spotDirection = normalize(lightSource.spotDirection);
float clampedCosine = max(0.0, dot(-lightVector, spotDirection));
if (clampedCosine < cos(radians(lightSource.spotCutoff))){ /* outside the spotlight cone */
attenuation = 0.0; /* full attenuation */
}
else { /* inside the spotlight cone */
attenuation = attenuation * pow(clampedCosine, lightSource.spotExponent);
}
}
}
// Calculo de los terminos de color: diffuso y especular
vec4 diffuseMaterialTerm = vec4(0.0, 0.0, 0.0, 1.0); /* El canal difuso será opaco y negro hasta que se sobreescriban sus datos */
if (material.hasDiffuseTexture == 0) { /* El canal difuso no tiene textura */
diffuseMaterialTerm = material.diffuse;
}
else if (material.hasDiffuseTexture == 1){
diffuseMaterialTerm = texture2D(material.diffuseTexture, textCoord);
}
vec4 diffuseReflection = attenuation * vec4(lightSource.lightColor, 1.0) * diffuseMaterialTerm * max(0.0, dot(normal, lightVector));
vec4 specularReflection = vec4(0.0);
if (dot(normal, lightVector) < 0.0 ) { // light source in the wrong side
specularReflection = vec4(0.0);
}
else { // light source in the right side
float NdotHV = max(dot(normal, halfVector), 0.0); /* Normal-dot-halfvector */
vec4 specularMaterialTerm = vec4 (0.0, 0.0, 0.0, 1.0); /* El canal especular será opaco y negro hasta que se sobreescriban sus datos */
if (material.hasSpecularTexture == 0){
specularMaterialTerm = material.specular;
}
else if (material.hasSpecularTexture == 1){
specularMaterialTerm = texture2D(material.specularTexture, textCoord);
}
specularReflection = attenuation * pow(NdotHV, material.shininess) * vec4(lightSource.lightColor, 1.0) * specularMaterialTerm;
}
totalLightingColor = diffuseReflection + specularReflection;
}
return totalLightingColor;
}
This is the code (Java) I´m using to upload this uniform (numLights):
GLES20.glUseProgram(gl_Program.GetProgramID()); // GetProgramID() returns 3 in my case
int numLights = iLightList.size(); // the scene I want to render has 1 light, so this value is 1
String numLightsString = "numLights";
int gl_numLights_Uniform_Locator = gl_Program.GetUniformLocation(numLightsString); // is 83 in my program
GLES20.glUniform1i(gl_numLights_Uniform_Locator, numLights);
int error = GLES20.glGetError(); // error is always 0
Any help would be very appreciated
EDIT 5:
I found something very strange. If I comment this code in the fragment shader, the uniform numLights is loaded with the correct value:
// vec4 emission = Material.emission;
// if (length(emission) != 0.0) { /* El material tiene un termino emisivo, es decir, emite luz. Lo añadimos al total de color calculado */
// totalColorLighting = totalColorLighting + emission;
// }
But if I do so, I lost the emission light part calculation... Any ideas ?
Upvotes: 0
Views: 6462
Reputation: 21
I am facing identical issue - i.e. passing an int uniform to a vertex shader results in unpredictable behaviour ON SOME HARDWARE: it is working perfectly fine on Android emulator and on my Kindle Fire 2nd gen tablet, but it is failing (crashing the phone!) on Galaxy S III. I discovered the phone crashes because in the vertex shader, the loop
for(int i=0; i<u_Sinks; i++)
where u_Sinks is the int uniform, appears to be infinite. But I do pass 1 to u_Sinks like this:
GLES20.glUniform1i( mSinksH , 1);
and of course I did make sure mSinksH gets assigned a valid value>=0:
mSinksH = GLES20.glGetUniformLocation(mProgramH, "u_Sinks");
Log.d("distorted", "mSinksH="+mSinksH);
and the Log prints out mSinksH=4, which looks correct to me.
Upvotes: -1
Reputation: 17324
It looks like GLES20.glUniform1i
should be used as the GLES20.glUniform1iv
case would be for uniform int numLights[1];
. As I'm sure you already have, GLES20.glUniform1i
should be after GLES20.glUseProgram
and before drawing anything.
83
is getting pretty high for a location, though not unreasonable. I don't know much about android specs but it's certainly possible to run out of uniform locations and memory. Maybe try simplifying the shader (test with less lights) or placing the uniform higher up.
[EDIT] To continue this idea, GLSL compilers can be really unintuitive when loops and functions are concerned. Also when branching starts to get quite large. You may just be hitting some weird GLSL compiler bug. You could try unrolling the loop manually, throwing indexComputeLights < MAX_LIGHTS && indexComputeLights < numLights
inside the for-loop condition should cause the generated conditionals to nest (at least on my nvidia card with current drivers). Maybe replace the expensive computeLight
call with a constant just for testing. I've hit cases where generated code seems to be too large and I get an invalid result rather than a compile error.
Does lowp
affect the ability to set the uniform?
Another way to get the desired outcome, which may also be faster, is to compile a shader for each number of lights. This can be done easily by injecting a #define NUM_LIGHTS x
at the top of your source.
Upvotes: 1