PDA

View Full Version : An issue about uniform block access in GLSL



jianliang79
09-08-2011, 12:29 AM
Our Application use uniform block to pass light source parameters to fragment shader when rendering a scene. Our code run perfectly in nVidia video card but can't render the correct result with an ATI 5770 card (catalyst 11.8, windows 7 x64) when light count is greater than 1. The vertex shader and fragment shader is pasted below:

vertex shader:

#version 150

uniform mat4 modelViewMatrix;
uniform mat3 normalMatrix;
uniform mat4 projectionMatrix;

uniform float normalMultiplier; // may be 1 or -1

in vec3 vertex;
in vec3 normal;
in vec2 texCoord;

out vec3 fragPosition; // in eye coordinates
out vec3 fragNormalVector; // in eye coordinates
out vec3 fragEyeVector; // in eye coordinates
out vec2 fragTexCoord;

void main()
{
// transform vertex position into eye coordinates
vec3 posEye = (modelViewMatrix * vec4(vertex, 1)).xyz;

fragPosition = posEye;
fragNormalVector = normalize(normalMatrix * (normal * normalMultiplier));
fragEyeVector = -normalize(posEye);
fragTexCoord = texCoord;

gl_Position = projectionMatrix * vec4(posEye, 1);
}


fragment shader:

#version 150

struct SLightSource {
vec4 color;
vec3 position; // light source position in eye coordinates
vec3 lightDir; // unit vector for light direction(toward light), in eye coordinates
float lightDirMixFactor; // 0 for directional light; 1 for point or spot light
float cosTheta; // cosTheta must be greater than cosPhi
float cosPhi;
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
};

layout(std140) uniform LightSourcesBlock {
SLightSource lightSources[32];
};

//
// material light reflection property
//
uniform vec4 diffuseColor;
uniform vec4 specularColor;
uniform vec4 ambientColor;
uniform float glossiness; // must be greater than 0

vec4 CalcLightColor(const in int lightIdx,
const in vec3 pos,
const in vec3 normalVector,
const in vec3 eyeVector)
{
vec4 lightSourceColor = lightSources[lightIdx].color;

// calculate vector from this fragment point to light source
vec3 lightVector = lightSources[lightIdx].position - pos;
float lightDistance = length(lightVector);
lightVector = normalize(lightVector);
lightVector = mix(lightSources[lightIdx].lightDir, lightVector, lightSources[lightIdx].lightDirMixFactor);

// calculate diffuse light color
float diffuseIntensity = max(dot(normalVector, lightVector), 0);
vec4 lightColor = diffuseColor * lightSourceColor * diffuseIntensity;

// calculate specular light color
vec3 reflection = normalize(reflect(-lightVector, normalVector));
float specularItensity = pow(max(dot(reflection, eyeVector), 0), glossiness);
lightColor += specularColor * lightSourceColor * specularItensity;

// calculate radial intensity attenuation
float attenuation = 1 / (lightSources[lightIdx].constantAttenuation
+ lightSources[lightIdx].linearAttenuation * lightDistance
+ lightSources[lightIdx].quadraticAttenuation * lightDistance * lightDistance);

// calculate angular intensity attenuation
float cosAlpha = dot(lightVector, lightSources[lightIdx].lightDir);
attenuation *= smoothstep(
lightSources[lightIdx].cosPhi,
lightSources[lightIdx].cosTheta,
cosAlpha);

lightColor *= attenuation;

// add ambient color
lightColor += ambientColor * lightSourceColor;

return lightColor;
}

uniform vec4 emittedColor;
uniform float opacity;

uniform sampler2D texSampler;

uniform float texMultiplier;

in vec3 fragPosition; // in eye coordinates
in vec3 fragNormalVector; // in eye coordinates
in vec3 fragEyeVector; // in eye coordinates
in vec2 fragTexCoord;

out vec4 fragColor;

void main()
{
vec4 texColor = texture(texSampler, fragTexCoord);
texColor.rgb = texColor.rgb * texMultiplier;
texColor.a *= opacity;

if (texColor.a == 0)
discard;

vec3 normalVector = normalize(fragNormalVector);
vec3 eyeVector = normalize(fragEyeVector);

// calculate light color for all light sources
vec4 lightColor = emittedColor;
lightColor += CalcLightColor(0, fragPosition, normalVector, eyeVector);
lightColor += CalcLightColor(1, fragPosition, normalVector, eyeVector);

lightColor.a = 1;

vec4 color = lightColor * texColor;
fragColor = vec4(color.rgb * color.a, color.a);
}


As you can see from the code, in our fragment shader, I use a uniform block to pass light source information, the block contain a structure array. I use std140 layout for this uniform block, I will fill the uniform block with the light sources' information before issuing GL drawing call. The code above show the case where we have two light sources, it run perfectly in all the nVidia cards we have used but will render incorrect result in my ATI radeon HD 5770 card (with catalyst 11.8 driver, windows 7 x64), the final result will only show the color lit by the first light source, the second light sources seems to be ignored by GLSL compiler. After some hack, I found that the fragment shader read wrong value for the second light sources' parameter (but it can always read correct value for the first light), I suspect this is due to the incorrect std140 layout. The layout of my structure we used to fill the uniform block is listed below:

[name] [offset]
color 0
position 16
lightDir 32
lightDirMixFactor 44
cosTheta 48
cosPhi 52
constantAttenuation 56
linearAttenuation 60
quadraticAttenuation 64

and the stride of my structure array is 80

I calculate these offset and stride according to openGL 3.2 specification, Is my calculation correct? And how can I do to resovle my problem with ATI card? thanks.

Alfonse Reinheart
09-08-2011, 12:54 AM
Props for the use of color for your code, but [ code ] tags would have been preferred.


color 0
position 16
lightDir 32

Your position was defined as a `vec3` in your shader. So the offset of the light direction should be 28, not 32. And the overall stride of your array should be 64, not 80.

jianliang79
09-08-2011, 07:44 AM
Thank you for the suggestion, but according to openGL specification:

A structure and each structure member have a base
offset and a base alignment, from which an aligned offset is computed by rounding
the base offset up to a multiple of the base alignment. The base offset of the first
member of a structure is taken from the aligned offset of the structure itself. The
base offset of all other structure members is derived by taking the offset of the
last basic machine unit consumed by the previous member and adding one. Each
structure member is stored in memory at its aligned offset.


If the member is a three-component vector with components consuming N
basic machine units, the base alignment is 4N .


In my structure, the "position" member's aligned offset is 16, and according to specification the base alignment of vec3 should be 16, thus the aligned offset of "lightDir" should be ((16 + 12) up_align 16) = 32.

And also according to openGL specification:

If the member is a structure, the base alignment of the structure is N , where
N is the largest base alignment value of any of its members, and rounded
up to the base alignment of a vec4.


the base offset of the member following the sub-structure is rounded up to
the next multiple of the base alignment of the structure.


My structure's base alignment should be 16, and its last member's aligned offset is 64 and the last member consume 4 bytes, so the next multiple of the base alignment of the structure will be 80, it is the stride of the array.

cameni
09-09-2011, 07:18 AM
There is an alignment bug in ATI drivers regarding std140 layout and nested structures inside a uniform block. It's been reported and the fix will appear in a new release of the drivers.

Meanwhile you can work around it by avoiding the nested structure and listing the members directly in the uniform block, though in your situation (with arrays) it's not usable ..