基于嵌套循环生成径向/椭圆/跑道形渐变蒙版:自定义宽高与距离归一化
嵌套循环实现椭圆类渐变蒙版的问题排查与修正
核心问题拆解
问题1:自定义椭圆尺寸的Padding匹配异常
原有判断逻辑的失真原因:
- 强转int的截断操作:计算偏移量平方除以半径平方的结果时,直接强转为int会截断所有小数部分。比如x偏移为3、x半轴为15时,计算结果是9/225=0.04,强转int后直接变为0,y方向的计算同理也变为0,最终x+y=0远小于1的判断阈值,大量本该在椭圆外的点会被误判为内部,最后生成的椭圆会收缩在中心区域,四周只剩1单位左右的冗余,完全达不到自定义Padding的预期效果
- 参数定义混淆:椭圆标准方程中的半径参数是半轴长(即从椭圆中心到对应方向边缘的直线距离),不是椭圆的总宽、总高。如果直接把椭圆总宽当半径传入,生成的椭圆尺寸会是预期的两倍,自然会超出遍历范围
- 对称Padding的自动实现逻辑:只要先定好固定遍历的总尺寸,再定义需要的椭圆总宽、总高,取遍历区域的正中心为椭圆中心,Padding会自动对称分布:比如遍历区域宽45、高100,指定椭圆总宽39、总高90时,左右Padding自动为(45-39)/2=3,上下Padding自动为(100-90)/2=5,不需要手动计算预留值
修正后的边界判断函数:
bool EdgeOrInBounds(Vector2 position) { // 全程用浮点数计算,禁止强转int截断小数 float offsetX = position.x - center.x; float offsetY = position.y - center.y; // 这里的radius.x、radius.y直接存储椭圆的半轴长 float ellipseEquationResult = (offsetX * offsetX) / (radius.x * radius.x) + (offsetY * offsetY) / (radius.y * radius.y); return ellipseEquationResult <= 1.0f; }
问题2:椭圆内部距离的0-1归一化失效
原有距离计算的逻辑问题:
- 欧氏直线距离的适配性差:原有的距离函数本身的数学计算逻辑没有错误,但欧氏直线距离只适合正圆的归一化。椭圆是拉伸的形状,不同方向的边缘到中心的距离不一样,同样的直线距离,在短轴方向可能已经碰到椭圆边缘了,在长轴方向可能还在很靠近中心的位置,直接拿这个直线距离除以半径做缩放,会出现渐变畸变,生成的渐变是正圆形的,和拉伸的椭圆形状不匹配
- 无意义的重复计算:要生成顺着椭圆形状的渐变蒙版,根本不需要计算欧氏直线距离,直接用椭圆标准方程的计算结果做归一化即可
正确的归一化逻辑:
椭圆标准方程的计算结果t有固定规律:
- 位置在椭圆中心时,offsetX、offsetY都为0,t=0
- 位置在椭圆边缘时,椭圆方程刚好满足等于1的条件,t=1
- 位置在椭圆外部时,t>1
直接用t值就能实现符合要求的0-1归一化: - 椭圆外的位置返回0
- 椭圆内的位置返回
1 - t,数值会从中心的1平滑线性降到椭圆边缘的0,完全顺着椭圆形状分布,没有畸变
修正后的归一化计算代码:
float GetNormalizedMaskValue(Vector2 position) { float offsetX = position.x - center.x; float offsetY = position.y - center.y; float radiusX = radius.x; float radiusY = radiusY; float t = (offsetX * offsetX) / (radiusX * radiusX) + (offsetY * offsetY) / (radiusY * radiusY); // 椭圆外直接返回0 if (t >= 1.0f) return 0.0f; // 椭圆内返回1-t,实现中心亮、边缘暗的线性渐变 return 1.0f - t; } // 原平方根函数的数学逻辑正确,可正常用于其他直线距离计算场景 float sqrt(float a) { return 1.0 / pow(a, -0.5f); }
修正后的完整遍历逻辑
// 固定遍历的总尺寸 const int mapWidth = 45; const int mapHeight = 100; // 自定义需要的椭圆总宽、总高,调整这两个值即可自动匹配对称Padding const float ellipseTotalWidth = 30; const float ellipseTotalHeight = 70; // 椭圆中心取遍历区域正中心,自动实现四周对称Padding Vector2 center = new Vector2(mapWidth / 2.0f, mapHeight / 2.0f); // 计算椭圆半轴长 Vector2 radius = new Vector2(ellipseTotalWidth / 2.0f, ellipseTotalHeight / 2.0f); // 双循环遍历所有坐标点 for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { Vector2 currentPos = new Vector2(x, y); float maskValue = GetNormalizedMaskValue(currentPos); // 将maskValue存入蒙版数组即可,数值范围为0~1 } }
通用扩展说明
如果后续需要做卵形、跑道形蒙版,只需要替换GetNormalizedMaskValue函数中的形状判断逻辑,归一化的思路完全通用,不需要重新调整循环结构。
内容的提问来源于stack exchange,提问作者dumbashell
相关产品推荐
相关产品推荐

