程序化物件放置(Procedural Placement)

在开放世界游戏中, 很多小物件(物品箱子, 杂物, 草,木桶)的摆放是很耗费工作量的, 靠人工手动摆放是不现实的,为了节省工作量, 工程师探索了 程序化摆放物件的各种算法.

在游戏中物件在世界中的位置可以用一个点来表示, 所有物件的分布就构成了一张密度图(density map)

目前而言,物件密度图生成算法有三种(不把噪声图考虑其中), 统一随机(Uniofrm Random), 抖动格子(Jitter Grid), 泊松硬盘采样(Possion Disk Sample)

统一随机(Uniofrm Random)

统一随机算法很简单, 思路: 在密度图的宽度和高度中进行进行就得到采样点.

代码实现

void GenerateUniformRandom(int width, int height, int newPointsCount, vector<Point2D>& sampleList)
{sampleList.clear();for (int i = 0; i < newPointsCount; ++i){sampleList.push_back(Point2D(Randf(width), Randf(height)));}
}

结果显示

width = 128, height = 128, sampleCount = 1400

抖动格子(Jitter Grid)

抖动格子是先按照固定的格子大小将整个图片进行划分, 划为N x M的格子, 然后在随机在不同的格子内进行随机点的生成

格子划分

格子内随机

代码实现

void GenerateJitterGrid(int width, int height, int newPointsCount, float gridSize, vector<Point2D>& sampleList)
{sampleList.clear();int GridNumX = (float)width / gridSize;int GridNumY = (float)height / gridSize;TRandomVector<Point2DInt> sampleGrids;for (int x = 0; x < GridNumX; ++x){for (int y = 0; y < GridNumY; ++y){sampleGrids.push_back(Point2DInt(x, y));}}int num = min(sampleGrids.Size(), newPointsCount);while (sampleList.size() < num){Point2DInt gridPos = sampleGrids.pop();float LeftTopX = (float)gridPos.x * gridSize;float LeftTopY = (float)gridPos.y * gridSize;float RightBottomX = (float)(gridPos.x + 1) * gridSize;float RightBottomY = (float)(gridPos.y + 1) * gridSize;float randomX = RandRangef(LeftTopX, RightBottomX);float randomY = RandRangef(LeftTopY, RightBottomY);sampleList.push_back(Point2D(randomX, randomY));}
}

结果显示

width = 128, height = 128, gridSize = 6.0, sampleCount = 1400

泊松硬盘采样(Possion Disk Sample)

泊松硬采样的算法实现有多种, 目前介绍一种比较容易实现的: 从整个图中随机第一个点, 然后从这个点开始进行多次随机发散 “距离和角度随机偏移”,得到一批采样点, 然后这批采样点加入随机数组中进行递归的随机发散行为, 最终生成最够的采样

随机第一个点,加入随机数组中

 TRandomVector<Point2D> processList;Point2D firstPoint = Point2D(Randf(width), Randf(height));processList.push_back(firstPoint);sampleList.push_back(firstPoint);

从初始点进行角度和距离的随机偏移, 偏移距离 = random(minDistance, random(1.0,2.0) * minDistance),   偏移角度 = random(0, 2 * PI)

Point2D point = processList.pop();for (int i = 0; i < newPointsCount; ++i)
{Point2D newPoint = GenerateRandomPointAround(point, minDistance);if (IsInRect(newPoint, width, height) && !IsNeighbourhood(grid, newPoint, minDistance, cellSize, 2)){processList.push_back(newPoint);sampleList.push_back(newPoint);grid.Push(newPoint);}
}Point2D GenerateRandomPointAround(const Point2D& point, float minDistance)
{float r1 = Randf(1.0);float r2 = Randf(1.0);//random radiusfloat radius = minDistance * (r1 + 1.0);//random anglefloat angle = 2.0 * PI * r2;float newX = point.x + radius * cos(angle);float newY = point.y + radius * sin(angle);return Point2D(newX, newY);
}

注意新生成的点还得判断其和周围已经生成的采样点的距离都大于minDisatnce

bool IsNeighbourhood(Grid2D& grid, const Point2D& point, float minDistance, float cellSize, int NearSeachCellSize = 2)
{Point2DInt gridPoint = ImageToGrid(point, cellSize);vector<Point2DInt> aroundCells = SquareAroundPoint(grid, gridPoint, 2);for (auto& cell : aroundCells){const vector<Point2D> &cellPoints = grid.data[cell];for (auto& otherPoint : cellPoints){if (point.Distance(otherPoint) < minDistance)return true;}}return false;
}

算法示例图

结果显示

width = 128, height = 128, minDisance = 3.0,  sampleCount = 1400

改进泊松硬盘采样------更合理的偏移距离

在游戏中,同个物件在不同的区域分布密度是不一样的,比如说铁矿石出现在城市的分布密度小,在野外出现的分布密度大。换句来说假如铁矿石的布置用的是泊松硬盘采样,则在城市的区域随机偏移距离总体比较大,野外区域的偏移距离总体比较小。 因此随机发散的距离应该是灵活变化,而非固定的 偏移距离 = random(minDistance, random(1.0,2.0) * minDistance),

更好的策略:规定物件的最大距离maxDistance和最小距离minDisatnce,根据密度图像素值value,  在生成新的采样点的时候计算动态的最小距离:newMinDistance = minDistance + (maxDistance - minDistance) * (1.0 - value)

新的生成点算法


Point2D GenerateRandomPointAroundDensity(const Point2D& point, float minDistance, float maxDistance, float cellSize, FIBITMAP* densityMap, float& newMinDistance)
{Point2DInt cellPos = ImageToGrid(point, cellSize);RGBQUAD color;FreeImage_GetPixelColor(densityMap, int(point.x), int(point.y), &color);float r1 = Randf(1.0);float r2 = Randf(1.0);//random radiusnewMinDistance = minDistance + (maxDistance - minDistance) * (1.0 - (float)color.rgbRed / 255.0f);float radius = (r1 + 1.0) * newMinDistance;//random anglefloat angle = 2.0 * PI * r2;float newX = point.x + radius * cos(angle);float newY = point.y + radius * sin(angle);return Point2D(newX, newY);
}
void GeneratePoissonRandomDensity(int width, int height, float minDistance, float maxDistance, int newPointsCount, vector<Point2D>& sampleList, FIBITMAP* densityMap)
{sampleList.clear();float cellSize = maxDistance / sqrt(2.0);Grid2D grid = Grid2D(ceilf((float)width / cellSize), ceilf((float)height / cellSize), cellSize);TRandomVector<Point2D> processList;Point2D firstPoint = Point2D(Randf(width), Randf(height));processList.push_back(firstPoint);sampleList.push_back(firstPoint);while (!processList.isEmpty() && sampleList.size() < newPointsCount){Point2D point = processList.pop();for (int i = 0; i < newPointsCount; ++i){float newMinDistance;Point2D newPoint = GenerateRandomPointAroundDensity(point, minDistance, maxDistance, cellSize, densityMap, newMinDistance);if (IsInRect(newPoint, width, height) && !IsNeighbourhood(grid, newPoint, newMinDistance, cellSize, 4)){processList.push_back(newPoint);sampleList.push_back(newPoint);grid.Push(newPoint);}}}
}

密度分布图 

结果显示

width = 128, height = 128, minDisance = 3.0, minDisance = 8.0,  sampleCount = 1400

项目代码链接

https://download.csdn.net/download/qq_29523119/18985787

资料参考

【1】http://devmag.org.za/2009/05/03/poisson-disk-sampling/

程序化物件放置(procedural placement)之泊松硬盘采样(poisson disk sampling)相关推荐

  1. 泊松圆盘采样(Poisson Disk Sampling)代码实现

    欢迎关注更多精彩 关注我,学习常用算法与数据结构,一题多解,降维打击. 参考资料 https://www.jianshu.com/p/4556ccad58d9 https://blog.csdn.ne ...

  2. 【项目记录】过程内容生成(PCG)与快速泊松碟采样算法实现Unity中的物件摆放

    背景   在当今游戏开发过程中,创建一个内容丰富的虚拟世界一直是一个十分费时的工作.游戏制作者们希望在更短的时间内在游戏中增加更加多样的内容,过程内容生成技术为有限时间内制作复杂的虚拟世界提供了一种解 ...

  3. 【GDC翻译】“地平线零之曙光”中基于GPU的程序化实时放置系统

    原视频:GDC Vault - GPU-Based Run-Time Procedural Placement in 'Horizon: Zero Dawn' PDF:https://www.gdcv ...

  4. R语言构建xgboost模型:使用xgboost构建泊松回归(poisson regression)模型

    R语言构建xgboost模型:使用xgboost构建泊松回归(poisson regression)模型 目录 R语言构建xgboost模型:使用xgboost构建泊松回归(poisson regre ...

  5. Python使用sklearn构建广义线性模型:泊松回归(Poisson regression)实战

    Python使用sklearn构建广义线性模型:泊松回归(Poisson regression)实战 目录 Python使用sklearn构建广义线性模型:泊松回归(Poisson regressio ...

  6. R语言广义线性模型泊松回归(Poisson Regression)模型

    R语言广义线性模型泊松回归(Poisson Regression)模型 试想一下,你现在就站在一个人流密集的马路旁,打算收集闯红灯的人群情况(?).首先,利用秒表和计数器,一分钟过去了,有5个人闯红灯 ...

  7. 泊松回归(Poisson regression)、COX回归、分类器变回归器、回归算法注意事项、多重共线性问题

    泊松回归(Poisson regression).COX回归.分类器变回归器.回归算法注意事项.多重共线性问题 目录

  8. python、turtle实现泊松盘采样

    泊松盘采样算法参考 https://www.jasondavies.com/poisson-disc/ 算法核心要点 以上一次采样点为中心,随机取圆环内点进行采样 采样时需要判断inner范围内是否已 ...

  9. 内存缓存(from memory cache)和硬盘缓存(from disk cache) 的区别

    引言 ?命中强制缓存时,资源会显示 from memory cache or from disk cache 两者的区别 内存缓存(from memory cache) 内存缓存具有两个特点,分别是快 ...

最新文章

  1. Python:数据提取之JSON与JsonPATH
  2. HDU2925(约瑟夫环问题)
  3. android 生成签名命令
  4. 5G NGC — UDM 统一数据管理
  5. Codeforces 235C
  6. 更深入地了解Java 8 Date and Time API
  7. 【本地差分隐私与随机响应代码实现】差分隐私代码实现系列(十三)
  8. Silverlight4 ColorPicker控件
  9. redis 端口_「建议收藏」手把手教你搭建redis集群
  10. java实现浏览器_利用Java实现网页浏览器
  11. Google永久允许使用Flash 100%详细简单+解决
  12. Metasploit之——社会工程学工具包
  13. gift to me by myself on 2012's new year
  14. Vue + Spring Boot 项目实战
  15. linux 移动硬盘 mac,Macbook pro使用原生EFI在移动硬盘安装Windows+Fedora双系统
  16. AdxMenu真的不错!我写了个中文的使用说明如下,希望大家用得着
  17. 边缘计算七大核心技术
  18. Web服务器群集——公有CA构建阿里云服务器HTTPS
  19. iPhone出现绿屏问题怎么修复?可以尝试这些解决方案
  20. 知乎9.7万人围观热帖:“同事被富婆包养了,我哪里不如他?”

热门文章

  1. 营销思维篇:透漏22个我赚钱的小秘密
  2. 程序猿生存定律-六个程序猿的故事(2)
  3. Mac 不能写入移动硬盘的解决方案
  4. matlab中区分fplot和plot,以及作图时sin(1./x)需要点除而不是除
  5. 下载了Mysql for excel却不在excel里显示
  6. 利用CMS漏洞渗透并获取某服务器权限
  7. 基于FPGA的DDS 信号发生器(三)
  8. Android音乐播放器开发(3)—注册
  9. ueditor 图片上传 java_Spring+Vue整合UEditor富文本实现图片附件上传的方法
  10. Windows11 安装教程(ultraiso制作启动盘)