5
$\begingroup$

I am following the WebGL2Fundametals tutorial. Using some sample (cube) data I was able to successfully call gl.drawElements(); using index, normal and geometry data. This are my fragment and vertex shaders:

Fragment shader:

#version 300 es    
precision mediump float;

in vec3 v_normal;
in vec3 v_surfaceToLight;
in vec3 v_surfaceToView;

uniform vec4 u_color;
uniform float u_shininess;
uniform vec3 u_lightDirection;

uniform float u_innerLimit; // In dot space
uniform float u_outerLimit; // In dot space

out vec4 outColor;

void main() {
  vec3 normal = normalize(v_normal);

  vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
  vec3 surfaceToViewDirection = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);

  float dotFromDirection = dot(surfaceToLightDirection, -u_lightDirection);
  float inLight = smoothstep(u_outerLimit, u_innerLimit, dotFromDirection);
  // Using normals for color calculation based on the incidence of light
  float light = inLight * dot(normal, surfaceToLightDirection);
  float specular = inLight * pow(dot(normal, halfVector), u_shininess);

  outColor = u_color;

  outColor.rgb *= light;

  // Add in the specular
  outColor.rgb += specular;
}

Vertex shader:

#version 300 es
in vec4 a_position;
in vec3 a_normal;

uniform mat4 u_world;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;

uniform vec3 u_lightWorldPosition;
uniform vec3 u_viewWorldPosition;

// varying to pass the normal to the fragment shader
out vec3 v_normal;

out vec3 v_surfaceToLight;
out vec3 v_surfaceToView;

void main() {

  gl_Position = u_worldViewProjection * a_position;

  // Orient the normals and pass to the fragment shader
  v_normal = mat3(u_worldInverseTranspose) * a_normal;

  vec3 surfaceWorldPosition = (u_world * a_position).xyz;
  v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;
  v_surfaceToView = u_viewWorldPosition - surfaceWorldPosition;
}

With the following call the cube is successfully rendered:

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
GLHelper.setUniformValues(this._uniforms, uniformValues);
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);

Here is the working cube data:

Vertices:

 // Front face
-100, -100, 100,
 100, -100, 100,
 100,  100, 100,
-100,  100, 100,

 // Back face
-100, -100, -100,
-100,  100, -100,
 100,  100, -100,
 100, -100, -100,

 // Top face
-100,  100, -100,
-100,  100,  100,
 100,  100,  100,
 100,  100, -100,

 // Bottom face
-100, -100, -100,
 100, -100, -100,
 100, -100,  100,
-100, -100,  100,

 // Right face
 100, -100, -100,
 100,  100, -100,
 100,  100,  100,
 100, -100,  100,

 // Left face
-100, -100, -100,
-100, -100,  100,
-100,  100,  100,
-100,  100, -100,

Normals:

 // Front
 0.0,  0.0,  1.0,
 0.0,  0.0,  1.0,
 0.0,  0.0,  1.0,
 0.0,  0.0,  1.0,

 // Back face
 0.0,  0.0, -1.0,
 0.0,  0.0, -1.0,
 0.0,  0.0, -1.0,
 0.0,  0.0, -1.0,

 // Top face
 0.0,  1.0,  0.0,
 0.0,  1.0,  0.0,
 0.0,  1.0,  0.0,
 0.0,  1.0,  0.0,

 // Bottom face
 0.0, -1.0,  0.0,
 0.0, -1.0,  0.0,
 0.0, -1.0,  0.0,
 0.0, -1.0,  0.0,

 // Right face
 1.0,  0.0,  0.0,
 1.0,  0.0,  0.0,
 1.0,  0.0,  0.0,
 1.0,  0.0,  0.0,

 // Left face
-1.0,  0.0,  0.0,
-1.0,  0.0,  0.0,
-1.0,  0.0,  0.0,
-1.0,  0.0,  0.0,

Indices:

 0,  1,  2,  0,  2,  3, // Front face
 4,  5,  6,  4,  6,  7, // Back face
 8,  9, 10,  8, 10, 11, // Top face
12, 13, 14, 12, 14, 15, // Bottom face
16, 17, 18, 16, 18, 19, // Right face
20, 21, 22, 20, 22, 23, // Left face

If I get it right the normals in this model should look something like the cube's normals in the very first animation in the "Introducing normals" part. Now I was trying to create and import a cube using Blender.

Unfortunately I wasn't able to recreate a cube with the same kind of normals. Blender generates only one (pink) normal per vertex and they also obviously have a totally different angle:

enter image description here

Some sources suggest that smoothing the faces would fix this issue. It did not for me. Here is the Wavefront file:

o Cube
v   1.000000 -1.000000 -1.000000
v   1.000000 -1.000000  1.000000
v  -1.000000 -1.000000  1.000000
v  -1.000000 -1.000000 -1.000000
v   1.000000  1.000000 -0.999999
v   0.999999  1.000000  1.000001
v  -1.000000  1.000000  1.000000
v  -1.000000  1.000000 -1.000000
vn  0.0000   -1.0000    0.0000
vn  0.0000    1.0000    0.0000
vn  1.0000   -0.0000    0.0000
vn  0.0000   -0.0000    1.0000
vn -1.0000   -0.0000   -0.0000
vn  0.0000    0.0000   -1.0000
s off
f 1//1 3//1 4//1
f 8//2 6//2 5//2
f 5//3 2//3 1//3
f 6//4 3//4 2//4
f 3//5 8//5 4//5
f 1//6 8//6 5//6
f 1//1 2//1 3//1
f 8//2 7//2 6//2
f 5//3 6//3 2//3
f 6//4 7//4 3//4
f 3//5 7//5 8//5
f 1//6 4//6 8//6

When I try to import and to render this data using gl.drawElements(gl.TRIANGLES, 72, gl.UNSIGNED_SHORT, 0); I get the following output:

ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 1

If I use above (.obj) vertex and normal data six time each I do not get any more errors but instead of a cube only a single triangle seems to be rendered.

enter image description here

I am pretty sure that attribute 1 is in vec3 a_normal; in my vertex shader. So I am mainly confused about one thing here:

  • How do I modify my code in order to interpret and render the data correctly?

EDIT:

After changing indices to be zero based I do get more than a single triangle, the object still looks wrong.

enter image description here

The result drawn with gl.LINES:

enter image description here

$\endgroup$
3
  • 1
    $\begingroup$ Blender questions belong on Blender. If you could edit out the "how to use Blender" parts of your question, we should be able to help you with the programming part. $\endgroup$
    – Dan Hulme
    Commented Jan 5, 2018 at 18:23
  • 1
    $\begingroup$ First thing to check is that the indices in the file you've shown are 1-based, while in GL they should be 0-based. That is, you need to subtract one from all the indices in the file. $\endgroup$
    – Dan Hulme
    Commented Jan 5, 2018 at 18:24
  • 1
    $\begingroup$ @DanHulme Done, I edited the question part to ask for help to modify the code in order to interpret the wavefront data correctly. I also subtracted 1 from every index in the file. This did change the rendered mesh, it is still not a cube though. Any help would be appreciated. $\endgroup$ Commented Jan 6, 2018 at 10:56

1 Answer 1

3
+25
$\begingroup$

The problem is with how wavefront files and OpenGL work with indices.

OpenGL

OpenGL allows you to have different buffers, allowing your vertex to have multiple attributes. However, for every vertex in a face, it has only one index.

Let us say that we have a quad. It consists out of a position buffer and a uv buffer. The positions would look like this

1, 1, 0;  -1, 1, 0;  -1, -1, 0;  1, -1, 0;

The uv's would look like this

0, 0;     1, 0;      1, 1;       0, 1;

The indices would look like this

0, 1, 2, 0, 2, 3

When OpenGL does it's rendering, it looks at the index for the location of the data. If we have the very first vertex of the very first face, with the index of 0. It then picks the position with the index of 0: (1, 1, 0). Then it picks the uv with also the index of zero: (0, 0).

Using the same index for each attribute means that all data can be sequentially stored in one long buffer. If OpenGL needs the data for a vertex, it only has to load one long strip of data. This is better for the cache than having to go to different parts of the memory to find all the information for just one vertex.

OpenGL uses the same index for all vertex attributes. You cannot tell OpenGL to take position number 3 with uv number 1.

Wavefront

Wavefront works differently. When doing the indexing, it indexes each attribute separately.

Here is a declaration of a face

f 5//3 2//3 1//3

We see three vertices, separated by a white space. Each vertex has two numbers. The first number is the index for the position. The second number is the index for the normal. They are both different.

Wavefront does not need to care about caching. It just needs to store the data by using the least amount of memory (if we load everything in to memory. Saving it in plain text is of course not efficient). When looking at your .obj file, we can see that we have 8 positions, but only 6 normals. We have been able to remove 6 floating point numbers in total (2 normals consisting of 3 floats).

Wavefront specifies different indices for each vertex attribute. For a vertex, the position could be number 5, but the normal is number 4.

Conclusion

Wavefront and OpenGL use a different way of storing the data. This means that when parsing your wavefront file, you need to re-index the data to what OpenGL wants.

What happened with you, is that OpenGL thought that the indices for the normals were actually indices for positions and that you had twice as many faces.

What I would do, is to save all that wavefront data to temporary lists. You create your main vertex buffers. You go through every vertex of every face. You look at the data of that vertex. Then you see if you already have that data combined in your buffers. If so, you use the index of that data, else you add the new data to your buffers and add that index.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.