Samplers inside structs

Hi all,

I’ve declared a uniform struct variable like this:


uniform struct lightSource {
    vec3 position, intensityConstant, attenuation;
    mat3 frame;
    mat4 shadowMatrix;
    sampler2DShadow shadowMap;
    sampler2D intensityMap;
} lightSources[maxLightCount];

Which used to work fine until I upgraded my drivers at some point and now the compiler keeps complaining that:

warning C7554: OpenGL requires samplers to be explicitly declared as uniform

Declaring the samplers as uniform inside the struct won’t help as then the compiler believes that:

warning C7524: OpenGL requires uniform variables to be declared in global scope

Aren’t samplers allowed inside structs? The specification says that you can have “basic types” inside them and samplers are “basic types”. Everything works as expected too, I’m just getting a lot of annoying messages.

Perhaps you did not specify the shader version, i.e:

#version 140 // for glslang 1.40

If you don’t the compiler will implicitly target the version 1.10.

AFAICT it should be allowed in version 1.10 as well. Besides, if I specify any version at all, warnings turn to errors, and the shaders won’t compile.

Two questions:

1: What is your IHV?

2: Are you sure that samplers defined in structs are supposed to work to begin with? I haven’t looked it up myself, but you should check to see if that is legal.

If it is legal, then it’s just a driver bug.

I’m using an NVIDIA board. Regarding whether it’s allowed or not, the specs state that “basic types” are allowed insied a struct declaration and samplers are “basic types”. Other than that I haven’t found anything explicitly allowing or disallowing it.

Well, it’s not explicitly stated, but it is implied by the fact that samplers must be uniforms (or function parameters) and uniforms must be declared at global scope, which then precludes structs and interface blocks with an instance name (1.5.09 spec, sections 4.3.5 and 4.3.7).

In case you are intending to use ubo: a uniform block does not take the struct qualifier, I suggest you to remove it if you are updating this block with a buffer object in the Opengl program. Also as Brolingstanz says, samplers are not allowed in uniform blocks.

EDIT:

If you just want to use a uniform structure, this code looks valid to me (from my understanding of the spec). As Alfonse said, it might be a driver bug in this case.

Well Brolingstanz seems to have a point that the spec may be implying that samplers inside srtucts are forbidden but then again it’s not clear. Could one of you guys try to include such a declaration in a random shader and see if the compiler complains on your implementation?

“Well Brolingstanz seems to have a point that the spec may be implying that samplers inside srtucts are forbidden but then again it’s not clear.”

Mind this is from a guy who plucks his nose hair with a hay-bond wimble.

I have fallen into the same problem…


#version 130
#extension GL_EXT_gpu_shader4 : enable
#extension GL_EXT_texture_array : enable

#define OMNI_LIGHT_CLASS_ID	0x1011

uniform struct lData 
{ 	
	int    iShadowCasting;
	int    iType;
	float  fAttenuation;
	vec3   vPosition;
	vec4   vColor;
	mat4x4 mat4_ViewMatrix;
	mat4x4 mat4_ProjectionMatrix;
	samplerCubeShadow cubeShadowMap;
	
}  Light[MAX_LIGHTS];

And i cannot sample by using:


vec4 sampleTest(int iLight)
{
vec4 vShadow = shadowCube(Light[iLight].cubeShadowMap, vec4(position_ls.xyz, depth));
}

but it works if i do it by:


int LightID = 0;
vec4 vShadow = shadowCube(Light[LightID].cubeShadowMap, vec4(position_ls.xyz, depth));

Anyone can clear this out please?

I’m targeting GL Context 3.0.

I have fallen into the same problem…

Then stop putting samplers in structs. Problem solved.

The spec is rather dubious about whether it should work, so don’t test it.

Do not treat samplers like regular uniforms. They’re special.

#version 130
#extension GL_EXT_gpu_shader4 : enable
#extension GL_EXT_texture_array : enable

This is confused and confusing. Array textures are part of 1.30; you shouldn’t use the extension version if your shader is targeting 1.30. Same goes for all of GPU_shader4.

but it works if i do it by:

Of course it does. Besides the fact that samplers are likely not allowed in structs, the spec is very clear about how arrays of samplers can be accessed. Namely, by integral constant expressions only. Even if samplers were allowed in structs, you wouldn’t be able to circumvent the integral constant expression rule just by making an array of structs that contain samplers.

In short: don’t get cute with GLSL. Stay on the beaten path, and everything will be fine.

Sorry, i should ‘require’ them in shader instead of enabling.
Of course getting rid of this pops an error in compiling.

0(72) : error C7531: global function shadowCube requires "#extension GL_EXT_gpu_shader4 : enable" before use
0(72) : error C0000: ... or #extension GL_EXT_texture_array : enable
0(109) : error C7531: global function texture2DArray requires "#extension GL_EXT_gpu_shader4 : enable" before use
0(109) : error C0000: ... or #extension GL_EXT_texture_array : enable

So it looks like

#extension GL_EXT_gpu_shader4 : require

should be there to allow me to do texture lookups in array/cube depth textures for omni shadow mapping.

PS. sorry, i meant we’re targetting 150 here ( 1.50.11 Opengl 3.2 )

Alfonse, here is what i am trying to achieve - i do forward rendering, so i need to pass all lights into the shader for processing.

Right now i have Omni-shadows done on Depth Cube textures ( thus that extension required ). It might happen that there are more than 1 lights on the scene that need shadow map calculation, so i somehow need to pass those lights and their samplers into the shader.

It worked ok some time ago - but after drivers were updated, things changed. This is how i used to do it:

[FRAG]

#version 150
#extension GL_EXT_gpu_shader4 : require

#define OMNI_LIGHT_CLASS_ID	0x1011
#define MAX_LIGHTS 6


uniform struct lData 
{ 	
	int    iShadowCasting;
	int    iType;
	float  fAttenuation;
	vec3   vPosition;
	vec4   vColor;
	mat4x4 mat4_ViewMatrix;
	mat4x4 mat4_ProjectionMatrix;
	samplerCubeShadow cubeShadowMap;
	
}  Light[MAX_LIGHTS];

uniform mat4x4 mat4_CameraInverse;
uniform sampler2DArray textureName;
uniform int iLightsVisible;

in vec3 vTexCoord;
in vec3 LightDir[MAX_LIGHTS];
in vec3 vNormal;
in vec4 vVertexPositionCameraSpace;

out vec4 FragColor;

vec4 CalculateOmniLight(int iLight, vec3 N, vec3 V)
{

	// Color calculation
	float atten = max(0.0, 1.0 - dot(LightDir[iLight], LightDir[iLight]));
	vec3 L = normalize(LightDir[iLight]);
	vec3 R = normalize(-reflect(L, N));
	float nDotL = max(0.0, dot(N, L));
	float rDotV = max(0.0, dot(R, V));
	vec4 diffuse = Light[iLight].vColor *  nDotL * atten;
	vec4 specular = Light[iLight].vColor * pow(rDotV, 4) * atten;
 

	vec4 vColor = (diffuse+specular);
	return vColor ;

	

}
vec4 CalculateOmniLightWithShadow(int iLight, vec3 N, vec3 V)
{
  

	// Color calculation
	float atten = max(0.0, 1.0 - dot(LightDir[iLight], LightDir[iLight]));
	vec3 L = normalize(LightDir[iLight]);
	vec3 R = normalize(-reflect(L, N));
	float nDotL = max(0.0, dot(N, L));
	float rDotV = max(0.0, dot(R, V));
	vec4 diffuse = Light[iLight].vColor *  nDotL * atten;
	vec4 specular = Light[iLight].vColor * pow(rDotV, 4) * atten;
 

	// Shadowmap sampling	 
	vec4 position_ls = Light[iLight].mat4_ViewMatrix * mat4_CameraInverse * vVertexPositionCameraSpace;
	vec4 abs_position = abs(position_ls);
	float fs_z = -max(abs_position.x, max(abs_position.y, abs_position.z));
	vec4 clip = Light[iLight].mat4_ProjectionMatrix * vec4(0.0, 0.0, fs_z, 1.0);
	float depth = (clip.z / clip.w) * 0.5 + 0.5;

  // HERE IS THE DEAL //
	vec4 vShadow = shadowCube(Light[iLight].cubeShadowMap, vec4(position_ls.xyz, depth));

	diffuse*=vShadow;	 


	vec4 vColor = (diffuse+specular);
	return vColor ;


}
void main()
{  
 
vec3 N = normalize(vNormal);
vec3 V = normalize(-vVertexPositionCameraSpace.xyz);
vec4 vColor=vec4(0,0,0,1);

int lCT=0;
for(lCT=0;lCT<iLightsVisible;lCT++)
{
	if(lCT<MAX_LIGHTS)
	{

	  if(Light[lCT].iType==OMNI_LIGHT_CLASS_ID)
	  {

	    if(Light[lCT].iShadowCasting==1)  	
		vColor+=CalculateOmniLightWithShadow(lCT,N,V);
	    else
		vColor+=CalculateOmniLight(lCT,N,V);
	  }


	}

}

FragColor = vColor  * texture2DArray(textureName,  vTexCoord.xyz);


}

VERT



#version 150

#define OMNI_LIGHT_CLASS_ID	0x1011
#define MAX_LIGHTS 6

uniform mat4x4 mat4_Model;  
uniform mat4x4 mat4_View; 
uniform mat4x4 mat4_Projection;
uniform mat4x4 mat4_CameraInverse;
uniform int iLightsVisible;

in vec3 in_VertexPosition;
in vec3 in_VertexNormal;
in vec3 in_VertexTexCoord;
in vec4 in_VertexTangent;
 
out vec3 vNormal;
out vec3 vTexCoord;
out vec3 LightDir[MAX_LIGHTS]; 
out vec4 vVertexPositionCameraSpace;

uniform struct lData 
{ 	
	int    iShadowCasting;
	int    iType;
	float  fAttenuation;
	vec3   vPosition;
	vec4   vColor;
	mat4x4 mat4_ViewMatrix;
	mat4x4 mat4_ProjectionMatrix;
	samplerCubeShadow cubeShadowMap;	
}  Light[MAX_LIGHTS];


void main()
{
 
vTexCoord = in_VertexTexCoord;

// Proper index to ColorMap
vTexCoord.z*=3;


mat4 modelViewMatrix = mat4_View * mat4_Model;
mat4 mvpMatrix = mat4_Projection * modelViewMatrix;

vVertexPositionCameraSpace= modelViewMatrix *  vec4(in_VertexPosition, 1.0); 

gl_Position = mvpMatrix * vec4(in_VertexPosition, 1.0);

 
// http://www.gamedev.net/topic/569060-normal-matrices-in-current-gl-versions/
// Instead of passing calculated model normals matrix - we go with this which is OK!
vNormal= vec3( modelViewMatrix * vec4(in_VertexNormal, 0.0));



int lCT=0;
for(lCT=0;lCT<iLightsVisible;lCT++)
{
	if(lCT > MAX_LIGHTS ) break;


	// Omni light calculation
	if(Light[lCT].type == OMNI_LIGHT_CLASS_ID)
	{	
		vec4 pos = mat4_View * vec4(Light[lCT].vPosition, 1.0);	
		vec3 lightPosEye = pos.xyz / pos.w;	
	        LightDir[lCT] = (lightPosEye - vVertexPositionCameraSpace.xyz)/ Light[lCT].fAttenuation;
	}


}
 

}


As it might happen that there are two lights in one place that are generating cube shadow maps ( omni lights ) - i need to pass those maps into the shader for sampling somehow. Because it can’t go with uniform structure - how do i pass the light data then ?

Thanks for any info…

-There are a couple of issues here. First, the UBO spec states that you cannot have samplers within a UBO. You’ll have to pull the cube shadow uniform out and into a set of uniforms or uniform array. Second, a shader cannot access a UBO array or a sampler array with a variable unless you’re using GLSL 4.00 or ARB_gpu_shader5. Since you’re targeting 1.50, presumably to allow GL3 hardware to run your program, you’ll want to avoid that.

What you can do is unroll the loops and use constant indexing for both the UBO and the sampler array. The most painless way to do this is to #define a macro with the loop body in it (#define PROCESS_LIGHT(idx) …) and just ‘call’ the macro multiple times with idx = 0…NUMLIGHTS-1.

Also, computing the light direction in the vertex shader is only an optimization if the number of vertices is smaller than the number of fragments generated (fragments can get culled, vertices do not). Something to keep in mind if the models in your project scale up.

The shadow should also be applied to vColor and not just diffuse – specular highlights in your shadowed areas would look odd.

Sorry, i should ‘require’ them in shader instead of enabling.
Of course getting rid of this pops an error in compiling.

That’s because you should be using the 1.30-style texture functions in 1.30 shaders. There is no “shadowCube” in 1.30; there’s just texture, which can take a samplerCubeShadow sampler and the associated parameters.

Right now i have Omni-shadows done on Depth Cube textures ( thus that extension required ). It might happen that there are more than 1 lights on the scene that need shadow map calculation, so i somehow need to pass those lights and their samplers into the shader.

Then do what everyone else does: render the object once for each light source, with different sets of uniforms and textures. Accumulate the results in the framebuffer (additive blending).

Alfonse, thanks for sharing the info, i was just thinking how to overcome this and it seems like “do what everyone else does” is the best approach from all that i have seen.

Thank you very much, i will come back with the results.

Just as promised, back with results and thanks for sharing the right way to go with multi pass lighting.

Works like a charm.


<object width=“425” height=“350”> <param name=“movie” value=“- YouTube”></param> <param name=“wmode” value=“transparent”></param> <embed src=“- YouTube” type=“application/x-shockwave-flash” wmode=“transparent” width=“425” height=“350”> </embed></object>

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.