Major confusion with Ortho projection matrix and Z Near / Z Far values

Started by
8 comments, last by Alberth 3 years, 11 months ago

I'm getting back into OpenGL graphics programming and I'm having trouble with projection matrices when it comes to the z near and z far values. They don't work how I thought they would, so I think I'm doing something wrong.

Starting off simple I have a default out of the box OpenGL 3.3 context. I am drawing a red square using a vertex buffer / index buffer and I am passing a single matrix setup as a orthographic projection to my shaders that acts as the MVP matrix.

The red square does show on the screen. BUT I have some major confusion about the z near and z far values needed in the ortho matrix and their relationship to vertices.

When I create my ortho matrix I set the z near as 0.0f and the z far as 1.0f

mvp.toOrtho(800.0f, 600.0f, 0.0f, 1.0f);

So I expect that my “camera” is at x: 0, y: 0, z: 0 and is looking down the Z axis. Where Z gets more negative as you go further (Right Hand Coord System). I also expect anything outside of a 0.0f to 1.0f range (0.0f to -1.0f z index) will not be visible on the screen,

Now, when my red square's vertices have the Z index set to a value between 0.0f and -1.0f it will show on the screen. If the Z index is a value of -2.0f and lower nothing shows on the screen. Which makes sense, because the direction of the “camera” is looking down the Z axis where further out is a more negative Z value.

However, if I set the Z index value of my vertices to a 1.0f value I still see the red square on the screen. Why is this happening? Wouldn't the red square be “off screen” or behind the camera because it has a Z index of 1.0f?

Just as a FYI, setting the value to 2.0f, so 100% outside of the z near / z far range, the red square does not show.

What am I doing wrong? Or what am I missing?

Implementation of my Ortho Matrix

void Matrix4::toOrtho(float viewWidth, float viewHeight, float zNear, float zFar) {

	//Col 1
	data[0] = 2.0f / viewWidth;
	data[1] = 0.0f;
	data[2] = 0.0f;
	data[3] = 0.0f;

	//Col 2
	data[4] = 0.0f;
	data[5] = 2.0f / viewHeight;
	data[6] = 0.0f;
	data[7] = 0.0f;

	//Col 3
	data[8] = 0.0f;
	data[9] = 0.0f;
	data[10] = 1.0f / (zNear - zFar);
	data[11] = 0;

	//Col 4
	data[12] = 0.0f;
	data[13] = 0.0f;
	data[14] = zNear / (zNear - zFar);
	data[15] = 1.0f;
}

Data in my buffer

//With Index bufffer
float size = 64.0f;
float zIndex = 1.0f;
vertices[0] = 0.0f;
vertices[1] = 0.0f;
vertices[2] = zIndex;

vertices[3] = size;
vertices[4] = 0.0f;
vertices[5] = zIndex;

vertices[6] = 0.0f;
vertices[7] = size;
vertices[8] = zIndex;

vertices[9] = size;
vertices[10] = size;
vertices[11] = zIndex;

indices[0] = 0;
indices[1] = 1;
indices[2] = 2;

indices[3] = 1;
indices[4] = 3;
indices[5] = 2;

Vertex Shader

#version 330 core
layout(location = 0) in vec3 pos;

uniform mat4 MVP;
void main() {
	gl_Position = MVP * vec4(pos.x, pos.y, pos.z, 1.0);
}
Advertisement

No good idea of what you are doing, but standard C++ doesn't do column-oriented matrices like Fortran. Would your results make sense if you view your settings as rows rather than columns?

If so, that would explain the problem.

@Alberth

From what I understand it doesn't matter how the data of a matrix is layed out in memory. I think what you are asking is what happens if I transpose my matrix. So I tried that and the red square is very distorted or not even visible. So I don't think its a data layout issue.

I added my vertex shader code just in case

@noodlebowl : how ortho matrices are built and their use can be read up anywhere. It looks like you're building them not correctly, but i haven't checked any details. Here are too examples that definitely.do.work with opengl, they're right-handed. A matrix is made from an array of 4 vec4s that are an array of 4Ts each. The constructor with the 1 as argument just returns an identity matrix. Ignore the syntax sugar, i just copied them out ithout further ado. Hth a bit.

template<typename T>
inline static mat4_t<T> ortho( const T &left, const T &right, const T &bottom, const T &top ) {
	mat4_t<T> result{ static_cast<T>(1) };
	result[0][0] = static_cast<T>(2) / (right - left);
	result[1][1] = static_cast<T>(2) / (top - bottom);
	result[2][2] = - static_cast<T>(1);
	result[3][0] = - (right + left) / (right - left);
	result[3][1] = - (top + bottom) / (top - bottom);
	return result;
}

template<typename T>
inline static mat4_t<T> ortho( const T &left, const T &right, const T &bottom, const T &atop,
		const T &zNear, const T &zFar ) {
	mat4_t<T> result{ static_cast<T>(1) };
	result[0][0] = static_cast<T>(2) / (right - left);
	result[1][1] = static_cast<T>(2) / (top - bottom);
	result[2][2] = - static_cast<T>(2) / (zFar - zNear);
	result[3][0] = - (right + left) / (right - left);
	result[3][1] = - (top + bottom) / (top - bottom);
	result[3][2] = - (zFar + zNear) / (zFar - zNear);
	return result;
}

I'm not editing my last post because on save the code parts will be messed up with &amps …

@noodlebowl try inverting the near/far parts. And you will want the left and bottom parts as well as the right and top to be able to draw only over a part of a window.

@Green_Baron

So I think my main problem was that I was basing / using projection matrix calculations intended for direct x and not open gl. I'm not really sure what direct x is doing when it comes to their z index mapping, but it definitely doesn't match open gl's.

After swapping out my projection calculations intended for open gl things work as expected. If anyone could explain or has a clue / reason why the direct x version of the formula doesn't work in open gl that would be awesome. I honestly thought it would be ok because of NDC, but maybe those don't apply to z index

For ref the ortho formula I was using:

https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixorthorh

This link would have been handy, sorry about the time you lost:

https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml

Here's my perspective projection code, to compare:

https://github.com/sjhalayka/blind_poker/blob/master/blind%20poker/bp/matrix_utils.mm

noodleBowl said:

@Green_Baron

So I think my main problem was that I was basing / using projection matrix calculations intended for direct x and not open gl. I'm not really sure what direct x is doing when it comes to their z index mapping, but it definitely doesn't match open gl's.

After swapping out my projection calculations intended for open gl things work as expected. If anyone could explain or has a clue / reason why the direct x version of the formula doesn't work in open gl that would be awesome. I honestly thought it would be ok because of NDC, but maybe those don't apply to z index

For ref the ortho formula I was using:

https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixorthorh

@noodlebowl i had a similar problen then i found this :

For programming purposes, OpenGL matrices are 16-value arrays with base vectors laid out contiguously in memory. The translation components occupy the 13th, 14th, and 15th elements of the 16-element matrix, where indices are numbered from 1 to 16 as described in section 2.11.2 of the OpenGL 2.1 Specification.

Column-major versus row-major is purely a notational convention. Note that post-multiplying with column-major matrices produces the same result as pre-multiplying with row-major matrices. The OpenGL Specification and the OpenGL Reference Manual both use column-major notation. You can use any notation, as long as it's clearly stated.

Sadly, the use of column-major format in the spec and blue book has resulted in endless confusion in the OpenGL programming community. Column-major notation suggests that matrices are not laid out in memory as a programmer would expect.

i have resolved this issue doing all the matrices and vectors calculations using standard notation and transposing the final matrices obtained before giving them to opengl .

Hope this help.

Column-major format is standard in the numeric community, probably because Fortran picked that format.

It is just that C++ uses row-major, perhaps because the numeric community indexes a matrix as (row, column), and c++ matrix[row][col] notation then makes it row-major.

This topic is closed to new replies.

Advertisement