If you're using the built-in vertex attribute gathering from vertex buffer bindings, a single numeric index is all you can use as indexes are used directly by the API.
This is immutable and cannot change, each element from the index buffer is used to gather attributes from the same offset in each bound attribute buffer.
A neat technique you may want to look at is that you can instead bind your geometry as textures or SSBOs to your shader and draw with no buffers bound, relying on
gl_VertexID in the shader to sample from your geometry sources, where
my_Positions is such a buffer and
my_Indices is a buffer of indices.
gl_VertexID is a shader-visible integer that holds the value of the index of the current vertex being transformed. If you draw with an index buffer, it's the index, if you draw unindexed geometry, it's in a linearly increasing sequence of
0,1,2.... By drawing geometry with N unindexed vertices with no index buffers and vertex buffers bound in the VAO, you can do the attribute sampling yourself in your shader like:
vec4 position = my_Positions[my_Indices[gl_VertexID]];
vec3 normal = my_Normals[my_Indices[gl_VertexID]];
This can trivially be extended into multiple index sources:
vec4 position = my_Positions[my_PositionIndices[gl_VertexID]];
vec3 normal = my_Normals[my_NormalIndices[gl_VertexID]];
If your indices are small enough, you can avoid binding separate buffers for indices and use the built-in element buffer by packing all your indices into a single R32 index, like
idx = ((pIdx & 0xFFFF) << 16 | (nIdx & 0xFFFF)). You still have to do the manual sampling in your shader, as the built-in index lookup would overshoot any bound buffers.