You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenGL/Pygame中旋转图像时如何保持缩放比例?

解决旋转图像拉伸问题的方案

核心问题出在屏幕宽高比的适配逻辑错误:你当前在模型矩阵的缩放阶段处理宽高比,导致旋转后X/Y轴对应的屏幕尺寸映射关系混乱,最终引发拉伸。正确的做法是将宽高比适配转移到正交投影矩阵中,让模型矩阵的缩放保持“无畸变”状态。

具体修改步骤

1. 重构正交投影矩阵生成逻辑

修改生成m_ortho的代码(假设在你的Game类中),让正交空间的X/Y单位对应相同的屏幕像素数:

# Game类中初始化正交投影矩阵
self.width = pygame.display.get_surface().get_width()
self.height = pygame.display.get_surface().get_height()
self.aspect_ratio = self.width / self.height

# 根据宽高比调整正交空间范围,保证X/Y单位像素一致
if self.aspect_ratio >= 1:
    self.ortho_left = -self.aspect_ratio
    self.ortho_right = self.aspect_ratio
    self.ortho_bottom = -1
    self.ortho_top = 1
else:
    self.ortho_left = -1
    self.ortho_right = 1
    self.ortho_bottom = -1 / self.aspect_ratio
    self.ortho_top = 1 / self.aspect_ratio

self.m_ortho = glm.ortho(self.ortho_left, self.ortho_right, self.ortho_bottom, self.ortho_top, 0, 10000)

2. 修改BaseButton类的缩放与坐标计算

移除模型缩放中的宽高比适配,新增屏幕尺寸和坐标的计算逻辑:

import pygame
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.original_scale = scale
        # 不再在缩放中叠加宽高比,正交投影已处理适配
        self.scale = self.original_scale
        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.screen_width, self.screen_height = self.calculate_screen_size()
        self.image = self.get_pygame_image(path=f'images/{tex_id}.png')
        
        # 计算按钮在屏幕上的中心坐标
        self.rect_center = self.calculate_screen_pos()
        self.rect = self.image.get_rect(center=self.rect_center)
        
        self.texture = self.get_2d_texture(image=self.image)
        self.tint = 1
        self.rend = False

    def calculate_screen_size(self):
        """根据正交空间缩放计算图像的屏幕像素尺寸"""
        aspect = self.game.width / self.game.height
        if aspect >= 1:
            screen_width = self.scale[0] * self.game.height
            screen_height = self.scale[1] * self.game.height
        else:
            screen_width = self.scale[0] * self.game.width
            screen_height = self.scale[1] * self.game.width
        return int(screen_width), int(screen_height)

    def calculate_screen_pos(self):
        """将正交空间坐标转换为屏幕坐标(适配pygame的Y轴方向)"""
        game = self.game
        x = ((self.pos[0] - game.ortho_left) / (game.ortho_right - game.ortho_left)) * game.width
        y = ((self.pos[1] - game.ortho_bottom) / (game.ortho_top - game.ortho_bottom)) * game.height
        # pygame的Y轴从顶部开始,需要反转
        y = game.height - y
        return (x, y)

    def getModelMatrix(self):
        m_model = glm.mat4()
        m_model = glm.translate(m_model, (*self.pos, 0))
        m_model = glm.rotate(m_model, self.rot, glm.vec3(0,0,1))
        m_model = glm.scale(m_model, (*self.scale, 1))
        return m_model
    
    def get_pygame_image(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.screen_width, self.screen_height))
        return image

    def get_2d_texture(self, image):
        texture = self.game.context.texture(size=image.get_size(), components=4, data=pygame.image.tostring(image, 'RGBA'))
        return texture
    
    def update(self):
        pass
    
    def render(self):
        self.update()
        if self.rend:
            self.vao.render()

3. 保留VBO的单位正方形设置

你的SurfaceVBO代码无需修改,保持aspect_ratio=1即可,因为正交投影已经处理了屏幕适配:

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 = 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

原理说明

修改后,正交投影矩阵会根据屏幕宽高比调整空间范围,使得正交空间中的1单位X和1单位Y对应相同的屏幕像素数。模型矩阵的缩放仅负责控制图像的相对大小,旋转时不会因为轴映射关系变化导致拉伸,彻底解决了旋转后的畸变问题。

内容的提问来源于stack exchange,提问作者user32678267

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 20:07:30