- Home /
projection matrix in unity
can someone give me more info on the projection matrix used within unity?
Is it the same as that projection matrix from OpenGL?
[ 2n/r-l 0 r+l/r-l 0
0 2n/t-b t+b/t-b 0
0 0 -(f+n)/f-n -2fn/f-n
0 0 -1 0 ]
is also this matrix stored in column major?
2n/r-l, 0, 0 ,0, <- first column
0, 2n/t-b, 0, 0, <- second column
r+l/r-l, t+b/t-b, -(f+n)/f-n, -1, <- third column
0, 0, -2fn/f-n, 0 <- last column
and one last question is around opengl probably: according to the opengl doc, v' = M * v and v is the un-transformed vertex and M is the transform matrix, according to this equation, what is the advantage using column major to store M instead of using row major?
Thanks
Not sure abour the rest, but if I remember, use column/row is just a convention. Doesn't affect anything use row or column (right/left hand).
Answer by Bunny83 · May 25, 2016 at 06:16 PM
Unity's Matrix4x4 struct uses the column-major format. The actual memory layout however is irrelevant. The struct has 16 float member variables:
m00 m01 m02 m03
m10 m11 m12 m13
m20 m21 m22 m23
m30 m31 m32 m33
They are stored in this order:
m00; m10; m20; m30; m01; m11; m21; m31; m02; m12; m22; m32; m03; m13; m23; m33;
| column 0 | column 1 | column 2 | column 3 |
As KazYarnof said in the comment there's no advantage / disadvantage using column / row major layout. However it does affect the way you have to treat the matrix of course. As you might know a matrix multiplication only works when the column count of the first matrix matches the row count of the second. A Vector4 can be treated either as 1x4 (row vector) or as 4x1(column vector).
(1x4) * (4x4) --> (4x1)
(4x4) * (4x1) --> (1x4)
R = M * V // V treated as column vector
( V.x V.y V.z V.w )
| | | |
\|/ \|/ \|/ \|/
m00 m01 m02 m03 --> R.x
m10 m11 m12 m13 --> R.y
m20 m21 m22 m23 --> R.z
m30 m31 m32 m33 --> R.w
----------------------------------------
R = V * M // V treated as row vector.
// this isn't defined in Unity. A vector is always treated as a column vector
// this could be done in Unity by doing "M.transpose * V"
V.x --> m00 m01 m02 m03
V.y --> m10 m11 m12 m13
V.z --> m20 m21 m22 m23
V.w --> m30 m31 m32 m33
| | | |
\|/ \|/ \|/ \|/
( R.x R.y R.z R.w )
Don't get confused by those "drawings". I arranged the vectors in a way you can see how they are multiplied, not according to their actual layout (row / column).
The major difference between Unity and OpenGL is that Unity uses a left-handed coordinate system while OpenGL uses a right.handed system.
X-axis Y-axis z-axis
Unity left-to-right bottom-to-top near-to-far
OpenGL left-to-right bottom-to-top far-to-near
So "forward" in OpenGL is "-z". In Unity forward is "+z". Most hand-rules you might know from math are inverted in Unity. For example the cross product usually uses the right hand rule c = a x b where a is thumb, b is index finger and c is the middle finger. In Unity you would use the same logic, but with the left hand.
However this does not affect the projection matrix as Unity uses the OpenGL convention for the projection matrix. The required z-flipping is done by the cameras worldToCameraMatrix. So the projection matrix should look the same as in OpenGL.
By using my ProjectionMatrixEditorWindow you can view (get) and edit (set) the projection matrix of the main camera inside the editor. This is an editor script so just place it in a folder called "editor". You can open the window via menu (Tools --> ProjectionMatrixEditor).
As you can see the resulting matrix looks similar to the one you've posted. However in Unity L and R (as well as T and B) are always of equal size so the (R+L) term (as well as the (T+B) term) result in "0".
Unity has an example script in the docs how to setup a custom off center perspective matrix.
edit
I forgot to mention that since Unity can run on different platforms using different APIs (DirectX / OpenGL) the actual projection matrix representation inside the GPU might be different from the representation you use in Unity. However you don't have to worry about that since Unity handles this automatically. The only case where it does matter when you directly pass a matrix from your code to a shader. In that case Unity offers the method GL.GetGPUProjectionMatrix which converts the given projection matrix into the right format used by the GPU.
So to sum up how the MVP matrix is composed:
M = transform.localToWorld of the object
V = camera.worldToCameraMatrix
P = GL.GetGPUProjectionMatrix(camera.projectionMatrix)
MVP = P V M
ps: If i messed something up, feel free to leave a comment. ^^
Hi @Bunny83 , thanks for your explanation, it's great. But I still have several questions. I learned about projection matrix from this link. I know Projection matrix = NDC matrix * Perspective matrix. I also attach 3 figures to help me explain myself.
You said in Unity, R and L (T and B) are alwasy of equal size. So (R-L) and (T-B) will be 0 in those deno$$anonymous$$ators part, which makes no sense. So I think those entries should be 0 (the first 2 rows in the projection matrix). Right? Also the F and N are the far and near clipping plane in Unity. Right? Thanks in advance.
![projection matrix][4]
They are of equal size but don't have the same sign. If you read my sentence again the term (R+L) is equal to 0. Therefore (R-L) can't be 0 if R and L are not both 0 as well. "R" is usually positive since it's on the right side and L is negative since it's on the left. If R = 1 and L = -1 the term (R-L) would be "2". That's why when you look at the ortho example of the page you've linked you have a term like 2 / (R - L) which would be just "1".
The values of R, L, T, B actually specify what value you will have at the right, left, top and bottom edge of the screen. So if you would have R = 5 and L = -5 the x range would be "10". So an inco$$anonymous$$g coordinate in camera space can reach from -5 (left edge of the screen) to +5 (right edge of the screen). The projection matrix simply turns the coordinates into clipspace so the values goes from -1 to 1. 2/(R-L) would become 2/10 == 1/5. So an inco$$anonymous$$g coordinage is basically divided by 5 which gives you your desired range of -1 to 1 for inco$$anonymous$$g coordinates of -5 to 5. If you have an off-center projection where R and L are not of equal size you would also have an offset term in the 4th column.
The person who made the page you linked used some terms in a wrong way. NDC (normalized device coordinates) are the coordinates after the perspective divide which is performed by the GPU. The Projection matrix actually outputs homogenous clipspace coordinates which are similar to NDC but before the normalization.
I'm not sure why he actually splits the "projection" into two parts. What he calls the "NDC matrix" is actually an orthographic projection matrix. However when doing a perspective projection any orthographic relation becomes quite pointless. The scaling of the coordinates depends on the frustum angles. Perspective projection does not have a left / right / top / bottom value. Each projection matrix will produce clipspace coordinates.
Thanks so so much. You are so kind. Now, I understand R = -L. I will try it out. Thanks again :)
Your answer
Follow this Question
Related Questions
Can you serialize custom camera projection matrices? 1 Answer
WorldToViewportPoint and ViewportToWorldPoint math 2 Answers
Possible to use an oblique frustum camera and still be able to change the fov? 1 Answer
Custom camera projection matrix of orthographic camera 1 Answer
Generating rays from cameras with custom projection matrices 1 Answer