Part of the Khronos Group
OpenGL.org

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

Results 1 to 7 of 7

Thread: Problem with z axis rotation projected orthografically

  1. #1
    Intern Newbie
    Join Date
    Sep 2016
    Posts
    48

    Problem with z axis rotation projected orthografically

    I've been trying to understand how orthographic projection and matrices work. Not just blindly using GLM. I've managed to build my own poorman's math lib, move an object, scale, and rotate in 2 Axis.

    BUT, when I rotate in the third axis: Z, the image does not rotate the way I expect it to:



    If I DO NOT multiply the model matrix with the projection matrix, then I see the model rotate in the Z axis properly. This issue only happens when I multiply my model matrix with the projection matrix. It's worth noting that every other rotation, translation and scaling works as expected when multiplying with the projection matrix. This visual issue only happens rotating in the Z axis.

    I'm writing everything in JavaScript and using column first matrices. This is my projection matrix:
    Code :
    	    var proj = [
    		    2/ (right - left), 0, 0, 0,
    		    0, 2 / (top - bottom), 0, 0,
    		    0, 0, -2 / (far - near), 0,
    		    -((right + left) / (right - left)), -((top + bottom) / (top - bottom)), -((far + near) / (far - near)), 1
    	   	];

    this is how I'm initializing my projection matrix. The canvas size is 640x480:
    Code :
    var aspectRatio  = Renderer.gl.canvas.clientWidth / Renderer.gl.canvas.clientHeight,
    canvasWidth  = (Renderer.gl.canvas.clientWidth / Renderer.gl.canvas.clientWidth)  * aspectRatio,
    canvasHeight = (Renderer.gl.canvas.clientHeight / Renderer.gl.canvas.clientWidth) * aspectRatio;
     
    mesh.projection  = Mathf.ortho(-canvasWidth, canvasWidth, -canvasHeight, canvasHeight, -1.0, 1.0);

    Translation Matrix:
    Code :
    // Translation works even with orthographic projection. 
    	Mat.translate = function (x, y, z) {
    		var tr = Mat.identity();
    		tr[12] =  x;
    		tr[13] =  y;
    		tr[14] =  z;
     
    		return tr;
    	};

    Scale Matrix:
    Code :
     // Not using right now, but when I do it works perfectly even with ortho projection
    	Mat.scale = function (x, y, z) {
    		var r = Mat.identity();
    		r[0] =  x;
    		r[5] =  y;
    		r[10] = z;
     
    		return r; 
    	};

    Rotation Z Axis.
    Code :
    // Rotates in the Z axis properly IF I don't multiply with orthographic projection.
    	Mat.rotateZ = function (degree) {
    		var r = Mat.Identity(),
    		angle = Mat.degToRad(degree),
    		cos 	= Math.cos(angle),
    		sin 	= Math.sin(angle);
     
    		r[0] =  cos; r[1] =  -sin;
    		r[4] =  sin; r[5] =   cos;
     
    		return r;
    	};

    ..and this is where I compute my model and projection matrices before sending the final matrix to the GPU.
    Code :
    		this.model = Mat.Identity();
     
    		// Set Matrices
    		this.translation 	= Mat.translate(this.x, this.y, this.z);
    		this.scale 			= Mat.scale(this.sx, this.sy, this.sz); // Not using yet
    		this.rotationZ 		= Mat.rotateZ(this.angle);
     
    		// Model Matrix
    		this.final = Mat.mul(this.final, this.translation);
    		this.final = Mat.mul(this.final, this.rotationY); 
     
    		// Projection
    		this.final = Mat.mul(this.final, this.projection);
     
    		Renderer.gl.uniformMatrix4fv(this.modelUniform, false, this.final);
    		Renderer.gl.drawElements(Renderer.gl.TRIANGLES, 6, Renderer.gl.UNSIGNED_SHORT, 0);

    I'm really trying to figure out why I'm unable to rotate on the Z axis properly. Every other transformation works perfectly along with the projection. Thanks in advance, hopefully I've provided enough detail. Oh and if you want to see my matrix multiplication implementation, here it is:

    https://gist.github.com/anonymous/1f...f6f4deeef521ae

  2. #2
    Senior Member OpenGL Guru
    Join Date
    Jun 2013
    Posts
    2,217
    Quote Originally Posted by hashbrown View Post
    I'm writing everything in JavaScript and using column first matrices. This is my projection matrix:
    Code :
    	    var proj = [
    		    2/ (right - left), 0, 0, 0,
    		    0, 2 / (top - bottom), 0, 0,
    		    0, 0, -2 / (far - near), 0,
    		    -((right + left) / (right - left)), -((top + bottom) / (top - bottom)), -((far + near) / (far - near)), 1
    	   	];
    You have the third and fourth columns swapped.

    The mean values (left+right)/2 and (top+bottom)/2 should provide a constant offset in NDC (and thus in window coordinates). Ordinarily, that would mean that they go in the right-hand column (i.e. they're multiplied by W); but for a perspective transformation, W is proportional to Z, so the X and Y offsets also need to be proportional to Z so that projective division results in the Z factor in both numerator and denominator cancelling. If it isn't clear, consider a symmetric perspective projection (where right+left=0 and top+bottom=0) multiplied by a translation on the left (so the transformation is applied to the coordinates after projection):
    Code :
    [ 1  0  0 tx ]   [ sx  0  0  0 ]   [ sx  0  D*tx  0 ]
    [ 0  1  0 ty ] * [  0 sy  0  0 ] = [  0 sy  D*ty  0 ]
    [ 0  0  1  0 ]   [  0  0  C -1 ]   [  0  0  C    -1 ]
    [ 0  0  0  1 ]   [  0  0  D  0 ]   [  0  0  D     0 ]

    Also, you'd typically have the first and second columns scaled by the near value. As it stands, the left, right, top and bottom values correspond to the Z=-1 plane (compared with e.g. glFrustum(), where they're on the near plane).

  3. #3
    Intern Newbie
    Join Date
    Sep 2016
    Posts
    48
    Quote Originally Posted by GClements View Post
    You have the third and fourth columns swapped.

    The mean values (left+right)/2 and (top+bottom)/2 should provide a constant offset in NDC (and thus in window coordinates). Ordinarily, that would mean that they go in the right-hand column (i.e. they're multiplied by W); but for a perspective transformation, W is proportional to Z, so the X and Y offsets also need to be proportional to Z so that projective division results in the Z factor in both numerator and denominator cancelling. If it isn't clear, consider a symmetric perspective projection (where right+left=0 and top+bottom=0) multiplied by a translation on the left (so the transformation is applied to the coordinates after projection):
    Code :
    [ 1  0  0 tx ]   [ sx  0  0  0 ]   [ sx  0  D*tx  0 ]
    [ 0  1  0 ty ] * [  0 sy  0  0 ] = [  0 sy  D*ty  0 ]
    [ 0  0  1  0 ]   [  0  0  C -1 ]   [  0  0  C    -1 ]
    [ 0  0  0  1 ]   [  0  0  D  0 ]   [  0  0  D     0 ]

    Also, you'd typically have the first and second columns scaled by the near value. As it stands, the left, right, top and bottom values correspond to the Z=-1 plane (compared with e.g. glFrustum(), where they're on the near plane).
    GC thank you very much for the answer. When you referred to providing a constant offset in NDC, you mean computing the values in normalized numbers (-1 to 1) right? Which is what I'm passing to my ortho function:

    Code :
    var aspectRatio  = Renderer.gl.canvas.clientWidth / Renderer.gl.canvas.clientHeight,
    canvasWidth  = (Renderer.gl.canvas.clientWidth / Renderer.gl.canvas.clientWidth)  * aspectRatio,
    canvasHeight = (Renderer.gl.canvas.clientHeight / Renderer.gl.canvas.clientWidth) * aspectRatio;
     
    mesh.projection  = Mathf.ortho(-canvasWidth, canvasWidth, -canvasHeight, canvasHeight, -1.0, 1.0);

    I figured I needed to insert normalized values into the ortho matrix since opengl works with normalized numbers. I guess I'm still a little lost not because of your answer, but because I'm still learning. You mentioned swapping the third and forth columns. You mean:



    Just to share my understanding on multiplying against the projection matrix, the whole purpose of these matrices is to build several that will end up solving certain equations. For example, if i multiply my translate and scale matrices, I ended up with: (x * sx) + ... and (y * sy) + .. and so on.

    When it comes to the projection matrix I understand my position xyz values will end up multiplying against the projection matrix c0r0 (column0 row0), c1r1, and c2r2 values...which as far as I know will scale xyz values of the moel. c0r3, c1r3, and c2r3 are offsets for the model's xyz position. That's as far as my understanding goes when it comes to multiplying against the projection matrix.

    Sorry for asking again, I'm just really trying to teach myself this, and a little stuck in this area.
    Last edited by hashbrown; 04-19-2017 at 05:35 PM.

  4. #4
    Member Regular Contributor
    Join Date
    May 2016
    Posts
    344
    the purpose of the "projection matrix" is not to solve any equation (what equation?), is shapes the "viewing volume"

    what is the "viewing volume" ? (some might ask)
    everything you see in the window is 2D (because its not a hologram), it is a quad ranging from [-1; +1] x [-1; +1], but there is (most likely) also a depth buffer that can store depth information of your scene (the invisible 3rd dimesion)

    to make 1 vertex appear on screen, you transform is first:
    -- into "world" space using the "mat4 ModelToWorld"
    -- then into "view" space using the "mat4 WorldToView"
    now you see the "viewing volume" as seen from the "camera", which is just a virtual point in the virtual "world"
    you still cant see any 3rd dimension

    to make things appear 3D, thaat means the further things are away from the "camera" (again, which is just a virtual point in the virtual "world"), the smaller they get on screen, the more centered (on screen) they appear

    that transformation, from "view" space into an actual 3D world, that does the projection matrix
    it transforms the "viewing volume" from "cube" to "frustum", at the top of the frustum is the "camera", looking into that frustum, seeing only whats in that frustum, where zNear & zFar are the clipping planes, fieldofview is the angle describing the frustum, and aspectratio the information needed to "fit" that frustum on screen (width x height)
    Code :
    glm::mat4 projection = glm::perspective(fieldofview, aspectratio, zNear, Zfar);

    take a look at this tutorial: (scroll down to "projection")
    http://www.opengl-tutorial.org/begin...ial-3-matrices

    thats not all (of the "viewing pipeline"):
    Code :
    vec4 result = Projection * WorldToView * ModelToWorld * vertex;
    the result (which has to lie within the cube [-1; +1] x [-1; +1] x [-1; +1]) has to be mapped to the window coordinates (a pixel within the rectangle [0; width] x [0; height])
    that is done by opengl, you just need to call:
    Code :
    glViewport(0, 0, width, height)
    https://learnopengl.com/#!Getting-st...dinate-Systems
    Last edited by john_connor; 04-20-2017 at 03:34 AM.

  5. #5
    Intern Newbie
    Join Date
    Sep 2016
    Posts
    48
    Quote Originally Posted by john_connor View Post
    the purpose of the "projection matrix" is not to solve any equation (what equation?), is shapes the "viewing volume"

    what is the "viewing volume" ? (some might ask)
    everything you see in the window is 2D (because its not a hologram), it is a quad ranging from [-1; +1] x [-1; +1], but there is (most likely) also a depth buffer that can store depth information of your scene (the invisible 3rd dimesion)

    to make 1 vertex appear on screen, you transform is first:
    -- into "world" space using the "mat4 ModelToWorld"
    -- then into "view" space using the "mat4 WorldToView"
    now you see the "viewing volume" as seen from the "camera", which is just a virtual point in the virtual "world"
    you still cant see any 3rd dimension

    to make things appear 3D, thaat means the further things are away from the "camera" (again, which is just a virtual point in the virtual "world"), the smaller they get on screen, the more centered (on screen) they appear

    that transformation, from "view" space into an actual 3D world, that does the projection matrix
    it transforms the "viewing volume" from "cube" to "frustum", at the top of the frustum is the "camera", looking into that frustum, seeing only whats in that frustum, where zNear & zFar are the clipping planes, fieldofview is the angle describing the frustum, and aspectratio the information needed to "fit" that frustum on screen (width x height)
    Code :
    glm::mat4 projection = glm::perspective(fieldofview, aspectratio, zNear, Zfar);

    take a look at this tutorial: (scroll down to "projection")
    http://www.opengl-tutorial.org/begin...ial-3-matrices

    thats not all (of the "viewing pipeline"):
    Code :
    vec4 result = Projection * WorldToView * ModelToWorld * vertex;
    the result (which has to lie within the cube [-1; +1] x [-1; +1] x [-1; +1]) has to be mapped to the window coordinates (a pixel within the rectangle [0; width] x [0; height])
    that is done by opengl, you just need to call:
    Code :
    glViewport(0, 0, width, height)
    https://learnopengl.com/#!Getting-st...dinate-Systems
    Thanks a lot John, I managed to fix it by sending the model and projection matrices as uniforms to the vertex shader and multiplied them there. When I mentioned equations, I was referring to things like (x * (2/R-L)), but I'm probably using the word wrong, sorry about that :P

    I will have to sit down and read more because now every time I rotate in the Z axis and multiply matrices on the cpu, the model does not rotate distorted anymore, but it does move in and out slightly. But if I multiply the model and projection matrices on the GPU, everything works, and I'm using the same Matrices.

    I'll figure out what I'm doing wrong, for now I can keep going. Thanks to both of you.

  6. #6
    Member Regular Contributor
    Join Date
    Jul 2012
    Location
    France
    Posts
    266
    Quote Originally Posted by hashbrown View Post
    I will have to sit down and read more because now every time I rotate in the Z axis and multiply matrices on the cpu, the model does not rotate distorted anymore, but it does move in and out slightly. But if I multiply the model and projection matrices on the GPU, everything works, and I'm using the same Matrices.
    Well, it looks like the multiplication of matrices is wrong. You can try to post it.
    Last edited by Silence; 04-21-2017 at 12:31 AM.

  7. #7
    Intern Newbie
    Join Date
    Sep 2016
    Posts
    48
    Quote Originally Posted by Silence View Post
    Well, it looks like the multiplication of matrices is wrong. You can try to post it.
    Hey Silence, you were right it was definitely the matrix multiplication function. Maybe those were the swapped columns GCElements mentioned in the first answer. The fact that it worked perfectly in the shader with the same matrices gave it away. I went back to the code and found I had this:

    Code :
                    // Don't know why I inverted these, maybe it was late.
    		rm[0] = a[1] * b[0];
    		rm[1] = a[5] * b[1];
    		rm[2] = a[9] * b[2];
    		rm[3] = a[13] * b[3];

    and I changed it to:

    Code :
                    // This works
    		rm[0] = a[0] * b[1];
    		rm[1] = a[1] * b[5];
    		rm[2] = a[2] * b[9];
    		rm[3] = a[3] * b[13];

    Now I'm multiplying the model and projection matrices from the cpu with no problem. Going to work on a camera now. Thanks Silence!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •