2

I've been messing around with OpenGL/ModernGL in Python/Pygame, and after getting my images rendered to the screen, I have been having issues rotating them.

I'm aware of the order that I need to translate, rotate and then scale, which works fine, however my problem stems from the scaling of images being a percentage of the screen resolution. My approach has been to create a shader that spans the screen and then when I need my image to fit, I scale it down to lets say 0.2 (aspect ratio) on the X and 0.1 on the Y.

When the image is rotated by 0 degrees (i.e. horizontal) that 0.1 on the y-axis makes the image 0.1 of the height of the screen, however when it is rotated by 90 degrees (vertical) the same value of 0.1 now scales the image by 0.1 of the width of the screen instead. Due to this, when I rotate a square by 90 degrees it becomes stretched horizontally.

Some attached images showing the problems below. The image used is just a start button from another old project of mine. The button is scaled to (0.5, 0.5) and initially rotated 0, then 90 degrees.

rotated 0 degrees rotated 90 degrees
rotated 0 degrees rotated 90 degrees

I'm not quite sure if there is a common way to fix this or if my approach to rendering images is wrong.

Below is the relevant code:

Inside of vbo.py:

class SurfaceVBO(BaseVBO):
    def __init__(self, app, context):
        super().__init__(app, context)
        self.format = '2f 2f'
        self.attribs = ['in_uv', 'in_position']

    def getVertexData(self):
        #aspect_ratio = self.app.height / self.app.width
        aspect_ratio = 1

        vertices = [((-1 * aspect_ratio),-1),((1 * aspect_ratio),-1),((1 * aspect_ratio),1),((-1 * aspect_ratio),1)]
        indices = [(0,2,3),(0,1,2)]
        
        tex_coord = [(0,0), (1,0), (1,1), (0,1)]
        tex_coord_indices = [(0,2,3),(0,1,2)]

        vertex_data = self.getData(vertices, indices)
        tex_coord_data = self.getData(tex_coord, tex_coord_indices)
        vertex_data = np.hstack([tex_coord_data, vertex_data])

        return vertex_data

Inside of surface.vert:

#version 330 core

in vec2 in_uv;
in vec2 in_position;

out vec2 uv;

// m_ortho is just a orthographic projection matrix spanning (-1, -1), (1, 1) with near 0, and far 10,000

uniform mat4 m_model;
uniform mat4 m_ortho;

void main()
{
    uv = in_uv;
    gl_Position = m_ortho * m_model * vec4(in_position.xy, 0, 1.0);
}

Inside of button.py:

import pygame
import math as m
import glm

class BaseButton:
    def __init__(self, game, vao_name='surface', tex_id='game_menu', pos=(0,0), rot=0, scale=(1,1)):
        self.game = game
        self.pos = pos
        self.rot = glm.radians(rot)
        self.aspect_ratio = self.game.height / self.game.width
        #self.aspect_ratio = 1
        self.original_scale = (scale[0], scale[1])
        self.scale = (self.aspect_ratio * (self.original_scale[0]), (self.original_scale[1]))
        self.m_model = self.getModelMatrix()
        self.vao_name = vao_name
        self.tex_id = tex_id
        self.vao = game.mesh.vao.vaos[vao_name]
        self.program = self.vao.program
        self.image = self.getPygameImage(path=f'images/{tex_id}.png')
        self.rect = self.image.get_rect(center = (((self.pos[0] + 1) / 2) * self.game.width, ((-self.pos[1] + 1) / 2) * self.game.height))
        self.texture = self.get2DTexture(image=self.image)
        self.tint = 1
        self.rend = False

    def update(self): ...

    def getModelMatrix(self):
        # identity matrix
        m_model = glm.mat4()
        # position
        m_model = glm.translate(m_model, (*self.pos, 0))
        #print(1, "\n", m_model)
        # rotation
        m_model = glm.rotate(m_model, self.rot, glm.vec3(0,0,1))
        #print(2, "\n", m_model)
        # scaling
        m_model = glm.scale(m_model, (*self.scale, 1))
        #print(3, "\n", m_model)
        return m_model
    
    def getPygameImage(self, path):
        image = pygame.image.load(path).convert_alpha()
        image = pygame.transform.flip(image, flip_x=False, flip_y=True)
        image = pygame.transform.scale(image, (self.scale[0] * self.game.width, self.scale[1] * self.game.height))
        return image

    def get2DTexture(self, image):
        texture = self.game.context.texture(size=image.get_size(), components=4,data=pygame.image.tostring(image, 'RGBA'))
        return texture
    
    def render(self):
        self.update()
        if self.rend:
            self.vao.render()
            # print(f'DEBUG - rendering {self}')

The code runs at 60 fps, and for every frame it updates each image, and renders each image to the screen.

New contributor
user32678267 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
1
  • You're aware that when stacking affine transform operations, that is literally what you're doing? and that they are executed as though in the reverse order? Commented yesterday

2 Answers 2

3

Don't change the scale after the initial calculation. Once you've created a "frame" for your image, the vertices and tex coords, with the right aspect ratio and dimensions that will fit within the window, you can rotate it as much as you like without needing to change anything.

Sign up to request clarification or add additional context in comments.

Comments

2

Do not scale. Setup properly the viewport e the orthogonal projection matrix, and give vertices values accordingly.

1 Comment

Thank you very much. Changing the orthogonal matrix to account for aspect ratio fixed it. Thank you.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.