I've been looking into OpenGL and diving into the 3D world. This question relates to OpenGL in general but I've been using WebGL (which is OpenGL ES if I believe). I've had a problem understanding how cubes are drawn. I know that to draw a quad, you create two triangles in (default) CCW order like
1 0
|‾ ‾ ‾ ‾|
| | Indices = 0,1,2, 0,2,3 (2 triangles one face)
| |
|_ _ _ _|
2 3
However, I'm having a bit of trouble understanding the best way to draw a cube. Is there a specific way I should be drawing the cube? For example, is drawing the faces in front->right->bottom->left->top->back the best order or is it some other way. Do I draw the cube faces counter-clockwise too or something? I just need to understand the different ways I could represent a model/cube and why.
It can be done with 2 triangle strips, but strips are usually replaced with indexed triangles for more complex geometry because they become more efficient when you need multiple strips. I think it can also be done with a single strip if you use a degenerate triangle to connect the two, but indexed is usually better for larger geometry. Quads just get converted into triangles.
The winding direction is important only when backface culling is enabled. Also, things get more complicated when you add texture coordinates and surface normals for lighting. The normals can be used to make the cube look faceted or smooth shaded, which is really for larger models, like spheres. Here is a tutorial I wrote years ago for OpenGL ES 2.0:
/******************************************************************************
Function DrawCubeSmooth
Return None
Description Draw a cube using Vertex and NormalsPerVertex Arrays and
glDrawArrays with two triangle strips. Because normals are
supplied per vertex, all the triangles will be smooth shaded.
Triangle strips are used instead of an index array. The first
strip is texture mapped but the second strip is not.
******************************************************************************/
void Cube2::DrawCubeSmooth(void)
{
static GLfloat Vertices[16][3] =
{ // x y z
{-1.0, -1.0, 1.0}, // 1 left First Strip
{-1.0, 1.0, 1.0}, // 3
{-1.0, -1.0, -1.0}, // 0
{-1.0, 1.0, -1.0}, // 2
{ 1.0, -1.0, -1.0}, // 4 back
{ 1.0, 1.0, -1.0}, // 6
{ 1.0, -1.0, 1.0}, // 5 right
{ 1.0, 1.0, 1.0}, // 7
{ 1.0, 1.0, -1.0}, // 6 top Second Strip
{-1.0, 1.0, -1.0}, // 2
{ 1.0, 1.0, 1.0}, // 7
{-1.0, 1.0, 1.0}, // 3
{ 1.0, -1.0, 1.0}, // 5 front
{-1.0, -1.0, 1.0}, // 1
{ 1.0, -1.0, -1.0}, // 4 bottom
{-1.0, -1.0, -1.0} // 0
};
static GLfloat NormalsPerVertex[16][3] = // One normal per vertex.
{ // x y z
{-0.5, -0.5, 0.5}, // 1 left First Strip
{-0.5, 0.5, 0.5}, // 3
{-0.5, -0.5, -0.5}, // 0
{-0.5, 0.5, -0.5}, // 2
{ 0.5, -0.5, -0.5}, // 4 back
{ 0.5, 0.5, -0.5}, // 6
{ 0.5, -0.5, 0.5}, // 5 right
{ 0.5, 0.5, 0.5}, // 7
{ 0.5, 0.5, -0.5}, // 6 top Second Strip
{-0.5, 0.5, -0.5}, // 2
{ 0.5, 0.5, 0.5}, // 7
{-0.5, 0.5, 0.5}, // 3
{ 0.5, -0.5, 0.5}, // 5 front
{-0.5, -0.5, 0.5}, // 1
{ 0.5, -0.5, -0.5}, // 4 bottom
{-0.5, -0.5, -0.5} // 0
};
static GLfloat TexCoords[8][2] =
{ // x y
{0.0, 1.0}, // 1 left First Strip
{1.0, 1.0}, // 3
{0.0, 0.0}, // 0
{1.0, 0.0}, // 2
{0.0, 1.0}, // 4 back
{1.0, 1.0}, // 6
{0.0, 0.0}, // 5 right
{1.0, 0.0} // 7
};
glEnableVertexAttribArray(VERTEX_ARRAY);
glEnableVertexAttribArray(NORMAL_ARRAY);
glEnableVertexAttribArray(TEXCOORD_ARRAY);
// Set pointers to the arrays
glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 0, Vertices);
glVertexAttribPointer(NORMAL_ARRAY, 3, GL_FLOAT, GL_FALSE, 0, NormalsPerVertex);
glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, 0, TexCoords);
// Draw first triangle strip with texture map
glDrawArrays(GL_TRIANGLE_STRIP, 0, 8);
// Draw second triangle strip without texture map
glDisableVertexAttribArray(TEXCOORD_ARRAY);
glDrawArrays(GL_TRIANGLE_STRIP, 8, 8);
glDisableVertexAttribArray(VERTEX_ARRAY);
glDisableVertexAttribArray(NORMAL_ARRAY);
};
I hope this helps.
Related
Just found a uv picture
As follows I created a triangle, three vertices and corresponding uv coordinates
let geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(
new Float32Array([-1.0, 1.0, 0, -1.0, -1.0, 0, 1.0, 1.0, 0]),
3
)
);
geometry.setAttribute(
"uv",
new THREE.BufferAttribute(new Float32Array([0.0, 1.0, 0, 0, 1.0, 1.0]), 2)
);
let mesh = new THREE.Mesh(
geometry,
new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load("/1.png"),
})
);
As shown in the figure below, the vertex order is counterclockwise, so the side shooting from the screen to us is the front.
When I clone a copy and do x-axis scaling -1
let clonedMesh = mesh.clone(true);
scene.add(mesh);
clonedMesh.scale.set(-1, 1, 1);
clonedMesh.position.x = 2;
scene.add(clonedMesh);
The order becomes clockwise as follows, the front should be shot into the screen, and MeshBasicMaterial only renders the front by default, so it stands to reason that the clonedMesh should not be able to see it.
So I'm writing some WebGL, no THREE.JS. I'm trying to render a cube, with a single texture mapped to every face of the cube. In my code where I set up my attributes I have something like:
var vertices = new Float32Array([
// x, y, z u, v
1.0, 1.0, 1.0, /* v0 right top front */ 1.0, 1.0,
-1.0, 1.0, 1.0, /* v1 left top front */ 0.0, 1.0,
-1.0, -1.0, 1.0, /* v2 left bottom front */ 0.0, 0.0,
1.0, -1.0, 1.0, /* v3 right bottom front */ 1.0, 0.0,
// u's switch for back faces
1.0, -1.0, -1.0, /* v4 right bottom back */ 0.0, 0.0,
1.0, 1.0, -1.0, /* v5 right top back */ 0.0, 1.0,
-1.0, 1.0, -1.0, /* v6 left top back */ 1.0, 1.0,
-1.0, -1.0, -1.0, /* v7 left bottom back */ 1.0, 0.0
]);
// the pairs of vertex triples
// 3 vertices = 1 triangle
// 2 triangles = 1 quad = 1 face
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
0, 3, 4, 0, 4, 5, // right
//0, 5, 6, 0, 6, 1, // top
1, 6, 7, 1, 7, 2, // left
//7, 4, 3, 7, 3, 2, // bottom
4, 7, 6, 4, 6, 5 // back
]);
I wind up with a cube with the texture reflected for the right and left faces, which is fine. For the top and the bottom, I have no faces because of the two commented out lines. When I comment them in, the faces don't have the texture sampled as I expected. Sure enough, if you look at the indices for the top face for instance, and the UV coordinates that they would have:
index | u | v
0 | 1.0 | 1.0
1 | 0.0 | 1.0
5 | 0.0 | 1.0
6 | 1.0 | 1.0
So we can see that index 1 and 5 (also, 0 and 6) have the same UV coordinates, so of course it wont look right on a quad.
I've been trying to draw out on paper, but I can't change the UV's without messing up another face's coordinates. What I'm wondering is: is it possible to use ELEMENT_ARRAY_BUFFERs to map UV coordinates on a cube, or do I need to use more data and draw using an ARRAY_BUFFER?
== EDIT ==
Looks like a dupe: OpenGL ES - texture map all faces of an 8 vertex cube?
Hate to answer my own question, but based on the hint here, I was able to get it to work by using 24 vertices instead of 8. I can use 24 instead of 36 because I'm repeating indices in my ELEMENT_ARRAY_BUFFER (something I wouldn't be able to do with just an ARRAY_BUFFER).
var vertices = new Float32Array([
// x, y, z, u, v
// front face (z: +1)
1.0, 1.0, 1.0, 1.0, 1.0, // top right
-1.0, 1.0, 1.0, 0.0, 1.0, // top left
-1.0, -1.0, 1.0, 0.0, 0.0, // bottom left
1.0, -1.0, 1.0, 1.0, 0.0, // bottom right
// right face (x: +1)
1.0, 1.0, -1.0, 1.0, 1.0, // top right
1.0, 1.0, 1.0, 0.0, 1.0, // top left
1.0, -1.0, 1.0, 0.0, 0.0, // bottom left
1.0, -1.0, -1.0, 1.0, 0.0, // bottom right
// top face (y: +1)
1.0, 1.0, -1.0, 1.0, 1.0, // top right
-1.0, 1.0, -1.0, 0.0, 1.0, // top left
-1.0, 1.0, 1.0, 0.0, 0.0, // bottom left
1.0, 1.0, 1.0, 1.0, 0.0, // bottom right
// left face (x: -1)
-1.0, 1.0, 1.0, 1.0, 1.0, // top right
-1.0, 1.0, -1.0, 0.0, 1.0, // top left
-1.0, -1.0, -1.0, 0.0, 0.0, // bottom left
-1.0, -1.0, 1.0, 1.0, 0.0, // bottom right
// bottom face (y: -1)
1.0, -1.0, 1.0, 1.0, 1.0, // top right
-1.0, -1.0, 1.0, 0.0, 1.0, // top left
-1.0, -1.0, -1.0, 0.0, 0.0, // bottom left
1.0, -1.0, -1.0, 1.0, 0.0, // bottom right
// back face (x: -1)
-1.0, 1.0, -1.0, 1.0, 1.0, // top right
1.0, 1.0, -1.0, 0.0, 1.0, // top left
1.0, -1.0, -1.0, 0.0, 0.0, // bottom left
-1.0, -1.0, -1.0, 1.0, 0.0 // bottom right
]);
// the pairs of vertex triples
// 3 vertices = 1 triangle
// 2 triangles = 1 quad = 1 face
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23
]);
Scroll above code example ^
The number of vertices can be further reduced, because some indices share the same XYZ and UV coordinates, though if I add normals to my interleaved buffer later (or any other attribute) I may need the repeated values.
triangle is not drawn when glOrtho z range between 0 and 1.
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( -1,1,-1,1,-1,1 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
// Draw a triagnle in z 0.5
glBegin( GL_TRIANGLES );
glColor3f( 1, 0, 0 );
glVertex3f( -0.5, -0.5, 0.5 );
glVertex3f( 0.5, -0.5, 0.5 );
glVertex3f( 0.0, 0.5, 0.5 );
glEnd();
It displays a red triangle. Its fine for me.
But when I change near clipping plane to 0, It displays nothing.
Triangle is drawing with z 0.5, and near and far is between 0 and 1.
But Triangle is not drawn why ?
Following code is used to display the Triangle in z 0.5
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( -1,1,-1,1,0,1 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
// Draw a triagnle in z 0.5 This triangle is not displayed.
glBegin( GL_TRIANGLES );
glColor3f( 1, 0, 0 );
glVertex3f( -0.5, -0.5, 0.5 );
glVertex3f( 0.5, -0.5, 0.5 );
glVertex3f( 0.0, 0.5, 0.5 );
glEnd();
Note that your triangle is being drawn BEHIND the camera. The positive Z direction is backward, toward the screen, and the negative Z direction is forward, toward the far plane. However, in glOrtho, the near and far planes are only distances in front of the camera, not actual Z coordinates.
In your first example, the near plane is -1, and the far plane is 1, so the frustum has a range of -1 unit in front of the camera (1 unit behind) to 1 unit in front of the camera. (from 1 behind to 1 in front) Therefore, your object behind the camera was drawn.
In your second example, the near plane is 0 units in front of the camera, and the far plane is 1 unit in front of the camera (from origin to 1 in front), so your object behind the camera was not drawn.
To fix it, you should change your triangle to be drawn in front of the camera by drawing it at -0.5 z instead.
// Setup ortho projection from the origin.
glOrtho( -1,1,-1,1,0,1 );
...
// Draw a triangle in front of the camera, at -0.5 z
glVertex3f( -0.5, -0.5, -0.5 );
glVertex3f( 0.5, -0.5, -0.5 );
glVertex3f( 0.0, 0.5, -0.5 );
There are also other methods such as reversing the Z axis, if you prefer to use positive Z values to represent forward:
// Setup ortho projection from the origin, with the Z axis reversed.
// (the far plane is behind the near plane.)
glOrtho( -1,1,-1,1,0,-1 );
...
// Draw a triangle at 0.5 z
glVertex3f( -0.5, -0.5, 0.5 );
glVertex3f( 0.5, -0.5, 0.5 );
glVertex3f( 0.0, 0.5, 0.5 );
The nearVal and farVal values do not specify the z values of the clipping planes, but the distance from the camera. In fixed-function OpenGL, eye space is defined with camera at origin, looking at -z direction.
glOrtho() is just defined so that positive values refer to positions in front of the viewer (so it is consistent to the behavior of glFrustum()), and negative vaules are behind the viewer, so actually, the planes are at z=-nearVal and z=-farVal in eye space.
Imagine you're standing on the ground looking up at a cube in the sky. As you tilt your head, the cube moves. I'm trying to replicate this using OpenGL ES on the iPhone by manipulating the tilt of the camera while looking at a simple 3D cube drawn around the origin. I'm using the gluLookAt() function from Cocos2d which is supposed to emulate the OpenGL version and it seems that when I try to tinker with any of the values, my cube disappears.
My question is: Can you provide a gluLookAt() usage here that will get me started manipulating the camera so I can figure out how this works? I'm really just interesting in learning how to tilt the camera along the Y axis.
Here is my current code:
Viewport Configuration
glBindFramebufferOES(GL_FRAMEBUFFER_OES, _viewFramebuffer);
glViewport(0, 0, _backingWidth, _backingHeight);
Projection Matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// Maybe this should be a perspective projection?? If so,
// can you provide an example using gluPerspective()?
glOrthof(-_backingWidth, _backingWidth,-_backingHeight, _backingHeight, -1, 1);
ModelView Matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt() // What goes here?
Drawing Code
static const GLfloat cubeVertices[] = {
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
};
static const GLushort cubeIndices[] = {
0, 1, 2, 3, 7, 1, 5, 4, 7, 6, 2, 4, 0, 1
};
static const GLubyte cubeColors[] = {
255, 255, 0, 255,
0, 255, 255, 255,
0, 0, 0, 0,
255, 0, 255, 255,
255, 255, 0, 255,
0, 255, 255, 255,
0, 0, 0, 0,
255, 0, 255, 255
};
glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
glEnableClientState(GL_VERTEX_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, cubeColors);
glEnableClientState(GL_COLOR_ARRAY);
glDrawElements(GL_TRIANGLE_STRIP, 14, GL_UNSIGNED_SHORT, cubeIndices);
I'm not completely sure what exactly you want, but here some explanations:
gluLookAt expects 3 vectors (each as 3 doubles): first the position of the camera (eye point), then the position to where you look (center point) and finally an up-vector that specifies an up-direction (this need not be the perfect orthogonal upward direction, as it is reorthogonalized anyway).
So if you stand at (0,0,5) and look at your cube (that is at the center) and want the y-axis to be the up-direction, you would call gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) to see your cube in full beauty.
If you want to tilt your head to the side, you just need to change the up-vector and rotate it to the side a bit. Or if you want to look up, but without tilting the head to the side, you still use the y-axis as up-vector, but you just look at another point, so you change the center point to a point above and in front of you (maybe rotated about the eye position). But this won't work if you want to look straight up, in this case you need to change the up-vector to something orthogonal to the y-axis (in addition to setting the center point to a point straight above you, of course).
But I think you want a perspective projection. Your current ortho is at least quite inappropriate for your coordinates, as it specifies a coordinate system in which coordinates are in the size of pixel, so your [-1,1]-cube is about the size of a pixel on the screen. Try gluPerspective(60.0, ((double)_backingWidth)/_backingHeight, 0.1, 100.0). If you really want an orthographic projection without any realistic perspective distortion, you can use glOrtho, but in this case you should keep the size proprtions of the glOrtho parameters and your model's coordinates roughly in-sync (therefore not specify a screen-sized ortho and using coordinates in the [-1,1] range).
I am coding to OpenGL ES 2.0 (Webgl). I am using VBOs to draw primitives. I have vertex array, color array and array of indices. I have looked at sample codes, books and tutorial, but one thing I don't get - if color is defined per vertex how does it affect the polygonal surfaces adjacent to those vertices? (I am a newbie to OpenGL(ES))
I will explain with an example. I have a cube to draw. From what I read in OpenGLES book, the color is defined as an vertex attribute. In that case, if I want to draw 6 faces of the cube with 6 different colors how should I define the colors. The source of my confusion is: each vertex is common to 3 faces, then how will it help defining a color per vertex? (Or should the color be defined per index?). The fact that we need to subdivide these faces into triangles, makes it harder for me to understand how this relationship works. The same confusion goes for edges. Instead of drawing triangles, let's say I want to draw edges using LINES primitives. Each edge of different color. How am I supposed to define color attributes in that case?
I have seen few working examples. Specifically this tutorial: http://learningwebgl.com/blog/?p=370
I see how color array is defined in the above example to draw a cube with 6 different colored faces, but I don't understand why is defined that way. (Why is each color copied 4 times into unpackedColors for instance?)
Can someone explain how color attributes work in VBO?
[The link above seems inaccessible, so I will post the relevant code here]
cubeVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
vertices = [
// Front face
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// Back face
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
// Top face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
// Bottom face
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,
// Right face
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,
// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);
cubeVertexPositionBuffer.itemSize = 3;
cubeVertexPositionBuffer.numItems = 24;
cubeVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
var colors = [
[1.0, 0.0, 0.0, 1.0], // Front face
[1.0, 1.0, 0.0, 1.0], // Back face
[0.0, 1.0, 0.0, 1.0], // Top face
[1.0, 0.5, 0.5, 1.0], // Bottom face
[1.0, 0.0, 1.0, 1.0], // Right face
[0.0, 0.0, 1.0, 1.0], // Left face
];
var unpackedColors = []
for (var i in colors) {
var color = colors[i];
for (var j=0; j < 4; j++) {
unpackedColors = unpackedColors.concat(color);
}
}
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(unpackedColors), gl.STATIC_DRAW);
cubeVertexColorBuffer.itemSize = 4;
cubeVertexColorBuffer.numItems = 24;
cubeVertexIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
var cubeVertexIndices = [
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
]
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(cubeVertexIndices), gl.STATIC_DRAW);
cubeVertexIndexBuffer.itemSize = 1;
cubeVertexIndexBuffer.numItems = 36;
The way I like to look at it is that each vertex is not a point in space but rather a bundle of attributes. These generally (but not always) include its location and may include its colour, texture coordinates, etc., etc., etc. A triangle (or line, or other primitive) is defined by specifying a set of vertices, then generating values for each attribute at each pixel by linearly interpolating the per-vertex values.
As Liam says, and as you've realised in your comment, this means that if you want to have a point in space that is used by a vertex for multiple primitives -- for example, the corner of a cube -- with other non-location attributes varying on a per-primitive basis, you need a separate vertex for each combination of attributes.
This is wasteful of memory to some degree -- but the complexity involved in doing it any other way would make things worse, and would require the graphics hardware to do a lot more work unpacking and repacking data. To me, it feels like the waste is comparable to the waste we get by using 32-bit RGBA values for each pixel in our video memory instead of keeping a "palette" lookup table of every colour we want to use and then just storing an index into that per-pixel (which is, of course, what we used to do when RAM was more expensive).
If the color of a set of polygons is the same then a vertex shared by all of the polygons, along with its color, can be defined once and shared by the polygons (using an index).
If the color of the polygons is different then even though the position of a vertex may be common, the color is not, and therefore the vertex as a whole cannot be shared. You will need to define the vertex for each polygon.