Custom Vertex Attributes creation and usage

Hi!

I'm trying to use custom shaders with OCCT to learn how to use this kind of libraries.
At this moment, I know how to create a vertexShader and fragmentShader from a file and attach them to a new shaderProgram.
Also, I am able to push different variables, and those are received correctly in the shader.

My doubt is how can I create a custom vertex attribute buffer linked to a location.
I have found the object "Graphic3d_ShaderAttributeList" and I have this code:

{
int customAttrLocation = 5;
Handle(Graphic3d_ShaderAttribute) attr = new Graphic3d_ShaderAttribute("customAttrVal", customAttrLocation);
Graphic3d_ShaderAttributeList theAttributes(NCollection_BaseAllocator::CommonBaseAllocator());
theAttributes.Append(attr);
ShaderProg->SetVertexAttributes(theAttributes);
}

Even if this code is correct (which I don't really know) I cannot find a way to fill this attribute buffer with actual data. I would like to fill it with Float values for different purposes.

I have enabled attribute location in my GLSL files using --> #extension GL_ARB_explicit_attrib_location : enable

Any help on how to fill my custom attribute buffer with values?

Thanks!!

gkv311 n's picture

You need to pass vertex attributes within Graphic3d_Buffer passed to Graphic3d_Group::AddPrimitiveArray():

void Graphic3d_Group::AddPrimitiveArray (const Graphic3d_TypeOfPrimitiveArray theType,
                                                  const Handle(Graphic3d_IndexBuffer)& theIndices,
                                                  const Handle(Graphic3d_Buffer)& theAttribs,
                                                  const Handle(Graphic3d_BoundBuffer)& theBounds,
                                                  const Standard_Boolean theToEvalMinMax = Standard_True)

The simplest way to realize how this buffer can be initialized is to look into src/Graphic3d/Graphic3d_ArrayOfPrimitives.cxx. You will need to add Graphic3d_TOA_CUSTOM attribute (referring to location index within the shader program) within array of Graphic3d_Attribute passed to Graphic3d_Buffer::Init() and initialize data for it within the buffer using one of available interfaces, which you'll find more convenient to you (basically speaking - through memory aliasing and offsets, the way how VBO are defined in OpenGL).

Pedrar NoName's picture

EDIT: Don't bother reading the post, already found why my attribute buffer was not working, i used the incorrect size to fill it... Thanks for the hints btw!!

Hi!

Thanks for the response, i think i'm starting to understand how OCCT is working here. Although i have a problem in the execution of what you said.

Until now, my Compute() routine was working correctly with this code:

###############

Handle(Graphic3d_Group) grupo = mpPrs->CurrentGroup();
Handle(Graphic3d_ArrayOfTriangles) aPArray = StdPrs_ShadedShape::FillTriangles(mShape, Standard_False, aDummy, aDummy, aDummy);
if (!aPArray.IsNull())
{
grupo->SetClosed(true);
grupo->SetPrimitivesAspect(CADdrawer->ShadingAspect()->Aspect());
grupo->AddPrimitiveArray(Graphic3d_TOPA_TRIANGLES, aPArray->Indices(), aPArray->Attributes(), aPArray->Bounds(), true);
}
###############

After your reply I thought about creating my own attribute buffer, filling it mannually with the same values found in "aPArray->Attributes()".
I verified that the Attributes buffer only contains 2 attributes, Position and Normals, so I created a buffer with those and then copied the data byte-to-byte ( I know this is not optimal, it's just to understand the flow of the buffer creation)

This is the resulting code I wrote:

###############
Handle(Graphic3d_Group) grupo = mpPrs->CurrentGroup();
Handle(Graphic3d_ArrayOfTriangles) aPArray = StdPrs_ShadedShape::FillTriangles(mShape, Standard_False, aDummy, aDummy, aDummy);
if (!aPArray.IsNull())
{
// Create my own buffer with same attributes
const Handle(NCollection_BaseAllocator)& allocator = Graphic3d_Buffer::DefaultAllocator();
const Handle(Graphic3d_Buffer) newAttrBuffer = new Graphic3d_Buffer(allocator);
Graphic3d_Attribute atri[2];
atri[0].Id = Graphic3d_TOA_POS;
atri[0].DataType = Graphic3d_TOD_VEC3;
atri[1].Id = Graphic3d_TOA_NORM;
atri[1].DataType = Graphic3d_TOD_VEC3;
int nbAttribs = 2;
newAttrBuffer->Init(aPArray->VertexNumber(), atri, nbAttribs);

// Fill new buffer with the same data for each attribute, to generate the same drawing
int dummyInd;
Standard_Size posStride, oldPstride, normStride, oldNstride;
const Standard_Byte* oldPosData = aPArray->Attributes()->AttributeData(Graphic3d_TOA_POS, dummyInd, oldPstride);
const Standard_Byte* oldNormData = aPArray->Attributes()->AttributeData(Graphic3d_TOA_NORM, dummyInd, oldNstride);
int attributesTam = aPArray->Attributes()->Size() / aPArray->Attributes()->NbAttributes;
for (int i = 0; i < attributesTam; i += 1)
{
newAttrBuffer->ChangeAttributeData(Graphic3d_TOA_POS, dummyInd, posStride)[i] = oldPosData[i];

newAttrBuffer->ChangeAttributeData(Graphic3d_TOA_NORM, dummyInd, normStride)[i] = oldNormData[i];

}

grupo->SetClosed(true);
grupo->SetPrimitivesAspect(CADdrawer->ShadingAspect()->Aspect());
grupo->AddPrimitiveArray(Graphic3d_TOPA_TRIANGLES, aPArray->Indices(), newAttrBuffer, aPArray->Bounds(), true);
}

###############

If the code was correct, my assumtion is that I should obtain the same results as in the previous code, but for some reason when using the new attribute buffer I obtain no result in my viewer.

Maybe I'm forgetting to set something in the new buffer? Or maybe the byte-to-byte copy is not correct?

I can't figure this out, no error message is shown and during debug execution all routines seem to execute correctly.

Thanks in advance!!

gkv311 n's picture

Graphic3d_Buffer::AttributeData() returns a pointer to beginning of attribute data in the buffer. So that the following loop and assignment make no sense to me:

const Standard_Byte* oldPosData = aPArray->Attributes()->AttributeData(Graphic3d_TOA_POS, dummyInd, oldPstride);
const Standard_Byte* oldNormData = aPArray->Attributes()->AttributeData(Graphic3d_TOA_NORM, dummyInd, oldNstride);
int attributesTam = aPArray->Attributes()->Size() / aPArray->Attributes()->NbAttributes;
for (int i = 0; i < attributesTam; i += 1)
{
  newAttrBuffer->ChangeAttributeData(Graphic3d_TOA_POS, dummyInd, posStride)[i] = oldPosData[i];
  newAttrBuffer->ChangeAttributeData(Graphic3d_TOA_NORM, dummyInd, normStride)[i] = oldNormData[i];
}

Instead, you need to returned stride multiplied by vertex index (from 0) as an offset from the beginning of this pointer, alias it actual per-attribute per-vertex layout (e.g. cast pointer to Graphic3d_Vec3* in case of Graphic3d_TOD_VEC3) and assign data by values. Have a deeper look at methods in Graphic3d_ArrayOfPrimitives like ::SetVertexNormal() and ::SetVertice():

  void SetVertexNormal (const Standard_Integer theIndex, const Standard_Real theNX, const Standard_Real theNY, const Standard_Real theNZ)
  {
    Standard_OutOfRange_Raise_if (theIndex < 1 || theIndex > myAttribs->NbMaxElements(), "BAD VERTEX index");
    if (myNormData != NULL)
    {
      Graphic3d_Vec3& aVec = *reinterpret_cast<Graphic3d_Vec3* >(myNormData + myNormStride * (theIndex - 1));
      aVec.x() = Standard_ShortReal (theNX);
      aVec.y() = Standard_ShortReal (theNY);
      aVec.z() = Standard_ShortReal (theNZ);
    }
    myAttribs->NbElements = Max (theIndex, myAttribs->NbElements);
  }

In case of interleaved attributes (which is default), you may also define a structure holding all per-vertex attributes at once and work with it:

struct MyPos3Norm3
{
  Graphic3d_Vec3 Pos;  //!< Graphic3d_TOD_VEC3
  Graphic3d_Vec3 Norm; //!< Graphic3d_TOD_VEC3
};

for (int aVertIt = 0; aVertIt < aNbVertices; ++aVertIt)
{
  MyPos3Norm3& aVert = newAttrBuffer->ChangeValue<MyPos3Norm3>(aVertIt);
  aVert.Pos = ...;  // assign vertex position
  aVert.Norm = ...; // assign vertex normal
}