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

SDL中渲染部分离屏的旋转纹理:如何确定正确的Src矩形?

旋转纹理超出屏幕时的正确Src矩形计算问题

我尝试渲染部分超出屏幕的旋转纹理,但无法确定该旋转纹理的正确Src矩形。适用于非旋转纹理的常规方法如下:

void CollisionObject::renderObject(SDL_Rect viewport)
{
    //dstrect is an object position + object max width / height

    if (not SDL_HasIntersection(&viewport, &dstrect))
        return;

    SDL_Rect src = physics::normalizedIntersection(dstrect, viewport); 
    SDL_Rect dest = physics::normalizedIntersection(viewport, dstrect);

    SDL_RenderCopyEx(gRenderer, texture, &src, &dest, body.getRotation(), NULL, SDL_FLIP_NONE);
}

//helper function
SDL_Rect normalizedIntersection(SDL_Rect a, SDL_Rect b)
{
    int x = a.x;
    int y = a.y;

    a.x = 0;
    a.y = 0;
    b.x -= x;
    b.y -= y;

    SDL_Rect res;
    SDL_IntersectRect(&a, &b, &res);
    return res;
}

但当纹理旋转时,该方法明显失效,会错误裁剪纹理的对应部分。(问题截图说明:物体顶部超出屏幕时,相交矩形y>0,错误裁剪了纹理上部,非旋转时该情况正常)


问题原因

原方法依赖轴对齐矩形的交集计算,但纹理旋转后,屏幕上的可见区域与原物体矩形的对应关系不再是轴对齐的,直接用原矩形相交会导致Src区域匹配错误。

解决方案

核心思路

  1. 计算旋转后物体的实际包围盒:旋转会让物体的边界扩大,必须用这个包围盒判断是否和视口相交。
  2. 对视口顶点做逆旋转变换:将屏幕上的视口区域映射回未旋转的纹理坐标系,从而得到准确的Src矩形范围。
  3. 确保旋转中心正确:SDL_RenderCopyEx的旋转中心默认是dest矩形左上角,通常需要改为物体自身中心。

具体实现代码

1. 顶点旋转工具函数

#include <cmath>
#include <algorithm>

// 旋转单个点,center为旋转中心,angle为旋转角度(弧度)
SDL_Point rotatePoint(SDL_Point point, SDL_Point center, double angle) {
    double cosA = cos(angle);
    double sinA = sin(angle);
    
    // 将点平移到以旋转中心为原点的坐标系
    int xRel = point.x - center.x;
    int yRel = point.y - center.y;
    
    // 旋转后平移回原坐标系
    SDL_Point rotated;
    rotated.x = static_cast<int>(center.x + (xRel * cosA - yRel * sinA));
    rotated.y = static_cast<int>(center.y + (xRel * sinA + yRel * cosA));
    return rotated;
}

2. 计算旋转后的物体包围盒

// 输入原矩形、旋转中心、旋转角度(弧度),返回旋转后的轴对齐包围盒
SDL_Rect getRotatedBounds(SDL_Rect rect, SDL_Point center, double angle) {
    // 原矩形的四个顶点
    SDL_Point vertices[4] = {
        {rect.x, rect.y},
        {rect.x + rect.w, rect.y},
        {rect.x + rect.w, rect.y + rect.h},
        {rect.x, rect.y + rect.h}
    };
    
    // 旋转所有顶点
    for (int i = 0; i < 4; ++i) {
        vertices[i] = rotatePoint(vertices[i], center, angle);
    }
    
    // 计算包围盒的最小/最大坐标
    int minX = vertices[0].x, maxX = vertices[0].x;
    int minY = vertices[0].y, maxY = vertices[0].y;
    for (int i = 1; i < 4; ++i) {
        minX = std::min(minX, vertices[i].x);
        maxX = std::max(maxX, vertices[i].x);
        minY = std::min(minY, vertices[i].y);
        maxY = std::max(maxY, vertices[i].y);
    }
    
    return {minX, minY, maxX - minX, maxY - minY};
}

3. 修改后的渲染函数

void CollisionObject::renderObject(SDL_Rect viewport) {
    // 物体的旋转中心(以自身中心为例)
    SDL_Point objRotCenter = {dstrect.x + dstrect.w / 2, dstrect.y + dstrect.h / 2};
    double rotRad = body.getRotation() * M_PI / 180.0; // 角度转弧度

    // 步骤1:计算旋转后的物体包围盒
    SDL_Rect rotatedBounds = getRotatedBounds(dstrect, objRotCenter, rotRad);
    
    // 步骤2:判断是否与视口相交,无交集则直接返回
    if (!SDL_HasIntersection(&viewport, &rotatedBounds)) {
        return;
    }

    // 步骤3:获取视口的四个顶点
    SDL_Point viewportVertices[4] = {
        {viewport.x, viewport.y},
        {viewport.x + viewport.w, viewport.y},
        {viewport.x + viewport.w, viewport.y + viewport.h},
        {viewport.x, viewport.y + viewport.h}
    };

    // 步骤4:将视口顶点逆旋转,转换到物体的局部纹理坐标系
    SDL_Point localTexPoints[4];
    for (int i = 0; i < 4; ++i) {
        // 逆旋转(角度取反)
        SDL_Point invRotated = rotatePoint(viewportVertices[i], objRotCenter, -rotRad);
        // 转换为相对于原物体矩形左上角的坐标(纹理局部坐标)
        localTexPoints[i].x = invRotated.x - dstrect.x;
        localTexPoints[i].y = invRotated.y - dstrect.y;
    }

    // 步骤5:计算纹理局部坐标系中的有效范围,同时裁剪到纹理尺寸内
    int minSrcX = std::max(0, std::min({localTexPoints[0].x, localTexPoints[1].x, localTexPoints[2].x, localTexPoints[3].x}));
    int maxSrcX = std::min(dstrect.w, std::max({localTexPoints[0].x, localTexPoints[1].x, localTexPoints[2].x, localTexPoints[3].x}));
    int minSrcY = std::max(0, std::min({localTexPoints[0].y, localTexPoints[1].y, localTexPoints[2].y, localTexPoints[3].y}));
    int maxSrcY = std::min(dstrect.h, std::max({localTexPoints[0].y, localTexPoints[1].y, localTexPoints[2].y, localTexPoints[3].y}));

    // 若有效范围为空,直接返回
    if (maxSrcX <= minSrcX || maxSrcY <= minSrcY) {
        return;
    }

    SDL_Rect src = {minSrcX, minSrcY, maxSrcX - minSrcX, maxSrcY - minSrcY};

    // 步骤6:计算视口与旋转包围盒的交集作为目标渲染区域
    SDL_Rect destIntersect;
    SDL_IntersectRect(&viewport, &rotatedBounds, &destIntersect);

    // 步骤7:计算渲染时的旋转中心(相对于目标区域左上角)
    SDL_Point renderRotCenter = {objRotCenter.x - destIntersect.x, objRotCenter.y - destIntersect.y};

    // 执行渲染
    SDL_RenderCopyEx(gRenderer, texture, &src, &destIntersect, body.getRotation(), &renderRotCenter, SDL_FLIP_NONE);
}

关键注意点

  • 旋转中心必须明确:使用物体自身中心作为旋转中心,避免SDL默认的左上角旋转导致的位置偏移。
  • 逆旋转的必要性:只有将屏幕视口区域逆变换回未旋转的纹理坐标系,才能准确找到需要渲染的纹理片段。
  • 边界裁剪:计算Src矩形时要确保不超出纹理的实际尺寸,避免SDL渲染错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 19:45:55