Reputation: 423
I am trying to render 3D prisms in LWJGL OpenGL with flat shading. For example, I have a cube indexed as following:
I only have 8 vertices in the vertex buffer, which I have indexed as above. Is there any way to implement flat normal shading on the cube such as below? I don't want to rewrite my vertex and index buffers to include duplicate vertices if possible.
Upvotes: 11
Views: 8780
Reputation: 21
(Can't believe I wrote all this before realizing this was for LWJGL. Sorry mate, not sure how much the C++ solutions can translate over. )
Don't know if you still want an answer, but there's probably some poor soul trying to figure this out (like me this morning), so here ya go.
The "flat" qualifier does work, but it wont work for anything that has more faces than vertices, which is nearly all triangle meshes. Duplicating vertices just feels like a waste, especially if you truly want flat shading, not just a sharp edge (in which case vertex duplicating makes perfect sense). Instead, you can create a separate buffer for face-specific data.
First, define some struct with the data you want to provide per-face, and create a list of all the data for your faces, ensuring the order matches the order of your indices:
struct FaceData {
glm::vec4 color;
glm::vec4 normal;
...
};
std::vector<FaceData> my_face_data; // Fill with data. Ideally, *.size() == face_count
Then, wherever you're creating and providing data for your VBO/VAO/EBO, create an SSBO:
GLuint FDB; // Face data buffer
glGenBuffers(1, &FDB);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, FDB);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(FaceData) * my_face_data.size(), my_face_data.data(), GL_STATIC_DRAW);
Before a rendering call, use glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, FDB);
, and after the call you should be able to unbind that buffer by replacing FBD with 0.
Then add something like this to your fragment shader code:
struct FaceData {
vec4 color;
vec4 normal;
...
// Note: sometimes GLSL's packing is weird
// It kept interpreting vec3s as 16 bytes instead of 12, so I had to use vec4s.
// When in doubt, using individual floats should always work since they're always 4 bytes
};
layout(std430, binding = 0) buffer MyFaceDataBuffer {
FaceData myData[];
};
void main() {
FaceData data = myData[gl_PrimitiveID]; // gl_PrimitiveID is just the primitive's index
vec4 color = data.color;
vec4 normal = data.normal;
...
// Fancy business of your choosing
}
Quick note: You can make multiple SSBOs, but you need unique bindings to use multiple at once (in frag shader: ...std430, binding = BINDING...
, before render call: glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING, FDB);
).
Keep in mind there is a maximum number of SSBO bindings, and you can query it using glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &yourInteger);
. You shouldn't need to worry about the limit much though. On my system it was 96, and that's the max number of bindings, so you can have as many SSBOs as you want as long as they're not all bound at once.
Upvotes: 1
Reputation: 46
For flat shading, it is better to use a geometry shader to compute the normals for each of the primitives. Although you can use the provoking-vertex method when rendering a cube, you cannot use it for certain geometric objects where the number of faces is more than that of the vertices: e.g. consider the polyhedron obtained by gluing two tetrahedra at their base triangle. Such an object will have 6 triangles but only 5 vertices (note that Euler's formula still holds: v-e+f = 5-9+6 = 2), so there are not enough vertices to send the face-normals via the vertices. Even if you can do that, another reason not to use provokig-vertex method is that it is not convenient to do so, because you would have to find a way to enumare the vertices in a way such that each vertex uniquely 'represents' a single face, so that you can associate the face-normal with it.
In a nutshell, just use a geometry shader, it is much simpler and more importantly much more robust. Not to mention that the normal calculations are done on the fly inside the GPU, rather than you having to set them up on CPU, creating & binding the necessary buffers and defining attributes which increases both the set-up costs and eats up the memory bandwith between the CPU and the GPU.
Upvotes: 1
Reputation: 210889
If you don't need any other attributes (e.g. texture coordinates), then there is an option to create a cube mesh with face normal vectors, by 8 vertices only. Use the flat
Interpolation qualifier
for the normal vector.
Vertex shader:
flat out vec3 surfaceNormal;
Fragment sahder:
flat out vec3 surfaceNormal;
When the flat
qualifier is used, then the output of the vertex shader will not be interpolated. The value given to the fragment shader is one of the attributes associated to one vertex of the primitive, the Provoking vertex.
For a GL_TRINANGLE
primitive this is either the last or the first vertex. That can be chosen by glProvokingVertex
.
Choose the first vertex:
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
For the order of the points of your cube mesh (image in the question)
front back
1 3 7 5
+---+ +---+
| | | |
+---+ +---+
0 2 6 4
you have to setup the following vertex coordinates and normal vectors:
// x y z nx, ny, nz
-1, -1, -1, 0, -1, 0, // 0, nv front
-1, -1, 1, 0, 0, 1, // 1, nv top
1, -1, -1, 0, 0, 0, // 2
1, -1, 1, 1, 0, 0, // 3, nv right
1, 1, -1, 0, 1, 0, // 4, nv back
1, 1, 1, 0, 0, 0, // 5
-1, 1, -1, 0, 0, -1, // 6, nv bottom
-1, 1, 1, -1, 0, 0, // 7, nv left
Define the indices in that way, that the vertices 7, 3, 0, 4, 6, 1 are the first vertex for both triangles of the left, right, front, back, bottom and top of the cube:
0, 2, 3, 0, 3, 1, // front
4, 6, 7, 4, 7, 5, // back
3, 2, 4, 3, 4, 5, // right
7, 6, 0, 7, 0, 1, // left
6, 4, 2, 6, 2, 0, // bottom
1, 3, 5, 1, 5, 7 // top
Draw 12 triangle primitives. e.g:
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
Upvotes: 12