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区域匹配错误。
解决方案
核心思路
- 计算旋转后物体的实际包围盒:旋转会让物体的边界扩大,必须用这个包围盒判断是否和视口相交。
- 对视口顶点做逆旋转变换:将屏幕上的视口区域映射回未旋转的纹理坐标系,从而得到准确的Src矩形范围。
- 确保旋转中心正确: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
相关产品推荐
相关产品推荐

