Constructing and Handling a MatrixLens

I’m currently attempting to use a MatrixLens to produce an “oblique projection”. And I think that I have this largely working–but there are some aspects that are eluding me, and on which I would like to ask for help…

To start with, my MatrixLens is being set up like so:

        self.matrixLens = MatrixLens()
        row1 = Vec4(0.025, 0, 0, 0)
        row2 = Vec4(0, 0.00625, 0.025, 0)
        row3 = Vec4(0, 0.025, 0, 0)
        row4 = Vec4(0, 0, 0, 1)
        mat = Mat4(row1, row2, row3, row4)
        self.matrixLens.setUserMat(mat)

But with any lens, I think, I find that if I don’t make adjustments to account for the game-window’s aspect-ratio, things may become distorted; the MatrixLens constructed above is no exception. To that end I have a window-event that adjusts the film-size. In short, it gets the new aspect ratio, specifies a base film-size, uses the aspect-ratio to adjust that film-size, and then applies the new values to the current lens. Something like so:

    def windowUpdated(self, graphicsWindow):
        aspectRatio = self.getAspectRatio()

        lens = self.camNode.getLens()

        xSize = 40
        ySize = 40

        if aspectRatio < 1:
            ySize /= aspectRatio
        else:
            xSize *= aspectRatio

        lens.setFilmSize(xSize, ySize)

For an OrthographicLens, this works. However, for my MatrixLens as constructed above, I find that a base film-size of 1 (instead of 40) instead produces the desired effect.

The camera, meanwhile, is placed at (0, -20, 20), and given a pitch-angle of -45. (Which for an OrthographicLens produces a view looking downwards, such that the origin is centred in the render.)

Which brings me to my two questions:

First, the resultant render is offset upwards on the y-axis. Can anyone tell me what I’m doing wrong there, and what I might do about it?

And second, I’ve tried to use the larger base film-size of 40 with my MatrixLens and then adjust the matrix to compensate, but it doesn’t seem to work. For example, the following matrix:

        row1 = Vec4(0.5, 0, 0, 0)
        row2 = Vec4(0, 0.125, 0.5, 0)
        row3 = Vec4(0, 0.5, 0, 0)
        row4 = Vec4(0, 0, 0, 1)

(i.e. scaled up by a similar amount)

Produces no visible output from my test-scene; I don’t know what space it’s trying to render.

So, any thoughts? (And if I’m doing something completely incorrectly, please tell me!)

1 Like

You may just need to update your scaling and skew factors in your 4x4 transformation matrix to account for the updating aspect ratio. Here’s a quick function implementation:

        def update_matrix_lens(event=None):
            matrix_lens = MatrixLens()
            aspect_ratio = self.getAspectRatio()
            base_film_size = 40

            x_size = base_film_size
            y_size = base_film_size

            if aspect_ratio < 1:
                y_size /= aspect_ratio
            else:
                x_size *= aspect_ratio

            row1 = Vec4(0.5 * x_size / base_film_size, 0, 0, 0)
            row2 = Vec4(0, 0.125 * y_size / base_film_size, 0.5, 0)
            row3 = Vec4(0, 0.5, 0, 0)
            row4 = Vec4(0, 0, 0, 1)
            mat = Mat4(row1, row2, row3, row4)

            matrix_lens.setUserMat(mat)
            matrix_lens.setFilmSize(x_size, y_size)
            # test the matrix lens on base.cam
            self.cam.node().setLens(matrix_lens)

        self.accept("window-event", update_matrix_lens)

The main change here is the normalization I added to row1 and row2, using base_film_size as the base unit. Now, this isn’t something I tend to do every day, so I may be mistaken in some way.

Come to think of it, you might also add a “shear factor” to row2 like so to modify the “obliqueness”:

        def update_matrix_lens(event=None):
            matrix_lens = MatrixLens()
            aspect_ratio = self.getAspectRatio()
            base_film_size = 40
            shear_factor = 0.5

            x_size = base_film_size
            y_size = base_film_size

            if aspect_ratio < 1:
                y_size /= aspect_ratio
            else:
                x_size *= aspect_ratio

            row1 = Vec4(0.5 * x_size / base_film_size, 0, 0, 0)
            row2 = Vec4(shear_factor, 0.125 * y_size / base_film_size, 0.5, 0)
            row3 = Vec4(0, 0.5, 0, 0)
            row4 = Vec4(0, 0, 0, 1)
            mat = Mat4(row1, row2, row3, row4)

            matrix_lens.setUserMat(mat)
            matrix_lens.setFilmSize(x_size, y_size)
            # test the matrix lens on base.cam
            self.cam.node().setLens(matrix_lens)

        self.accept("window-event", update_matrix_lens)

Hm, I’m afraid that it doesn’t seem to work: the scene still just disappears when the lens is so updated. :/

Here is my modified “windowUpdated” method:

def windowUpdated(self, graphicsWindow):
        aspectRatio = self.getAspectRatio()

        lens = self.camNode.getLens()

        baseFilmSize = 40

        xSize = baseFilmSize
        ySize = baseFilmSize

        if aspectRatio < 1:
            ySize /= aspectRatio
        else:
            xSize *= aspectRatio

        if isinstance(lens, MatrixLens):
            row1 = Vec4(0.5 * xSize / baseFilmSize, 0, 0, 0)
            row2 = Vec4(0, 0.125 * ySize / baseFilmSize, 0.5, 0)
            row3 = Vec4(0, 0.5, 0, 0)
            row4 = Vec4(0, 0, 0, 1)
            mat = Mat4(row1, row2, row3, row4)
            lens.setUserMat(mat)

        lens.setFilmSize(xSize, ySize)

(Although I’ve tried a few variations of this, including making the MatrixLens anew and assigning it to the camera-node, as in your version above, I believe.)

[edit]

Okay, I think that I’ve made progress:

Stepping away from the matter of using the intended film-size, I’ve done two things:

First, I’ve removed my placement and rotation of the camera, leaving it at its default values. I daresay that the view-transformation was throwing off my matrix, and I’m not currently clear on what else to do about it.

(But does this potentially incur culling issues…?)

And second, I’ve changed the matrix to the following:

        row1 = Vec4(0.025, 0, 0, 0)
        row2 = Vec4(0, 0.025, 0.025, 0)
        row3 = Vec4(0, 0.0125, 0, 0)
        row4 = Vec4(0, 0, 0, 1)

Now, this doesn’t quite centre the view–almost, but not quite. It seems that the y-value in row 3 controls both the tilt of the view and the y-offset of the rendering, which is a bit frustrating…