To be clear, OpenGL expect clip space coordinates at the end of the vertex shader, not normalized coordinates. Clip space coordinates (what you put in gl_Position) are coordinates that are between -w and w on all axis when they are visible from the camera. The clipping stage will test each point components (x, y, z) against the w component to determine if a point is in the view (-w < x < w; -w < y < w; -w < z < w for the point to be visible). The perspective divide, that is executed between the vertex shader and fragment shader will then divide the coordinates by w to produce the normalized device coordinates (NDC).
That is why if your search on the web "perspective projection matrix", you are likely to come across seemingly very different matrix formulations(!) Even for just OpenGL(!!!)
I remember I found that VERY confusing when I first tried to learn about it...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | //Thinking of vert positions in meters Array<glm::vec4, 6> squareVerts_meters = { glm::vec4{2.0f, 1.0f, 3.0f, 1.0f}, glm::vec4{2.4f, 1.0f, 2.0f, 1.0f}, glm::vec4{4.0f, 1.0f, 3.0f, 1.0f}, glm::vec4{2.0f, -0.5f, 3.0f, 1.0f}, glm::vec4{2.4f, -0.5f, 2.0f, 1.0f}, glm::vec4{4.0f, -0.5f, 3.0f, 1.0f} }; Array<glm::vec4, 6> squareVerts_openGLClipSpace; {//Projection transform f32 fov = glm::radians(90.0f); f32 aspectRatio = 16.0f/9.0f; f32 tanHalfFov = TanR(fov / 2.0f); f32 xScale = 1.0f / (tanHalfFov * aspectRatio); f32 yScale = 1.0f / tanHalfFov; for(i32 vertI{}; vertI < 6; ++vertI) { squareVerts_openGLClipSpace[vertI].x = squareVerts_meters[vertI].x * xScale; squareVerts_openGLClipSpace[vertI].y = squareVerts_meters[vertI].y * yScale; squareVerts_openGLClipSpace[vertI].z = 1.0f; squareVerts_openGLClipSpace[vertI].w = squareVerts_meters[vertI].z; }; }; ...code sending verts to openGL |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | Array<glm::vec4, 6> squareVerts_meters = { glm::vec4{2.0f, 1.0f, 3.0f, 1.0f}, glm::vec4{2.4f, 1.0f, 2.0f, 1.0f}, glm::vec4{4.0f, 1.0f, 3.0f, 1.0f}, glm::vec4{2.0f, -0.5f, 3.0f, 1.0f}, glm::vec4{2.4f, -0.5f, 2.0f, 1.0f}, glm::vec4{4.0f, -0.5f, 3.0f, 1.0f} }; Array<glm::vec4, 6> squareVerts_openGLClipSpace; {//Projection transform f32 focalLength = .8f; for(i32 vertI{}; vertI < 6; ++vertI) { f32 distanceToPoint = focalLength + squareVerts_meters[vertI].z; f32 perspectiveDivide = (distanceToPoint/focalLength); f32 aspectRatio = 16.0f/9.0f; squareVerts_openGLClipSpace[vertI].x = squareVerts_meters[vertI].x; squareVerts_openGLClipSpace[vertI].y = squareVerts_meters[vertI].y * aspectRatio; squareVerts_openGLClipSpace[vertI].z = 1.0f; squareVerts_openGLClipSpace[vertI].w = perspectiveDivide; }; }; ...code sending verts to openGL |
W is correct, only if your coordinate system is left handed, otherwise it should be negated.
Z is incorrect (Not going to repeat what had alrady been explained to death, just go over it again if you need).
W should always jut have the original Z (or it's negative, as mentioned above).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | struct v4 { f32 x, y, z, w; }; void ProjectionTestUsingFOV_InMeters(f32 windowWidth, f32 windowHeight) { //These are assumed to be in camera space, with camera looking down positive z axis (left handed coordinate system) v4 squareVerts_meters[6] = { v4{2.0f, 1.0f, 3.0f, 1.0f}, v4{2.4f, 1.0f, 2.0f, 1.0f}, v4{4.0f, 1.0f, 3.0f, 1.0f}, v4{2.0f, -0.5f, 3.0f, 1.0f}, v4{2.4f, -0.5f, 2.0f, 1.0f}, v4{4.0f, -0.5f, 3.0f, 1.0f} }; //Where we will store clip space coordinates v4 squareVerts_openGLClipSpace[6]; {//Projection transform which will convert x, y and z to clip space as well as store original z value in clip space w f32 fov = glm::radians(90.0f); f32 aspectRatio = 16.0f/9.0f; f32 tanHalfFov = TanR(fov / 2.0f); f32 xScale = 1.0f / (tanHalfFov * aspectRatio); f32 yScale = 1.0f / tanHalfFov; f32 farClip = 100.0f; f32 nearClip = 1.0f; //These equations were calculated assuming camera z values are positive f32 a = (-farClip - nearClip) / (nearClip - farClip); f32 b = (2.0f * farClip * nearClip) / (nearClip - farClip); for(i32 vertI{}; vertI < 6; ++vertI) { squareVerts_openGLClipSpace[vertI].x = squareVerts_meters[vertI].x * xScale; squareVerts_openGLClipSpace[vertI].y = squareVerts_meters[vertI].y * yScale; squareVerts_openGLClipSpace[vertI].z = squareVerts_meters[vertI].z * a + b; squareVerts_openGLClipSpace[vertI].w = squareVerts_meters[vertI].z; }; }; //Send down newly projected verts to openGL GLfloat verts[] = { squareVerts_openGLClipSpace[0].x, squareVerts_openGLClipSpace[0].y, squareVerts_openGLClipSpace[0].z, squareVerts_openGLClipSpace[0].w, 1.0f, 0.0f, 0.0f, squareVerts_openGLClipSpace[1].x, squareVerts_openGLClipSpace[1].y, squareVerts_openGLClipSpace[1].z, squareVerts_openGLClipSpace[1].w, 0.0f, 1.0f, 0.0f, squareVerts_openGLClipSpace[2].x, squareVerts_openGLClipSpace[2].y, squareVerts_openGLClipSpace[2].z, squareVerts_openGLClipSpace[2].w, 1.0f, 0.0f, 0.0f, squareVerts_openGLClipSpace[3].x, squareVerts_openGLClipSpace[3].y, squareVerts_openGLClipSpace[3].z, squareVerts_openGLClipSpace[3].w, 1.0f, 0.0f, 0.0f, squareVerts_openGLClipSpace[4].x, squareVerts_openGLClipSpace[4].y, squareVerts_openGLClipSpace[4].z, squareVerts_openGLClipSpace[4].w, 0.0f, 1.0f, 0.0f, squareVerts_openGLClipSpace[5].x, squareVerts_openGLClipSpace[5].y, squareVerts_openGLClipSpace[5].z, squareVerts_openGLClipSpace[5].w, 1.0f, 0.0f, 0.0f }; GLuint bufferID; glGenBuffers(1, &bufferID); glBindBuffer(GL_ARRAY_BUFFER, bufferID); glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 7, 0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 7, (char*)(sizeof(GLfloat)*3)); GLushort indicies[] = { 0, 1, 3, 3, 1, 4, 1, 2, 4, 2, 5, 4 }; GLuint indexBufferID; glGenBuffers(1, &indexBufferID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW); glDisable(GL_TEXTURE_2D); glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_SHORT, 0); glEnable(GL_TEXTURE_2D); }; |