2021SC@SDUSC

文章目录

  • UV 展开
    • 创建割缝
    • UV展开的扭曲情况
  • UvUnwrap

UV 展开

参数曲面的参数域变量一般用UV字母来表达,比如参数曲面F(u,v)。所以一般叫的三维曲面本质上是二维的,它所嵌入的空间是三维的。凡是能通过F(u,v)来表达的曲面都是参数曲面,比如NURBS曲面。对于三角网格,如果能把它与参数平面建立一一映射,那么它也就被参数化了,这个映射就是UV展开。如下图所示,左图是右边网格在参数平面上的展开,这样每个顶点都有了一个uv参数值,这也被称为纹理坐标。

创建割缝

网格UV展开到平面的时候,如果没有割缝产生,那么每个顶点在其相邻三角形内的纹理坐标都是一样的,故可简称为顶点的纹理坐标。如果有割缝产生,割缝处的顶点在不同三角形内的纹理坐标是不一样的。这时,顶点和纹理坐标是一对多的关系。对于任意拓扑结构的网格,需要给它添加割缝,把它分割成一片一片的圆盘结构,再做展开。

UV展开的应用里,经常需要创建一些网格割缝。好的割缝,一般有这些性质:

  • 长度很短
  • 割线光滑
  • 沿特征边
  • 分布在视觉不明显的地方
  • 在全自动UV展开应用里,割缝首先要能把网格割成一片一片的圆盘结构

UV展开的扭曲情况

网格展开到平面区域,除了可展曲面,其它曲面在展开后都会产生一些扭曲。一般有两种扭曲。一种是曲面本身的几何所决定的,比如球面展开到平面,一定会产生扭曲。想要减少展开的扭曲程度,可以在扭曲程度大的地方增加曲面割线。另一种是展开算法中的约束产生的扭曲,比如固定边界的UV展开。一种直观的观察展开扭曲程度的方式是,把一张棋盘格图片贴到网格上,棋盘格越均匀,UV展开扭曲越小。

UvUnwrap

任意拓扑网格全自动UV展开大概有以下步骤:自动找纹理割缝,自动UV展开,如果展开扭曲大,会自动进行再分割展开,最后自动纹理坐标打包。

//任意拓扑网格全自动UV展开类
class UvUnwrapper
{public:void setMesh(const Mesh &mesh);void setTexelSize(float texelSize);void unwrap();const std::vector<FaceTextureCoords> &getFaceUvs() const;const std::vector<Rect> &getChartRects() const;const std::vector<int> &getChartSourcePartitions() const;float getTextureSize() const;private:void partition();void splitPartitionToIslands(const std::vector<size_t> &group, std::vector<std::vector<size_t>> &islands);void unwrapSingleIsland(const std::vector<size_t> &group, int sourcePartition, bool skipCheckHoles=false);void parametrizeSingleGroup(const std::vector<Vertex> &verticies,const std::vector<Face> &faces,std::map<size_t, size_t> &localToGlobalFacesMap,size_t faceNumToChart,int sourcePartition);bool fixHolesExceptTheLongestRing(const std::vector<Vertex> &verticies, std::vector<Face> &faces, size_t *remainingHoleNum=nullptr);void makeSeamAndCut(const std::vector<Vertex> &verticies,const std::vector<Face> &faces,std::map<size_t, size_t> &localToGlobalFacesMap,std::vector<size_t> &firstGroup, std::vector<size_t> &secondGroup);void calculateSizeAndRemoveInvalidCharts();void packCharts();void finalizeUv();void buildEdgeToFaceMap(const std::vector<size_t> &group, std::map<std::pair<size_t, size_t>, size_t> &edgeToFaceMap);void buildEdgeToFaceMap(const std::vector<Face> &faces, std::map<std::pair<size_t, size_t>, size_t> &edgeToFaceMap);double distanceBetweenVertices(const Vertex &first, const Vertex &second);float areaOf3dTriangle(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c);float areaOf2dTriangle(const Eigen::Vector2d &a, const Eigen::Vector2d &b, const Eigen::Vector2d &c);void triangulateRing(const std::vector<Vertex> &verticies,std::vector<Face> &faces, const std::vector<size_t> &ring);void calculateFaceTextureBoundingBox(const std::vector<FaceTextureCoords> &faceTextureCoords,float &left, float &top, float &right, float &bottom);//网格数据,任意拓扑结构Mesh m_mesh;//纹理坐标,顺序和顶点顺序一一对应std::vector<FaceTextureCoords> m_faceUvs;std::map<int, std::vector<size_t>> m_partitions;std::vector<std::pair<std::vector<size_t>, std::vector<FaceTextureCoords>>> m_charts;std::vector<std::pair<float, float>> m_chartSizes;std::vector<std::pair<float, float>> m_scaledChartSizes;std::vector<Rect> m_chartRects;std::vector<int> m_chartSourcePartitions;bool m_segmentByNormal = true;float m_segmentDotProductThreshold = 0.0;    //90 degreesfloat m_texelSizePerUnit = 1.0;float m_resultTextureSize = 0;bool m_segmentPreferMorePieces = true;bool m_enableRotation = true;static const std::vector<float> m_rotateDegrees;
};
//UV自动展开
//输入:模型、三角面的纹理索引、割缝顶点、UV网格
void uvUnwrap(const Object &object,std::vector<std::vector<QVector2D>> &triangleVertexUvs,std::set<int> &seamVertices,std::map<QUuid, std::vector<QRectF>> &uvRects)
{//获取模型的顶点、网格面片、法线const auto &choosenVertices = object.vertices;const auto &choosenTriangles = object.triangles;const auto &choosenTriangleNormals = object.triangleNormals;triangleVertexUvs.resize(choosenTriangles.size(), {QVector2D(), QVector2D(), QVector2D()});if (nullptr == object.triangleSourceNodes())return;const std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes = *object.triangleSourceNodes();simpleuv::Mesh inputMesh;for (const auto &vertex: choosenVertices) {//UV的点存储列表simpleuv::Vertex v;v.xyz[0] = vertex.x();v.xyz[1] = vertex.y();v.xyz[2] = vertex.z();inputMesh.vertices.push_back(v);}std::map<QUuid, int> partIdToPartitionMap;std::vector<QUuid> partitionPartUuids;for (decltype(choosenTriangles.size()) i = 0; i < choosenTriangles.size(); ++i) {const auto &triangle = choosenTriangles[i];const auto &sourceNode = triangleSourceNodes[i];const auto &normal = choosenTriangleNormals[i];//UV的面存储列表simpleuv::Face f;f.indices[0] = triangle[0];f.indices[1] = triangle[1];f.indices[2] = triangle[2];inputMesh.faces.push_back(f);simpleuv::Vector3 n;n.xyz[0] = normal.x();n.xyz[1] = normal.y();n.xyz[2] = normal.z();inputMesh.faceNormals.push_back(n);auto findPartitionResult = partIdToPartitionMap.find(sourceNode.first);if (findPartitionResult == partIdToPartitionMap.end()) {partitionPartUuids.push_back(sourceNode.first);partIdToPartitionMap.insert({sourceNode.first, (int)partitionPartUuids.size()});inputMesh.facePartitions.push_back((int)partitionPartUuids.size());} else {inputMesh.facePartitions.push_back(findPartitionResult->second);}}simpleuv::UvUnwrapper uvUnwrapper;uvUnwrapper.setMesh(inputMesh);uvUnwrapper.unwrap();qDebug() << "Texture size:" << uvUnwrapper.getTextureSize();const std::vector<simpleuv::FaceTextureCoords> &resultFaceUvs = uvUnwrapper.getFaceUvs();const std::vector<simpleuv::Rect> &resultChartRects = uvUnwrapper.getChartRects();const std::vector<int> &resultChartSourcePartitions = uvUnwrapper.getChartSourcePartitions();std::map<int, QVector2D> vertexUvMap;for (decltype(choosenTriangles.size()) i = 0; i < choosenTriangles.size(); ++i) {const auto &triangle = choosenTriangles[i];const auto &src = resultFaceUvs[i];auto &dest = triangleVertexUvs[i];for (size_t j = 0; j < 3; ++j) {QVector2D uvCoord = QVector2D(src.coords[j].uv[0], src.coords[j].uv[1]);dest[j][0] = uvCoord.x();dest[j][1] = uvCoord.y();int vertexIndex = triangle[j];auto findVertexUvResult = vertexUvMap.find(vertexIndex);if (findVertexUvResult == vertexUvMap.end()) {vertexUvMap.insert({vertexIndex, uvCoord});} else {if (!qFuzzyCompare(findVertexUvResult->second, uvCoord)) {seamVertices.insert(vertexIndex);}}}}for (size_t i = 0; i < resultChartRects.size(); ++i) {const auto &rect = resultChartRects[i];const auto &source = resultChartSourcePartitions[i];if (0 == source || source > (int)partitionPartUuids.size()) {qDebug() << "Invalid UV chart source partition:" << source;continue;}uvRects[partitionPartUuids[source - 1]].push_back({rect.left, rect.top, rect.width, rect.height});}
}

源码分析学习记录(12)——自动UV展开相关推荐

  1. 源码分析学习记录(9)——PBR材质

    2021SC@SDUSC Dust3D中的材质采用PBR模型.PBR就是Physically-Based Rendering的缩写,意为基于物理的渲染.它提供了一种光照和渲染方法,能够更精确的描绘光和 ...

  2. 源码分析学习记录(11)——半边结构

    2021SC@SDUSC Dust3D在网格无缝缝合时使用了半边数据结构存储相关数据. 表示多边形网格的一个常用方式就是使用共享的顶点列表和面的列表.这样的表示方法在许多情况下都非常方便和高效,但是在 ...

  3. 源码分析学习记录(6)——蒙皮

    2021SC@SDUSC 文章目录 蒙皮算法 刚性绑定算法 柔性绑定算法 SkinnedMeshCreator createMeshFromTransform 蒙皮算法 骨骼的蒙皮算法,又称为骨骼与皮 ...

  4. 源码分析学习记录(5)——骨骼存储与建立

    2021SC@SDUSC 文章目录 骨骼数据结构 updateMatrix 建立骨骼树形结构 骨骼决定了模型整体在世界坐标系中的位置和方位. 在渲染静态模型时, 由于模型的顶点都是定义在模型坐标系中的 ...

  5. Spark-Core源码学习记录 3 SparkContext、SchedulerBackend、TaskScheduler初始化及应用的注册流程

    Spark-Core源码学习记录 该系列作为Spark源码回顾学习的记录,旨在捋清Spark分发程序运行的机制和流程,对部分关键源码进行追踪,争取做到知其所以然,对枝节部分源码仅进行文字说明,不深入下 ...

  6. 使用源码安装 PostgreSQL 12.5 主从集群

    推荐阅读 Helm3(K8S 资源对象管理工具)视频教程:https://edu.csdn.net/course/detail/32506 Helm3(K8S 资源对象管理工具)博客专栏:https: ...

  7. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  8. Springboot源码分析第一弹 - 自动装配实现

    Springboot就不用多了吧,解放Java开发双手的神器. 最显著的特点就是,去配置化,自动装配,自动配置.让开发人员只需要注重业务的开发 今天就来了解一下自动装配的源码是怎么实现的 预先准备 直 ...

  9. 小视频app源码无障碍服务实现自动跳过APP启动页广告

    小视频app源码无障碍服务实现自动跳过APP启动页广告实现的相关代码 一. res目录下新建xml文件夹新建文件accessibility.xml <?xml version="1.0 ...

最新文章

  1. Angular2:从AngularJS 1.x 中学到的经验
  2. 那些轻轻拍了拍Attention的后浪们
  3. golang log.Fatal() 和 panic() 函数的区别
  4. [Python爬虫] 之十二:Selenium +phantomjs抓取中的url编码问题
  5. 怎样用计算机记账,仓管员怎么用电脑记账?简单实用的电脑操作方式一览!
  6. JQuery:JQuery遍历详解
  7. sBRDF空间双向反射分布函数完全解析
  8. 了解计算机中的信息编码教案,《信息编码》教学设计
  9. winform遍历bartender_C# 调用Bartender打印的2种方式
  10. PIP卸载升级与安装不成功
  11. Arduino笔记四电子指南针罗盘HMC5883L
  12. 如何加载CASS DAT格式文件
  13. EditPlus安装步骤
  14. 云免停机卡免流服务器监控
  15. 计算机科学与生命科学的关系,计算机科学与生命科学论文
  16. html保存至心愿单按钮,王者荣耀添加心愿单有什么用 心愿单怎么实现
  17. 数据结构溢彩加强版——(二)算法篇
  18. 网页中Flash播放器里的视频获取的方法
  19. PTA:7-34 通讯录的录入与显示 (10分)
  20. re.I参数实现在findall和finditer方法中实现匹配忽略大小写

热门文章

  1. WIN7远程桌面连接方法!
  2. DD-WRT 中继桥接模式 配置方法
  3. ThunderBird登陆hotmail等邮箱的配置
  4. 神秘的移动电商:服务三低人群
  5. 佳明步频、垂直幅度、触地时间
  6. P3480 [POI2009]KAM-Pebbles 题解
  7. DAO设计模式(转)
  8. Onunload与Onbeforeunload
  9. 2023年妇女节是哪一天 妇女节是2023年几月几日?
  10. 日程管理APP——TickTo