{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/unity-drawcall/",
  "atlas": "https://www.pystone.net/?node=unity-drawcall#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/unity-drawcall.md",
  "context": "https://www.pystone.net/notes/unity-drawcall.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:unity-drawcall",
  "slug": "unity-drawcall",
  "title": "DrawCall",
  "type": "note",
  "visibility": "public",
  "idStability": "rename-stable",
  "author": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "publisher": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "aliases": [],
  "summary": "DrawCall 创建时间：2020/6/15 12:20",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "890aac36cf093518e0f46ed0b52fb1be7400c2e709f95296f534fb0b1b9d00c9",
  "assets": [
    {
      "reference": "assets/1592212245575.png",
      "url": "/media/03141d8dc4f060146554.png",
      "mediaType": "image/png",
      "contentHash": "03141d8dc4f0601465547444bb0912c2912a8662110a418c40073c89bf5c2902",
      "byteLength": 34126,
      "width": 571,
      "height": 333
    },
    {
      "reference": "assets/1592212252241.png",
      "url": "/media/44bfc7e5df8c3541e023.png",
      "mediaType": "image/png",
      "contentHash": "44bfc7e5df8c3541e02366f8eeb52be01c56da0faebdfef87b96feba89a294a8",
      "byteLength": 29738,
      "width": 577,
      "height": 329
    }
  ],
  "headings": [
    {
      "depth": 1,
      "text": "DrawCall",
      "anchor": "drawcall",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#drawcall"
    },
    {
      "depth": 2,
      "text": "DrawCall",
      "anchor": "drawcall-1",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#drawcall-1"
    },
    {
      "depth": 2,
      "text": "概念",
      "anchor": "概念",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#%E6%A6%82%E5%BF%B5"
    },
    {
      "depth": 2,
      "text": "DrawCall影响帧率",
      "anchor": "drawcall影响帧率",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#drawcall%E5%BD%B1%E5%93%8D%E5%B8%A7%E7%8E%87"
    },
    {
      "depth": 2,
      "text": "如何减少Draw Call",
      "anchor": "如何减少draw-call",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91draw-call"
    },
    {
      "depth": 3,
      "text": "批处理(Batching)",
      "anchor": "批处理batching",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#%E6%89%B9%E5%A4%84%E7%90%86batching"
    },
    {
      "depth": 4,
      "text": "Dynamic Batching",
      "anchor": "dynamic-batching",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#dynamic-batching"
    },
    {
      "depth": 4,
      "text": "Static Batching",
      "anchor": "static-batching",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#static-batching"
    },
    {
      "depth": 5,
      "text": "坏处：VBO增大，通过内存来换取性能",
      "anchor": "坏处vbo增大通过内存来换取性能",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#%E5%9D%8F%E5%A4%84vbo%E5%A2%9E%E5%A4%A7%E9%80%9A%E8%BF%87%E5%86%85%E5%AD%98%E6%9D%A5%E6%8D%A2%E5%8F%96%E6%80%A7%E8%83%BD"
    },
    {
      "depth": 4,
      "text": "Batching对提升性能的启发",
      "anchor": "batching对提升性能的启发",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#batching%E5%AF%B9%E6%8F%90%E5%8D%87%E6%80%A7%E8%83%BD%E7%9A%84%E5%90%AF%E5%8F%91"
    },
    {
      "depth": 3,
      "text": "减少反光阴影等",
      "anchor": "减少反光阴影等",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#%E5%87%8F%E5%B0%91%E5%8F%8D%E5%85%89%E9%98%B4%E5%BD%B1%E7%AD%89"
    },
    {
      "depth": 3,
      "text": "遮挡剔除",
      "anchor": "遮挡剔除",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#%E9%81%AE%E6%8C%A1%E5%89%94%E9%99%A4"
    },
    {
      "depth": 3,
      "text": "NGUI的优化",
      "anchor": "ngui的优化",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#ngui%E7%9A%84%E4%BC%98%E5%8C%96"
    },
    {
      "depth": 2,
      "text": "计算（值得以后研究，把这个公式运用到优化实践中）",
      "anchor": "计算值得以后研究把这个公式运用到优化实践中",
      "citation": "https://www.pystone.net/notes/unity-drawcall/#%E8%AE%A1%E7%AE%97%E5%80%BC%E5%BE%97%E4%BB%A5%E5%90%8E%E7%A0%94%E7%A9%B6%E6%8A%8A%E8%BF%99%E4%B8%AA%E5%85%AC%E5%BC%8F%E8%BF%90%E7%94%A8%E5%88%B0%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5%E4%B8%AD"
    }
  ],
  "claims": [],
  "outgoing": [],
  "incoming": [
    {
      "id": "note:game-graphics-and-runtime",
      "title": "游戏图形与运行时",
      "url": "https://www.pystone.net/notes/game-graphics-and-runtime/",
      "atlas": "https://www.pystone.net/?node=game-graphics-and-runtime#knowledge-atlas",
      "label": "游戏图形与运行时",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "Unity中的“DrawCall”导航项",
      "citation": "https://www.pystone.net/notes/game-graphics-and-runtime/#unity"
    }
  ],
  "contentMarkdown": "# DrawCall\n> 创建时间：2020/6/15 12:20\n\n## DrawCall\n\n## 概念\n\nDraw Call就是CPU调用图形编程接口，比如DirectX或OpenGL，来命令GPU进行渲染的操作。\n（DirectX中的DrawIndexedPrimitive命令，OpenGL中的glDrawElement命令）\n\n命令缓冲区(Command Buffer)\nCPU向其中添加命令，而由GPU从中读取命令\n添加和读取的过程相互独立\n命令有很多种类，而Draw Call是其中的一种\n改变渲染状态（Shader，Texture）\n\n## DrawCall影响帧率\n\n每次调用Draw Call之前，CPU需要向GPU发送很多内容，包括数据，状态，命令等。在这一阶段，CPU需要完成很多工作，例如检查渲染状态等。\nGPU的渲染能力很强，渲染速度往往快于CPU提交命令的速度。\n如果Draw Call的数量太多，CPU就会把大量时间花费在提交Draw Call命令上，造成CPU的过载。\n\n## 如何减少Draw Call\n\n### 批处理(Batching)\n\n把很多小的Draw Call合并成一个大的Draw Call。\n\n> 在CPU和RAM中合并网格，而 **合并网格** 本身是需要计算，消耗CPU时间，而且 **创建新网格** 也会占用内存。因此批处理的频次不宜太高，不然造成的消耗可能得不偿失\n\nUnity内置了Draw Call Batching技术：在一次DrawCall中批量处理多个物体。只要物体的变换和材质相同，GPU就可以按完全相同的方式进行处理，即可以把它们放在一个Draw Call中。\n\n条件: 合并的网格会在一次渲染任务中进行绘制，他们的渲染数据，渲染状态和shader都是一样的，因此合并的条件至少是： **同材质、同贴图、同shader** 。最好网格顶点格式也一致。\n\nUnity提供了Dynamic Batching和Static Batching两种方式。\n\n#### Dynamic Batching\n\nDynamic Batching是完全自动进行的，不需要也无法进行任何干预。\n\n动态批处理的约束：\n\n  1. 批处理动态物体需要在每个顶点上进行一定的开销，所以动态批处理仅支持小于900顶点的网格物体。\n\n  2. 如果着色器使用顶点位置，法线和UV值三种属性，那么只能批处理300顶点以下的物体；如果着色器需要使用顶点位置，法线，UV0，UV1和切向量，那么只能批处理180顶点以下的物体。\n\n  3. 不要使用缩放，分别拥有缩放大小（1,1,1）和（2,2,2）的两个物体将不会进行批处理。\n\n  4. 统一缩放的物体不会和非统一缩放的物体进行批处理。\n\n  5. 使用缩放尺度（1,1,1）和（1,2,1）的两个物体将不会进行批处理，但是使用缩放尺度（1,2,1）和（1,3,1）的两个物体将可以进行批处理。\n\n  6. 使用不同材质的实例化物体将会导致批处理失败。\n\n  7. 拥有lightmap的物体含有额外（隐藏）的材质属性，比如：lightmap的偏移和缩放系数等。所以，拥有lightmap的物体将不会进行批处理（除非他们指向lightmap的同一部分）。\n\n  8. 多通道的shader会妨碍批处理操作。比如，几乎unity中所有的着色器在前向渲染中都支持多个光源，并为它们有效地开辟多个通道。\n\n  9. 预设体的实例会自动地使用相同的网格模型和材质。\n\n  10. 网格合并的顶点数量有上限（Unity中好像是65535还是900？）\n\n  11. 使用MultiplePass的shader也不会进行批处理\n\n  12. 接受实时阴影的物体也不会进行批处理\n\n#### Static Batching\n\nStatic Batching需要把静止的物体标记为Static，然后无论大小，都会组成Batch。\n如何对动态加载的静态物体进行静态合批操作：\n\n```csharp\nstring s1 = &quot;Hello &quot;;\nstring s2 = s1;\ns1 += &quot;World&quot;;\n\nSystem.Console.WriteLine(s2);\n//Output: Hello\n\n```\n\n> 可以通过CombineChildren脚本（StandardAssets/Scripts/UnityScripts/CombineChildren）手动把物体组合在一起，但这个脚本会影响可见性测试，因为组合在一起的物体始终会被看作一个物体，从而会增加GPU要处理的几何体数量，因此要小心使用。\n\n##### 坏处：VBO增大，通过内存来换取性能\n\n如果在静态批处理前有一些物体共享了相同的网格（例如这里的两个箱子），那么每一个物体都会有一个该网格的复制品，即一个网格会变成多个网格被发送给GPU。\n如果使用同一网格的对象很多，可能需要牺牲一定的渲染性能，避免使用静态批处理。例如，如果在一个使用了1000个重复树模型的森林中使用静态批处理，那么结果就会产生1000倍的内存，这会造成严重的内存影响。\n\n#### Batching对提升性能的启发\n\n  1. 避免使用大量小的网格，当确实需要时，考虑是否要合并。\n\n  2. 首先是尽量减少场景中使用的材质数量，即尽量共享材质，对于仅纹理不同的材质可以把纹理组合到一张更大的纹理中。把纹理打包成图集，减少材质数量。\n\n  3. 把不会移动的物体标记为Static。\n\n  4. 合并本身有消耗，因此尽量在编辑器下进行合并\n\n### 减少反光阴影等\n\n尽量少的使用反光，阴影之类的，因为那会使物体多次渲染。\n同样的设置，如果将灯光的阴影效果打开，DrawCall会大幅增加，使用lightmap可以实现想要的阴影效果\n\n### 遮挡剔除\n\n对于复杂的静态场景，还可以考虑自行设计遮挡剔除算法，减少可见的物体数量同时也可以减少Draw Call。\n\n### NGUI的优化\n\nNGUI主要是有三大模块组成：UIPanel，UIWidget，UIDrawcall组成。其中UIPanel是用来管理UIWidget控件和UIDrawCall，而UIWidget是所有组件的基类。\n\n在NGUI框架中，会有一个静态的list用来存放所有的Panel，然后每个单独的Panel下会保存自己的UIWidget和UIDrawCall，在每次绘制的时候panel会遍历自己下面的所有层级下的子物体，直到查找结束，或者遇到新的panel会跳出当前分支，继续寻找其他分支，直到全部查找结束。所以说在实际运行中，每次都会为一个UIWidget绘制一个DrawCall，如果这时候连续的多个UIWidget使用的材质和纹理一致，就会公用一个DrawCall。\n使用不同材质和纹理：\n\n![Alt text](assets/1592212245575.png)\n\n使用材质和纹理：\n\n![Alt text](assets/1592212252241.png)\n\n使用同一个图集，并且使用同样的材质在同一个panel下才会合并为一个Drawcall\n\n> 如果使用同一个图集、材质，但是中间夹杂了其他的渲染状态，也会导致重新调用一次DrawCall。\n>\n> 另外还需要注意一点就是在panel下如果动态的物体，就是为了实现某种效果，需要UI 进行位置移动，这种情况下，最好做成动态分离，因为只要panel下UI有移动，panle就会对清空之前的保存的UIWidget和UIDrawCall，重新进行渲染，这样就会造成性能浪费\n\n对UI进行界面排布就需要对图集和层级做好规划，进而减少DrawCall次数\n\n## 计算（值得以后研究，把这个公式运用到优化实践中）\n\nNVIDIA 在 GDC 曾提出，25K batchs/sec 会吃满 1GHz 的 CPU，100的使用率。所以他们推出了一条公式，来预估游戏中大概可以 Run 多少个 Batch：\n举个例子：如果你的目标是游戏跑30FPS、使用2GHz的CPU、20%的工作量拨给Draw Call来使用，那你每帧可以有多少Draw Call呢？\n333 Batchs/Frame = 25K * 2 * (0.2/30)\n\nRef：\n<https://blog.csdn.net/yu1368072332/article/details/85676537>\n"
}
