I´m doing my own game engine. Now, the next step is to build my fragment shader for multiple lighting sources. I found a very strange behaviour I can't understand. In my Moto G 2014 with an 305 Adreno video chip a glsl length function call gives me an incorrect value over a ambient lighting uniform, resulting in wrong scene lighting. Let's see for fist the fragment code:
#define numLights x
#pragma glsl
precision lowp float;
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[numLights];
struct MaterialParameters {
vec4 emission;
vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
bool hasDiffuseTexture;
bool hasSpecularTexture;
bool hasEmissionTexture;
bool hasAmbientTexture;
bool hasNormalTexture;
sampler2D emissionTexture;
sampler2D diffuseTexture;
sampler2D specularTexture;
sampler2D ambientTexture;
sampler2D normalTexture;
};
uniform MaterialParameters Material;
precision lowp float;
varying vec2 varyingTextcood;
varying vec3 varyingNormalDirection;
varying vec3 varyingViewDirection;
varying vec3 outLightVector[numLights];
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection = normalize(varyingViewDirection);
vec3 lightDirection;
float attenuation;
// initialize total lighting with ambient lighting
vec4 totalLighting;
vec4 emissionTerm;
if ((length(Material.emission) != 0.0) || (Material.hasEmissionTexture)) {
/* El material tiene un termino emisivo, es decir, emite luz. Lo andimos al total de color calculado */
if (!Material.hasEmissionTexture) {
emissionTerm = Material.emission.rgba;
}
else {
emissionTerm = texture2D(Material.emissionTexture, varyingTextcood).rgba;
}
if (emissionTerm.a > 0.0){
totalLighting = emissionTerm;
}
}
for (int index = 0; index < numLights; index++) // for all light sources
{
vec4 ambientalTerm;
vec4 specularReflection;
vec4 diffuseReflection;
if (length(LightSource[index].ambient.rgb) > 0.0){
// es luz ambiental
if (Material.hasAmbientTexture){
ambientalTerm = vec4(LightSource[index].ambient, 1.0) * texture2D(Material.ambientTexture, varyingTextcood);
}
else {
ambientalTerm = vec4(LightSource[index].ambient, 1.0) * vec4(Material.ambient);
}
//totalLighting = vec4(0.0,1.0,0.0,1.0);
}
else {
if (0.0 == LightSource[index].position.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(outLightVector[index]);
}
else // point light or spotlight (or other kind of light)
{
vec3 positionToLightSource = outLightVector[index];
float distance = length(positionToLightSource);
lightDirection = normalize(positionToLightSource);
attenuation = 1.0 / (LightSource[index].constantAttenuation
+ LightSource[index].linearAttenuation * distance
+ LightSource[index].quadraticAttenuation * distance * distance);
if (LightSource[index].spotCutoff <= 90.0) // spotlight?
{
float clampedCosine = max(0.0, dot(-lightDirection, normalize(LightSource[index].spotDirection)));
if (clampedCosine < cos(radians(LightSource[index].spotCutoff))) // outside of spotlight cone?
{
attenuation = 0.0;
}
else
{
attenuation = attenuation * pow(clampedCosine, LightSource[index].spotExponent);
}
}
}
vec4 diffuseTerm;
if (Material.hasDiffuseTexture){
diffuseTerm = texture2D(Material.diffuseTexture,varyingTextcood);
}
else {
diffuseTerm = Material.diffuse;
}
if (diffuseTerm.a > 0.0){
diffuseReflection = attenuation
* vec4(LightSource[index].lightColor, 1.0) * diffuseTerm
* max(0.0, dot(normalDirection, lightDirection));
}
if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?
{
specularReflection = vec4(0.0, 0.0, 0.0, 0.0); // no specular reflection
}
else // light source on the right side
{
vec4 specularTerm;
if (Material.hasSpecularTexture){
specularTerm = texture2D(Material.specularTexture,varyingTextcood);
}
else {
specularTerm = Material.specular;
}
if (specularTerm.a > 0.0){
/* OPCION SIN HALFVECTOR
specularReflection = attenuation * vec4(LightSource[index].lightColor,1.0) * specularTerm
* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), Material.shininess);
*/
// OPCION CON HALFVECTOR
vec3 light_half_vector = normalize(outLightVector[index] + viewDirection);
specularReflection = attenuation * vec4(LightSource[index].lightColor,1.0) * specularTerm
* pow(max(dot(light_half_vector, normalDirection), 0.0), Material.shininess);
}
}
}
totalLighting = totalLighting + ambientalTerm + diffuseReflection + specularReflection;
}
gl_FragColor = clamp(totalLighting, 0.0, 1.0);
}
Well, inside the main function, if we look at the foor loop, we have this line:
if (length(LightSource[index].ambient.rgb) > 0.0){
I found that debbuging with my Moto G, the shader always enter inside this statement, no matter that the scene hasn't an ambient light source. I can test this easyly writing totalLighting = vec4(1.0) inside the if branch.
This is not happening in the Adreno Profiler. I can't understand what's wrong because the profiler connects to the Moto G GPU and retrieves all the uniforms value, and ambient is a vec3 of 0.0 values. Even if I take a screen capture of the same device in the profiler I get the expected lighting behaviour in the profiler and a wrong behaviour in the phone. Shouldn't it be the same in both sites if they are connected?
As a curiosity, if I change the order of the declarations inside the LightSourceParameters I get very diferent results, and I can't understand why. For example, take a look at he screen capture I upload, all the scene gets red that is the color I'm using to clear the screen when the scene has been rendered with
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
It's red for debbuging prupourses.
This is the original image in the moto g phone, in the usual declaration of the uniforms I have:
Rendering in the moto g phone
The next one is the capture I get if I move the vec3 ambient declaration to the end of the LightSourceParameter struct:
Rendering in the phone moving the ambient light declaration to the end of the LightSourceParameters struct
And this is the profiler capture where you can see the value of the uniform. Doesn't matter if the ambient declaration is at the begining of the struct or the end, the result is the same, as I would expect in the phone:
Scene rendered through the Adreno profiler
Does somebody knows what is wrong here or what I'm missunderstanding ?
Edit 1:
Commenting out the if statement of the ambient light:
//if (0.0 < length(LightSource[index].ambient)){
I allow the flow to go ahead and calculate the diffuse and specular light/material data. This is not optimal, but it´s a way to debug so I´m going to use it for now. The scene keeps black except the yellow sun rays (like the first image) until I substitute the light color in the diffuse calculation with a vec3(1.0) in this way:
/*
diffuseReflection = attenuation
* vec4(LightSource[index].lightColor, 1.0) * diffuseTerm
* max(0.0, dot(normalDirection, lightDirection));
*/
diffuseReflection = attenuation
* vec4(1.0,1.0,1.0, 1.0) * diffuseTerm
* max(0.0, dot(normalDirection, lightDirection));
This way the diffuse term is calculated as if It was being done in the Adreno profiler and the image is rendered well. So my supossition is that the full array of light struct has garbage in it or wrong data, but I cant understand why this is only happening in the moto g.
You can see all the screen captures in the thread in stack overflow I have opened for this: http://stackoverflow.com/questions/29731812/uniform-value-is-wrong-in-ad...
Unfortunally they there have not been able to help me
Uniform value is wrong in adreno 305 qualcomm device but right in Adreno Profiler
Posted: Tue, 2015-04-21 10:31
Thanks for the report, and apologies for the late response.
I created a small sample program using your supplied shader and was able to reproduce the problem on a Moto X and Snapdragon 801 device.
As a workaround, I moved the ambient out of the LightSourceParameters struct.
uniform LightSourceParameters LightSource[numLights];
uniform vec3 TestAmbient; // <- move 'ambient' out of above
Then I changed the "if" condition from "if (length(LightSource[index].ambient.rgb) > 0.0)" to "if (length(TestAmbient) > 0.0)"
I added "fragcolor = vec4( 1.0, 0.5, 0.5, 1.0);" after the "if", and after the "else" for that "if" above I added "fragcolor = vec4( 0.5, 0.5, 1.0, 1.0);". Then I multiplied the gl_FragColor by "fragcolor".
Here is the output when the program passes a "TestAmbient" uniform of length > 0 :
http://i.imgur.com/knMCsyo.jpg
And here is the output when the program passes a "TestAmbient" uniform of length <= 0:
http://i.imgur.com/lbWRjmF.jpg
I have submitted my sample with your struct problem to the graphics compiler team, and will let you know if/when there's any progress on that issue.
Sorry if my reply was confusing: the compiler problem is caused by the struct.
So everything you need to use from within the struct has to be taken out of the struct, just like you did with 'ambient'. That is, you need to create arrays of everything inside the struct that is used in the code, so for example:
uniform vec3 lightColor[numLights];
uniform vec4 position[numLights];
...
uniform float spotCutoff[numLights];
An easier alternative to making all these changes to your shader is for you to use a device containing a Snapdragon 805. The problem only exists on Snapdragon 801 devices like your Moto G 2014, and your original sample works fine on my Snapdragon 805 device.
The issue is not whose at fault. Without going into detail this IS a driver bug and mod is suggesting a potential workaround for the issue. No mention was made that this was your fault as that how I think you are interpreting the feedback. The emulator will most likey give different result ( I have seen this from personal experience ) as that just a translator that hands off the OpenGL/GLSL calls to the host device driver, in other words its using OpenGL and NOT OpenGLES natively.
Sorry to hear that the experiment with the struct didn't work. However, note that "MaterialParameters" is still using a struct.
Just to be sure we have checked everything, what is 'x' in this #define?
#define numLights x
Note that when I originally tried out my sample test, I had to give a specific number ('1') instead of 'x' because the shader does not compile with 'x'. I saw these errors:
05-06 22:13:35.772: E/lengthTest(12817): Failed to compile the fragment shader with ERROR: Fragment shader compilation failed.
05-06 22:13:35.772: E/lengthTest(12817): ERROR: 0:33: 'x' : undeclared identifier
05-06 22:13:35.772: E/lengthTest(12817): ERROR: 0:33: '' : constant expression required
05-06 22:13:35.772: E/lengthTest(12817): ERROR: 0:33: '' : array size must be a constant expression
05-06 22:13:35.772: E/lengthTest(12817): ERROR: 0:68: '' : constant expression required
05-06 22:13:35.772: E/lengthTest(12817): ERROR: 0:68: '' : array size must be a constant expression
05-06 22:13:35.772: E/lengthTest(12817): ERROR: 0:91: '<' : wrong operand types no operation '<' exists that takes a left-hand operand of type 'int' and a right operand of type 'float' (or there is no acceptable conversion)
05-06 22:13:35.772: E/lengthTest(12817): ERROR: 6 compilation errors. No code generated.
05-06 22:13:35.772: E/lengthTest(12817): Failed to initialize the shaders!
So I have to ask you if you could try changing 'x' to a specific number and let us know the results.
Sorry for the late answer.
Yes, you change x in define for any number you want. Usually 1 or 2.
Thanks