GLSL mean normal estimation per vertex

Hello,

I’m trying to implement The “Mean Weighted Equally” (MWE) vertex normal algorithm (summation of normals of all faces incident to the vertex) with GLSL and vertex buffer objects.

My problem is how to use custom, user-defined attribute variables per vertex for the vertex normals since the maximum number of attribute locations is limited by the graphics hardware (16) and i’m already using 5 attributes.

Is there another way to communicate from “outside” to the vertex shade per vertex ? (I want to use 30 vec3 per vertex for example)

Using the built-in input variable ‘gl_VertexId’ (through EXT_gpu_shader4 or GL3.0+, or your own vertex attribute carrying the vertex ID) you can address:

a) a Texture Buffer Object (TBO), doing Vertex-Texture-Fetches
b) a Uniform Array, backed by a Uniform Buffer Object (UBO)
c) generic GPU memory through a pointer and pointer arithmetic (NV_shader_buffer_load)

to store an almost arbitrary amount of per-vertex data.
TBOs seem to be the most widespread solution at the moment, but it requires you to store the data in Textures (backed by a TBO).

Thank you for your quick reply…
Texture Buffer Object is great for storing large amount of data.

I used one to store all normal vertices of faces of the model.
and one to store the face indices incident to each vertex.
The number of faces that contain each vertex is arbitrary.

My problem is how to lookup into the second TBO to obtain the
list of the the face indices per vertex in order to lookup into the first TBO? (GLSL)

Using ‘texelFetchBuffer’ with integer texture coordinate ‘gl_VertexId’ i can fetch only a vec4.

My problem will be the same if i use only one TBO which consists of the normal vetices incident to each vertex… :frowning:

Using ‘texelFetchBuffer’ with integer texture coordinate ‘gl_VertexId’ i can fetch only a vec4.

You can manage to perform multiple texture fetch using a 2D texture. For example, the x coordinate would be the vertex ID and along the y coordinate, the faces normals list.

nice…!!!

thanx dletozeun

Another option would be to use a geometry shader with triangle adjacency.

Potential downside here is having to compute adjacency, and the geoshader is NV only at the moment (though I hear AMD has added the GS to its 9.6b drivers!).

Hello again and thanks for your responces

You can manage to perform multiple texture fetch using a 2D texture. For example, the x coordinate would be the vertex ID and along the y coordinate, the faces normals list.

As i need absolute integer values returned from the sampler for your GLSL code, i used Texture Integer (GL_EXT_texture_integer) to store the faces indices lists.
(This extension provides a set of new “unnormalized” integer texture formats. Formats with both signed and unsigned integers are provided.
In these formats, the components are treated as true integers. When such textures are accessed by a shader, actual integer values are returned.)

However, the returned normal vector values are not the correct ones…

I have implemented this project using :

  1. one VBO for vertex-normal vectors ( but i do not use normals - gl_Normal ) and some attributes.
  2. one TBO for normal vectors of model faces
  3. one Textrure Integer for face indices

Here is the vertex shader code:



#version 120
#extension GL_EXT_gpu_shader4 : require

varying vec4  Idiffuse,IambientGlobal,Iambient;
varying vec3  Inormal ,IlightDir     ,IhalfVector;
varying float Idist;

attribute ivec4 attr;

uniform isampler2D     TEX_I_N;
uniform  samplerBuffer TBO_P_N;
	
// attr[2] = number of faces that contain each vertex

vec3 computeNormal()
{
  int texel;
  vec3 normal = vec3(0.0,0.0,0.0);
		
  for(int i=0; i < attr[2]; i++){
      texel   = texture2D(TEX_I_N, ivec2(i,gl_VertexID)).x;
      normal += texelFetchBuffer(TBO_P_N, texel).xyz;
  }
  normal /= attr[2];
  return normalize(normal);
}
	
void main()
{	
 vec3 n,aux; vec4 ecPos;
 Inormal = normalize(gl_NormalMatrix * computeNormal());
		
 ecPos  = gl_ModelViewMatrix * gl_Vertex;
 aux    = vec3(gl_LightSource[0].position-ecPos);
		
 IlightDir   = normalize(aux); Idist = length(aux);
 IhalfVector = normalize(gl_LightSource[0].halfVector.xyz);
 Idiffuse    = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
 Iambient    = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
 IambientGlobal = gl_LightModel.ambient * gl_FrontMaterial.ambient;
			
 gl_Position   = ftransform();
 gl_FrontColor = gl_Color;
} 


…and the opengl setup code:



	GLint i,j,ii=0,max_k(0);


//////////////////////

	// TBO with faces normal vectors

	GLfloat *normals = new GLfloat[numOfFaces*4]; 
	for(i=0; i<numOfFaces; i++){
		normals[ii]   = (*Faces)[i].normal.x;
		normals[ii+1] = (*Faces)[i].normal.y;
		normals[ii+2] = (*Faces)[i].normal.z;
		normals[ii+3] = 1.f;
		ii+=4;
	}

	glDeleteBuffers	(1, &textureBuffers); glGenBuffers (1, &textureBuffers);
	glDeleteTextures(2, textures);	      glGenTextures(2, textures);


	
	glBindBuffer (GL_TEXTURE_BUFFER_EXT, textureBuffers);
		glBufferData  (GL_TEXTURE_BUFFER_EXT, 4 * numOfFaces * sizeof(GLfloat), normals, GL_STATIC_DRAW);
	glBindTexture(GL_TEXTURE_BUFFER_EXT, textures[0]);
		glTexBufferEXT( GL_TEXTURE_BUFFER_EXT, GL_RGBA32F_ARB, textureBuffers);
	glBindTexture(GL_TEXTURE_BUFFER_EXT, 0 );
	glBindBuffer (GL_TEXTURE_BUFFER_EXT, 0);

//////////////////////

	// Texture Integer with face indices for each vertex

	//  -- Vertex_Faces[i]: faces indices list for vertex 'i'
	for(i=0; i<numOfVerts; i++)
		if(max_k < (int)Vertex_Faces[i].size())
			max_k = Vertex_Faces[i].size();

	GLint *indices = new GLint [numOfVerts*max_k];

	for(i=0; i<numOfVerts; i++)
		for(j=0; j<max_k; j++)
			indices[i*max_k + j] = (j < (int)Vertex_Faces[i].size()) ? *(Vertex_Faces[i].begin() + j) : -1;

	glBindTexture  (GL_TEXTURE_2D, textures[1]);
		glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S	   , GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T	   , GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE16I_EXT, max_k, numOfVerts, 0, GL_LUMINANCE_INTEGER_EXT, GL_UNSIGNED_SHORT, (const GLvoid *)indices);
	glGenerateMipmapEXT(GL_TEXTURE_2D);

	delete [] indices;
	delete [] normals;


…and the opengl render code:




	if(Rmode == GL_RENDER) glUseProgram(pro2);

	glColor3f(1.f,1.f,1.f);

	glActiveTexture(GL_TEXTURE0); glBindTexture( GL_TEXTURE_BUFFER_EXT, textures[0]);
	glActiveTexture(GL_TEXTURE1); glBindTexture( GL_TEXTURE_2D	  , textures[1]);

	glBindBuffer(GL_ARRAY_BUFFER, vboId[0]);
		glEnableClientState(GL_VERTEX_ARRAY);
			glVertexPointer(3, GL_FLOAT, 48, BUFFER_OFFSET(0)  );
		glEnableClientState(GL_NORMAL_ARRAY);
			glNormalPointer(   GL_FLOAT, 48, BUFFER_OFFSET(12) );
////
	if(Rmode == GL_RENDER){
		int loc = glGetAttribLocation (pro2, "attr");
		glEnableVertexAttribArray(loc);
			glVertexAttribIPointerEXT(loc,4,GL_INT,48,BUFFER_OFFSET(24));
		glUniform1i (glGetUniformLocation(pro2, "TBO_P_N"), 0);
		glUniform1i (glGetUniformLocation(pro2, "TEX_I_N"), 1);
	}
///

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId[2]);
		glDrawElements(GL_TRIANGLES, 3*numOfFaces, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glDisableClientState(GL_VERTEX_ARRAY);  
		glDisableClientState(GL_NORMAL_ARRAY);	
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindTexture(GL_TEXTURE_2D , 0);
	glBindTexture(GL_TEXTURE_BUFFER_EXT,0);
	glActiveTexture(0);

	if(Rmode == GL_RENDER) glUseProgram(NULL);


Could anybody help me…???

Yeap that will be a nice solution and i’ll try it at the future but now i want to solve it with the ‘textures’ perspective…

Thanks for your reply…

I may missing something, but your approach seems quite complicated to me. However it should work.


glBindBuffer (GL_TEXTURE_BUFFER_EXT, textureBuffers);
		glBufferData  (GL_TEXTURE_BUFFER_EXT, 4 * numOfFaces * sizeof(GLfloat), normals, GL_STATIC_DRAW);

How the normals array is arranged? You are specifying 4 floats per face normals whereas a normal could represented as a 3d vector. I assume you store a normal in a 4d vector here, something like: (X, Y, Z, 0.0 ).

EDIT : Sorry, I missed the beginning of the code (the normal array setup :slight_smile: ).

However, a normal is a vector, so in homogeneous form, the 4th component must be set to zero.

When I suggested to use VTF in a 2D texture, I was thinking of storing normal data in this texture and not an index to a TBO which actually contains normals data.
Normal data may be stored in a RGBA or RGB texture since a normal is normalized and each of its components is in the [0 1] interval.

Some more anomalies I noticed:


GLint *indices = new GLint [numOfVerts*max_k];
...
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE16I_EXT, max_k, numOfVerts, 0, GL_LUMINANCE_INTEGER_EXT, GL_UNSIGNED_SHORT, (const GLvoid *)indices);

Why GL_UNSIGNED_SHORT as data type whereas indices type is GLint? It should be GL_UNSIGNED_INT, and the indices array type, GLuint.

The result would be this:


GLuint *indices = new GLuint [numOfVerts*max_k];
...
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE16I_EXT, max_k, numOfVerts, 0, GL_LUMINANCE_INTEGER_EXT, GL_UNSIGNED_INT, (const GLvoid *)indices);

In addition, you should not generate mipmaps, it does not make any sense since the texture is not actual mapped on a mesh but fetched as as vertex attribute data. Use GL_NEAREST for min and mag filters and do not generate mipmaps.

YES.I’ve done it…!! I made it work… :slight_smile:

So,

  1. I removed generation of mipmaps (f!@#ing copy-paste…) and used GL_NEAREST for min and mag filters and GL_CLAMP_TO_EDGE for wrap parameters.

  2. I used GL_UNSIGNED_INT instead of GL_UNSIGNED_SHORT as indices data type. (even i think, it’s not a mistake)

However, a normal is a vector, so in homogeneous form, the 4th component must be set to zero.

I took the code from one of the NVIDIA samples which uses TBO for rendering, here is the code:


    //normal texture buffer first
    glBindBuffer( GL_TEXTURE_BUFFER_EXT, textureBuffers[1]);
    // must pad normals to size 4 for texbo efficiency
    if ( model->getNormalSize() == 3) {
        float *tempNorm = new float[model->getNormalCount() * 4];
        const float *src = model->getNormals();
        float *dst = tempNorm;
        for (int ii = 0; ii<model->getNormalCount(); ii++) {
            *dst++ = *src++;
            *dst++ = *src++;
            *dst++ = *src++;
            *dst++ = 1.0f;         /// !!!
        }
        glBufferData( GL_TEXTURE_BUFFER_EXT, 4 * model->getNormalCount() * sizeof(float), tempNorm, GL_STATIC_DRAW);
        delete []tempNorm;
    }
    else {
        glBufferData( GL_TEXTURE_BUFFER_EXT, model->getNormalSize() * model->getNormalCount() * sizeof(float), model->getNormals(), GL_STATIC_DRAW);
    }
    glBindTexture( GL_TEXTURE_BUFFER_EXT, textures[1]);
    glTexBufferEXT( GL_TEXTURE_BUFFER_EXT, GL_RGBA32F_ARB, textureBuffers[1]);


Moreover, in my vertex shader i use only the xyz cordinates of the vec4 fetched texel. So, it does not depend what will the 4th coordinate be.


normal += texelFetchBuffer(TBO_P_N, texel).xyz;

  1. Vertex shader worked when i replaced

texel = texture2D(TEX_I_N, ivec2(i,gl_VertexID)).x;

with


texel	= texelFetch2D(TEX_I_N, ivec2(i,gl_VertexID),0).x;

When I suggested to use VTF in a 2D texture, I was thinking of storing normal data in this texture and not an index to a TBO which actually contains normals data.

Yeap, i know…but i thought that i will send less data with my way…(am i correct !!!)

Thanxxx all for your help !!

I used GL_UNSIGNED_INT instead of GL_UNSIGNED_SHORT as indices data type. (even i think, it’s not a mistake)

I may missing something but i do not see why? You create an array whose elements type is int and actually say to the driver that it is short… Your program works even if you let GL_UNSIGNED_SHORT?

I took the code from one of the NVIDIA samples which uses TBO for rendering, here is the code:

In this case there is an error in their code. :slight_smile:

Yeap, i know…but i thought that i will send less data with my way…(am i correct !!!)

Yes that’s true. I was thinking in terms of cheaper gpus or ATI ones, you know, without TBO support :wink: .
Anyway if you do not have to update the normal texture each frame, it is pretty equivalent.