Using normals with voxels

Started by
8 comments, last by slicer4ever 10 years, 3 months ago

Hello everyone,

I just set the prefix to OpenGL because thats in the end what my engine is based on. Tho the question is not related to OpenGL per se but of a general kind.

Currently i am rendering terrain using cubes. 'Calculating' the normals for the cube faces is fairly obvious but im running into some problems with the fact that a vertex basically has 3 normals. I am using a vertex buffer with the points of the cube and an index buffer which holds the indices for the faces. So a cube in the end consists of 8 vertices and up to 36 indices (depending on the neighbors). Now a vertex has 3 distinct normals and there should be no interpolation amongst the faces of the cube. Is there an elegant way to my normals working and avoiding to create 3 vertices for each point with lots of redundant data?

Thanks in advance for you help and a merry xmas

Plerion

Advertisement
You can't avoid the "redundancy". For proper normals you need 24 distinct vertices (see here for something similar).

Some suggestions though:
- if bandwidth is a problem, you could use low-precision formats (normals and maybe even positions can be integers)
- how about creating the cube procedurally in the vertex shader ? (hmmm, probably only applicable with instancing...)

PS: Ah, ja, o schöni Wiehnachte smile.png

Hey, thank you for the hints. I have considered several options. First i was still trying to somehow get it done with 8 vertices and like a cube map for normals but in the end decided thats not even worth trying, as it hits performance way too bad and im not really in trouble with the current bandwidth. So rather ive tried to compress my data (3 floats position, 3 floats normals) and ended up with 2 bytes position and 1 byte normal. 1 byte of the position contains x and y as chunks have 16x16 rows so its upper 4 and lower 4 bits of the value. The second byte contains the z position as a row can be up to 256 blocks high. Finally the normal is compressed like this: 1 -> 1/0/0, -1 -> -1/0/0, 10 -> 0/1/0, -10 -> 0/-1/0, 100 -> 0/0/1, -100 -> 0/0/-1 and then reconstructed using the step function and division. So now i use less badnwidth and have correct normals and still have a byte left for occlusion values (as im using an Int4 vector).

PS: Merci, das wünsch ich diar doch au :D

If you only need flat shading, then you may actually get away with only eight vertices even if a cube with normals technically has 24 unique vertices. In flat shading, an attribute is automatically replicated over all vertices for a given primitive, and it is in fact possible to specify a cube with a vertex/normal array size of only eight entries. Look up the command glProvokingVertex which specifies which of the three vertices in a triangle, for example, that contains the flat-shaded attribute.

If you have for example a flat-shaded normal attribute in your vertex shader, and specify the last vertex as the provoking vertex, then the last vertex is the one containing the normal for all three vertices. The first two vertices effectively contains unused normal data and you should be able to take advantage of that to reduce the size of the vertex arrays.


//     7-----6
//    /|    /|
//   3-----2 |
//   | 4---|-5 
//   |/    |/
//   0-----1
 
    vector3 p[] = {
       { -1, -1, -1},
       {  1, -1, -1},
       {  1,  1, -1},
       { -1,  1, -1},
       { -1, -1,  1},
       {  1, -1,  1},
       {  1,  1,  1},
       { -1,  1,  1},
    };
 
    vector3 n[] = {
       {  0, -1,  0},
       {  1,  0,  0},
       {  0,  0, -1},
       { -1,  0,  0},
       {  0,  0,  0},
       {  0,  0,  0},
       {  0,  1,  0},
       {  0,  0,  1},
    };
 
    int index[] = {
        0,1,2,3,0,2,
        5,6,1,6,2,1,
        5,4,7,6,5,7,
        4,0,3,7,4,3,
        3,2,6,7,3,6,
        5,4,0,1,5,0,
    };

Try and see if this works, I don't have the possibility to try it myself at the moment. The index list is a list for 12 CCW-winded triangles building the cube in the comment at the top of the code.

If you only need to draw parts of the cube, you can truncate the index list accordingly.

Brother Bob's solution is pretty interesting, and probably better than what I'm about to propose, but I thought I'd add another option to the pot anyway.

You could construct the normal in the fragment shader using the derivative intrinsic. Basically: n = normalize(cross(dfdx(pos), dfdy(pos)));

Obvious drawback is that you must perform that calculation per fragment. But you don't even have to send any normal data down to the vertex shader!

Also, in a similar vein, you could compute the primitive normal in the geometry shader and spare yourself the per fragment computation, but it's not a given that that would be any faster.
And yet another idea: Vertex Puller. One of the latest GPU Pro books has a chapter about this. In essence you don't bind your vertex (and index buffer) as such, but as readable buffers or textures. Then in the vertex shader you load/sample from them using the vertex ID. Manual input assembler, so to speak.

PS: This is just for fun (D3D11 vertex shader, use with Draw(36,0)) wink.png :


void ProceduralCubeVS(uint iid: SV_VertexID,
  out float3 position   : POSITION,
  out float2 tex        : TEXCOORD,
  out float3 normal     : NORMAL,    
  out float4 positionCS : SV_Position)
{
    uint face = iid / 6;
    uint index = iid % 6;
    float sign = face >= 3 ? 1 : -1;
    uint dir = face % 3;
    normal = float3(dir.xxx == uint3(0,1,2));
    float3 t = normal.yzx;
    float3 b = normal.zxy;

    const uint FSQIDS[] = {0,1,2,1,3,2};    
    uint id = FSQIDS[index];
    tex = float2(id & 1, (id >> 1) & 1);
    float2 fsq = float2(tex * float2(1,-1) + float2(-0.5,0.5));    
    normal *= sign;
    position = float3(fsq.x * t + sign * fsq.y * b + 0.5 * normal);

    positionCS = mul(float4(position,1), WorldViewProjection);
    position = mul(float4(position, 1), World).xyz;    
    normal = mul(normal, (float3x3)World).xyz;    
}

768b0a297248306.jpg

@BrotherBob: Your suggestion is very interesting! I will surely try that as soon as i find time!

@Samith: I will try this as well and see what impact on performance it has and then see what balances out better, thanks for the tipp!

@unbird: Hehe, that looks interesting, but need to have a closer look at it first :D

An important question: Is this really a problem?

You should wait with this optimization until you find out that it is really an issue. Then, depending on exactly what the issue is, you optimize.

It is not unusual that a change of algorithms can give much better pay-off. For example, improved culling or the use of LOD.

[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/

I have basiclly two solutions to this issue

1. send points to the geometry shader and use it to draw cubes, this should be good for when you have a lot of freemoving cubes or basically cube particles.

2. if the terrain is more or less static then assembling the vertecies, normals and texture coordinates manually into raw ttriangles and then pushing it into a VBO is the way to go, even with the simplest possible culling it's fast enough that you don't have to mess around with indices at all.

If you only need flat shading, then you may actually get away with only eight vertices even if a cube with normals technically has 24 unique vertices. In flat shading, an attribute is automatically replicated over all vertices for a given primitive, and it is in fact possible to specify a cube with a vertex/normal array size of only eight entries. Look up the command glProvokingVertex which specifies which of the three vertices in a triangle, for example, that contains the flat-shaded attribute.

If you have for example a flat-shaded normal attribute in your vertex shader, and specify the last vertex as the provoking vertex, then the last vertex is the one containing the normal for all three vertices. The first two vertices effectively contains unused normal data and you should be able to take advantage of that to reduce the size of the vertex arrays.


//     7-----6
//    /|    /|
//   3-----2 |
//   | 4---|-5 
//   |/    |/
//   0-----1
 
    vector3 p[] = {
       { -1, -1, -1},
       {  1, -1, -1},
       {  1,  1, -1},
       { -1,  1, -1},
       { -1, -1,  1},
       {  1, -1,  1},
       {  1,  1,  1},
       { -1,  1,  1},
    };
 
    vector3 n[] = {
       {  0, -1,  0},
       {  1,  0,  0},
       {  0,  0, -1},
       { -1,  0,  0},
       {  0,  0,  0},
       {  0,  0,  0},
       {  0,  1,  0},
       {  0,  0,  1},
    };
 
    int index[] = {
        0,1,2,3,0,2,
        5,6,1,6,2,1,
        5,4,7,6,5,7,
        4,0,3,7,4,3,
        3,2,6,7,3,6,
        5,4,0,1,5,0,
    };
Try and see if this works, I don't have the possibility to try it myself at the moment. The index list is a list for 12 CCW-winded triangles building the cube in the comment at the top of the code.

If you only need to draw parts of the cube, you can truncate the index list accordingly.


I'd like to point out that this solution is very special to this case currently, if you start adding other attributes, such as texture coordinates. This solution begins to fall apart, and you are back to square one.

Although i will admit it's pretty creative, not something i would have thought of trying.
Check out https://www.facebook.com/LiquidGames for some great games made by me on the Playstation Mobile market.

This topic is closed to new replies.

Advertisement