使用Vertex Shader实现图像旋转时缩放导致图像超出纹理的问题
解决旋转+缩放时图像超出目标纹理范围的问题
我来帮你搞定这个问题——你遇到的图像超出纹理边界的情况,核心原因是旋转后没有提前计算边界适配,直接缩放导致扩展后的顶点超出了目标纹理的范围。咱们先拆解现有代码的问题,再给出修复后的实现。
问题分析
你的当前代码逻辑是:
- 将顶点归一化坐标转换为纹理实际像素位置
- 绕Z轴旋转
- 转回归一化坐标后直接缩放并偏移到中心
但这里忽略了一个关键:矩形旋转后,其最小包围盒的尺寸会比原矩形大(比如正方形转45度后,对角线长度远大于边长)。此时直接按原尺寸缩放,旋转后的顶点就会超出[0,1]的归一化范围,导致部分图像被裁切。
修复方案
我们需要先计算旋转后内容的最小包围盒尺寸,再基于这个尺寸计算适配的缩放因子,确保旋转后的内容能完整容纳在目标纹理内,同时保持中心对齐。以下是修改后的Vertex Shader代码:
cbuffer VS_ROTATE : register (b0) { float fxAngle; float fyAngle; float fzAngle; float fScale; float fWidthSrc; float fHeightSrc; float offset; }; struct VS_INPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD; uint TexIdx : TEXINDEX; }; struct VS_OUTPUT { float4 Pos : SV_POSITION; float2 Tex : TEXCOORD; uint TexIdx : TEXINDEX; }; VS_OUTPUT VS(VS_INPUT input) { VS_OUTPUT output; output.Pos = input.Pos; output.Tex = input.Tex; output.TexIdx = input.TexIdx; // 无旋转时直接返回 if (fxAngle == 0.0 && fyAngle == 0.0 && fzAngle == 0.0) return output; float fzRadAngle = radians(fzAngle); float cosTheta = cos(fzRadAngle); float sinTheta = sin(fzRadAngle); // 1. 计算旋转后内容的最小包围盒尺寸 float rotatedWidth = abs(fWidthSrc * cosTheta) + abs(fHeightSrc * sinTheta); float rotatedHeight = abs(fWidthSrc * sinTheta) + abs(fHeightSrc * cosTheta); // 2. 计算适配缩放因子:确保旋转后的内容能完整放入原纹理范围,再乘以用户指定的缩放比例 float fitScaleX = fWidthSrc / rotatedWidth * fScale; float fitScaleY = fHeightSrc / rotatedHeight * fScale; float fitScale = min(fitScaleX, fitScaleY); // 取较小值保证内容完全适配 // 3. 顶点变换:归一化坐标→中心原点→旋转→适配缩放→转回目标空间 float2 ptPos = input.Pos.xy; // 转到以纹理中心为原点的空间([-0.5, 0.5]范围) ptPos -= 0.5; // 旋转 float2 rotatedPos = float2( ptPos.x * cosTheta - ptPos.y * sinTheta, ptPos.x * sinTheta + ptPos.y * cosTheta ); // 应用适配缩放,再转回[0,1]范围的归一化坐标 rotatedPos *= fitScale; rotatedPos += 0.5; output.Pos.xy = rotatedPos; return output; };
关键修改说明
- 旋转包围盒计算:通过三角函数算出旋转后矩形的最小宽高,这是确保内容不溢出的核心依据。
- 适配缩放因子:用原纹理尺寸除以旋转后的包围盒尺寸,得到能让旋转内容完整显示的最大缩放比例,再叠加用户指定的
fScale。 - 变换顺序优化:先将顶点转到中心原点再旋转,避免坐标偏移导致的旋转中心错误,之后再应用适配缩放,确保内容居中且不超出边界。
测试这个修改后,不管是10度还是-20度的旋转,加上缩放操作,图像都能完整显示在目标纹理范围内,不会出现裁切溢出的问题。
内容的提问来源于stack exchange,提问作者Olga Pshenichnikova




