diff --git a/404.html b/404.html index 0c60fd0..cbbc10e 100644 --- a/404.html +++ b/404.html @@ -9,13 +9,13 @@ - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/images/BatchMesh-750e6e9e501409d9cd97e091eaea0ae6.png b/assets/images/BatchMesh-750e6e9e501409d9cd97e091eaea0ae6.png deleted file mode 100644 index eb409af..0000000 Binary files a/assets/images/BatchMesh-750e6e9e501409d9cd97e091eaea0ae6.png and /dev/null differ diff --git a/assets/images/BigMesh-ac06d3de110849ac6ddd3072ca42b033.png b/assets/images/BigMesh-ac06d3de110849ac6ddd3072ca42b033.png deleted file mode 100644 index 65c6ccc..0000000 Binary files a/assets/images/BigMesh-ac06d3de110849ac6ddd3072ca42b033.png and /dev/null differ diff --git a/assets/js/76550e64.70031a83.js b/assets/js/76550e64.70b68e55.js similarity index 70% rename from assets/js/76550e64.70031a83.js rename to assets/js/76550e64.70b68e55.js index 25653b4..4c1f112 100644 --- a/assets/js/76550e64.70031a83.js +++ b/assets/js/76550e64.70b68e55.js @@ -1 +1 @@ -"use strict";(self.webpackChunkryao_blog=self.webpackChunkryao_blog||[]).push([[2693],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>f});var a=r(7294);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,a)}return r}function i(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var s=a.createContext({}),c=function(e){var t=a.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},p=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,l=e.originalType,s=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),u=c(r),m=n,f=u["".concat(s,".").concat(m)]||u[m]||d[m]||l;return r?a.createElement(f,i(i({ref:t},p),{},{components:r})):a.createElement(f,i({ref:t},p))}));function f(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var l=r.length,i=new Array(l);i[0]=m;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[u]="string"==typeof e?e:n,i[1]=o;for(var c=2;c{r.r(t),r.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>l,metadata:()=>o,toc:()=>c});var a=r(7462),n=(r(7294),r(3905));const l={sidebar_position:4},i="\u5173\u4e8eDrawCall",o={unversionedId:"gpu-series/draw_call",id:"gpu-series/draw_call",title:"\u5173\u4e8eDrawCall",description:"DrawCall \u662f\u5728\u6027\u80fd\u4f18\u5316\u7684\u65f6\u5019\u7ecf\u5e38\u8ba8\u8bba\u5230\u7684\u4e1c\u897f\uff0cDrawCall \u8fc7\u591a\u4f1a\u5bfc\u81f4 CPU \u9700\u8981\u7ec4\u88c5\u5f88\u591a\u6e32\u67d3\u6307\u4ee4\uff0c\u51c6\u5907\u5f88\u591a\u6570\u636e\uff0c\u5f53\u8d85\u8fc7\u4e00\u5b9a\u6570\u91cf\u540e\uff0c\u4f1a\u5bfc\u81f4 CPU \u4e0e GPU \u901a\u4fe1\u51fa\u73b0\u74f6\u9888\uff0c\u5f71\u54cd\u6027\u80fd\u3002\u6240\u4ee5\u4e00\u822c\u6765\u8bf4\u4f1a\u9700\u8981\u7528\u6279\u91cf\u6e32\u67d3\uff08\u201c\u5408\u6279\u201d\uff09\u6280\u672f\uff0c\u901a\u8fc7\u51cf\u5c11CPU\u5411GPU\u53d1\u9001\u6e32\u67d3\u547d\u4ee4\uff08DrawCall\uff09\u7684\u6b21\u6570\uff0c\u4ee5\u53ca\u51cf\u5c11GPU\u5207\u6362\u6e32\u67d3\u72b6\u6001\u7684\u6b21\u6570\uff0c\u5c3d\u91cf\u8ba9GPU\u4e00\u6b21\u591a\u505a\u4e00\u4e9b\u4e8b\u60c5\uff0c\u6765\u63d0\u5347\u903b\u8f91\u7ebf\u548c\u6e32\u67d3\u7ebf\u7684\u6574\u4f53\u6548\u7387\u3002",source:"@site/docs/gpu-series/draw_call.md",sourceDirName:"gpu-series",slug:"/gpu-series/draw_call",permalink:"/docs/gpu-series/draw_call",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/gpu-series/draw_call.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"[GPU Gems 3\u7b14\u8bb0] Part V-4: Broad-Phase Collision Detection with CUDA",permalink:"/docs/gpu-series/gems-5-4"},next:{title:"JointSolver\u7cfb\u5217",permalink:"/docs/category/jointsolver\u7cfb\u5217"}},s={},c=[{value:"\u9759\u6001\u5408\u6279",id:"\u9759\u6001\u5408\u6279",level:2},{value:"\u5408\u5e76\u9636\u6bb5",id:"\u5408\u5e76\u9636\u6bb5",level:3},{value:"\u6279\u5904\u7406\u9636\u6bb5",id:"\u6279\u5904\u7406\u9636\u6bb5",level:3},{value:"\u5c0fTips",id:"\u5c0ftips",level:3}],p={toc:c},u="wrapper";function d(e){let{components:t,...l}=e;return(0,n.kt)(u,(0,a.Z)({},p,l,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"\u5173\u4e8edrawcall"},"\u5173\u4e8eDrawCall"),(0,n.kt)("p",null,"DrawCall \u662f\u5728\u6027\u80fd\u4f18\u5316\u7684\u65f6\u5019\u7ecf\u5e38\u8ba8\u8bba\u5230\u7684\u4e1c\u897f\uff0cDrawCall \u8fc7\u591a\u4f1a\u5bfc\u81f4 CPU \u9700\u8981\u7ec4\u88c5\u5f88\u591a\u6e32\u67d3\u6307\u4ee4\uff0c\u51c6\u5907\u5f88\u591a\u6570\u636e\uff0c\u5f53\u8d85\u8fc7\u4e00\u5b9a\u6570\u91cf\u540e\uff0c\u4f1a\u5bfc\u81f4 CPU \u4e0e GPU \u901a\u4fe1\u51fa\u73b0\u74f6\u9888\uff0c\u5f71\u54cd\u6027\u80fd\u3002\u6240\u4ee5\u4e00\u822c\u6765\u8bf4\u4f1a\u9700\u8981\u7528\u6279\u91cf\u6e32\u67d3\uff08\u201c\u5408\u6279\u201d\uff09\u6280\u672f\uff0c\u901a\u8fc7\u51cf\u5c11CPU\u5411GPU\u53d1\u9001\u6e32\u67d3\u547d\u4ee4\uff08DrawCall\uff09\u7684\u6b21\u6570\uff0c\u4ee5\u53ca\u51cf\u5c11GPU\u5207\u6362\u6e32\u67d3\u72b6\u6001\u7684\u6b21\u6570\uff0c\u5c3d\u91cf\u8ba9GPU\u4e00\u6b21\u591a\u505a\u4e00\u4e9b\u4e8b\u60c5\uff0c\u6765\u63d0\u5347\u903b\u8f91\u7ebf\u548c\u6e32\u67d3\u7ebf\u7684\u6574\u4f53\u6548\u7387\u3002"),(0,n.kt)("admonition",{type:"tip"},(0,n.kt)("p",{parentName:"admonition"},"\u4e0d\u8fc7\u8fd9\u79cd\u505a\u6cd5\u662f\u5728GPU\u7b97\u529b\u6ca1\u6709\u88ab\u5145\u5206\u5229\u7528\uff0c\u800cCPU\u628a\u66f4\u591a\u7684\u65f6\u95f4\u90fd\u8017\u8d39\u5728\u6e32\u67d3\u547d\u4ee4\u7684\u63d0\u4ea4\u4e0a\u65f6\uff0c\u624d\u6709\u610f\u4e49\u3002\u5982\u679c\u74f6\u9888\u5728GPU\uff0c\u6bd4\u5982GPU\u6027\u80fd\u504f\u5dee\uff0c\u6216\u7247\u6bb5\u7740\u8272\u5668\u8fc7\u4e8e\u590d\u6742\u7b49\uff0c\u90a3\u4e48\u6ca1\u51c6\u9002\u5f53\u51cf\u5c11\u6279\u5904\u7406\uff0c\u53cd\u800c\u80fd\u8fbe\u5230\u4f18\u5316\u7684\u6548\u679c\u3002\u5f53\u7136\uff0c\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u786e\u5b9e\u662f\u4ee5CPU\u51fa\u73b0\u74f6\u9888\u66f4\u4e3a\u5e38\u89c1\u3002")),(0,n.kt)("h2",{id:"\u9759\u6001\u5408\u6279"},"\u9759\u6001\u5408\u6279"),(0,n.kt)("p",null,"\u9759\u6001\u5408\u6279\u53ef\u4ee5\u5206\u4e3a\u9884\u5904\u7406\u9636\u6bb5\u7684\u5408\u5e76\u548c\u8fd0\u884c\u9636\u6bb5\u7684\u6279\u5904\u7406\u3002"),(0,n.kt)("h3",{id:"\u5408\u5e76\u9636\u6bb5"},"\u5408\u5e76\u9636\u6bb5"),(0,n.kt)("p",null,"\u5c06\u7b26\u5408\u5408\u6279\u6761\u4ef6\u7684\u7f51\u683c\u53d6\u51fa\uff0c\u5bf9\u7f51\u683c\u4e0a\u7684\u9876\u70b9\u8fdb\u884c\u7a7a\u95f4\u53d8\u6362\uff0c\u53d8\u6362\u5230\u5408\u5e76\u6839\u8282\u70b9\u7684\u5750\u6807\u7cfb\u4e0b\uff0c\u518d\u5408\u5e76\u6210\u4e00\u4e2a\u65b0\u7684\u7f51\u683c\u3002"),(0,n.kt)("p",null,"\u8fd9\u6837\u505a\u7684",(0,n.kt)("strong",{parentName:"p"},"\u76ee\u7684"),"\u662f\u4e3a\u4e86\u201c\u56fa\u5316\u201d\u9876\u70b9\u7f13\u51b2\u533a\u548c\u7d22\u5f15\u7f13\u51b2\u533a\u5185\u7684\u6570\u636e\uff0c\u4f7f\u5176\u9876\u70b9\u4f4d\u7f6e\u7b49\u4fe1\u606f\u90fd\u5728\u76f8\u540c\u7684\u5750\u6807\u7cfb\u4e0b\u3002\u8fd9\u6837\u8fd0\u884c\u65f6\u5982\u679c\u9700\u8981\u5bf9\u5408\u5e76\u540e\u7684\u5bf9\u8c61\u8fdb\u884c\u7a7a\u95f4\u53d8\u6362\uff08\u624b\u52a8\u9759\u6001\u5408\u6279\u5bf9\u8c61\u7684\u6839\u8282\u70b9\u53ef\u88ab\u7a7a\u95f4\u53d8\u6362\uff09\uff0c\u5219\u65e0\u9700\u4fee\u6539\u7f13\u51b2\u533a\u5185\u7684\u9876\u70b9\u5c5e\u6027\uff0c\u53ea\u63d0\u4f9b\u6839\u8282\u70b9\u7684\u53d8\u6362\u77e9\u9635\u5373\u53ef\u3002"),(0,n.kt)("p",null,"\u4f46\u662f\u5408\u5e76\u540e\u4f1a\u81a8\u80c0\u573a\u666f\u6587\u4ef6\uff0c\u4f1a\u5728\u4e00\u5b9a\u7a0b\u5ea6\u4e0a\u5f71\u54cd\u573a\u666f\u7684\u52a0\u8f7d\u65f6\u95f4\uff0c\u53e6\u4e00\u65b9\u9762\uff0c\u4e0d\u540c\u5e73\u53f0\u5bf9\u4e8e\u5408\u5e76\u7684\u9876\u70b9\u548c\u7d22\u5f15\u6570\u91cf\u6709\u9650\u5236\uff0c\u8d85\u8fc7\u8fd9\u4e2a\u9650\u5236\u5c31\u4f1a\u5408\u5e76\u6210\u591a\u4e2a\u65b0\u7f51\u683c"),(0,n.kt)("h3",{id:"\u6279\u5904\u7406\u9636\u6bb5"},"\u6279\u5904\u7406\u9636\u6bb5"),(0,n.kt)("p",null,"\u5982\u679c\u4f7f\u7528\u76f8\u540c\u7684\u6750\u8d28\uff0c\u90a3\u4e48\u5728\u8fd0\u884c\u65f6\u5c31\u53ef\u4ee5\u5408\u6279\u6210\u529f\u3002\u624b\u52a8\u4fee\u6539\u6750\u8d28\u548c\u4fee\u6539\u6e32\u67d3\u5668\u4f7f\u7528\u7684\u7f51\u683c\uff08\u4e0d\u518d\u4f7f\u7528\u5408\u6279\u540e\u7684\u5927\u7f51\u683c\uff09\u90fd\u4f1a\u6253\u65ad\u6279\u5904\u7406\u3002"),(0,n.kt)("h3",{id:"\u5c0ftips"},"\u5c0fTips"),(0,n.kt)("p",null,"\u4e00\u6b21\u9759\u6001\u5408\u6279\uff0c\u5e76\u4e0d\u8868\u793a\u4e00\u5b9a\u53ea\u6709\u4e00\u6b21 DrawCall \u547d\u4ee4\u7684\u8c03\u7528\u3002"),(0,n.kt)("p",null,"\u5408\u5e76\u53d1\u751f\u540e\uff0c\u6bcf\u4e2a\u53c2\u4e0e\u5408\u6279\u7684\u7f51\u683c\u4fe1\u606f\uff08\u9876\u70b9\u3001\u7d22\u5f15\u7b49\uff09\u5c31\u4f1a\u88ab\u6700\u7ec8\u786e\u5b9a\uff0c\u4e0d\u518d\u88ab\u4fee\u6539\u3002\u5f53\u4e00\u4e2a\u53c2\u4e0e\u5408\u5e76\u7684\u5355\u4f4d\u4e0d\u663e\u793a\u65f6\uff0c\u5982\u88ab\u8bbe\u7f6e\u4e3a\u9690\u85cf\u6216\u88ab\u89c6\u690e\u4f53\u5254\u9664\uff0c\u5f15\u64ce\u5e76\u4e0d\u4f1a\u4fee\u6539\u9876\u70b9\u7f13\u51b2\u533a\u548c\u7d22\u5f15\u7f13\u51b2\u533a\u7684\u5185\u5bb9\uff0c\u800c\u4f1a\u62c6\u5206\u82e5\u5e72\u4e2a\u5c0f\u7684 DrawCall \u6765\u5206\u6b21\u6e32\u67d3\u3002\u901a\u8fc7\u8c03\u6574\u6bcf\u4e2a DrawCall \u7684\u7d22\u5f15\uff08\u8d77\u59cb\u7d22\u5f15\u3001\u7d22\u5f15\u4e2a\u6570\uff09\u6765\u8df3\u8fc7\u4e0d\u5e94\u8be5\u88ab\u663e\u793a\u7684\u5355\u4f4d\u3002"),(0,n.kt)("center",null,(0,n.kt)("p",null,(0,n.kt)("img",{alt:"staticBatch",src:r(6216).Z,width:"1080",height:"608"}))),(0,n.kt)("p",null,"\u6240\u4ee5\u4e5f\u53ef\u4ee5\u770b\u89c1\u9759\u6001\u5408\u6279\u548c\u76f4\u63a5\u4f7f\u7528\u5927\u7f51\u683c\u662f\u4e0d\u4e00\u6837\u7684\u3002"),(0,n.kt)("p",null,"\u5176\u4e00\uff0c\u6001\u5408\u6279\u5728\u8fd0\u884c\u65f6\uff0c\u7531\u4e8e\u6bcf\u4e2a\u53c2\u4e0e\u5408\u5e76\u7684\u5bf9\u8c61\u53ef\u4ee5\u901a\u8fc7\u8d77\u59cb\u7d22\u5f15\u7b49\u5f7c\u6b64\u533a\u5206\uff0c\u56e0\u6b64\u53ef\u4ee5\u901a\u8fc7\u4e0a\u8ff0\u591a\u6b21DrawCall\u7684\u7b56\u7565\uff0c\u5b9e\u73b0\u9690\u85cf\u6307\u5b9a\u7684\u5bf9\u8c61\uff1b\u800c\u76f4\u63a5\u4f7f\u7528\u5927\u7f51\u683c\uff0c\u5219\u65e0\u6cd5\u505a\u5230\u8fd9\u4e00\u70b9\u3002"),(0,n.kt)("p",null,"\u5176\u4e8c\uff0c\u9759\u6001\u5408\u6279\u53ef\u4ee5\u6709\u6548\u53c2\u4e0eCPU\u7684\u89c6\u9525\u5254\u9664\u3002\u5f53\u6709\u5254\u9664\u53d1\u751f\u65f6\uff0c\u88ab\u9001\u8fdb\u6e32\u67d3\u7ba1\u7ebf\u7684\u9876\u70b9\u6570\u91cf\u5c31\u4f1a\u51cf\u5c11\uff08\u901a\u8fc7\u53c2\u6570\u63a7\u5236\uff09\uff0c\u4e5f\u5c31\u610f\u5473\u7740\u88ab\u9876\u70b9\u7740\u8272\u5668\u5904\u7406\u7684\u9876\u70b9\u4f1a\u51cf\u5c11\uff0c\u63d0\u5347\u4e86GPU\u7684\u6548\u7387\uff1b\u800c\u4f7f\u7528\u5927\u7f51\u683c\u6e32\u67d3\u65f6\uff0c\u7531\u4e8e\u6574\u4e2a\u7f51\u683c\u90fd\u4f1a\u88ab\u9001\u8fdb\u6e32\u67d3\u7ba1\u7ebf\uff0c\u56e0\u6b64\u6bcf\u4e00\u4e2a\u9876\u70b9\u90fd\u9700\u8981\u88ab\u9876\u70b9\u7740\u8272\u5668\u5904\u7406\uff0c\u5982\u679c\u6444\u50cf\u673a\u53ea\u80fd\u7167\u5230\u4e00\u70b9\u70b9\uff0c\u90a3\u4e48\u7edd\u5927\u591a\u6570\u53c2\u4e0e\u8ba1\u7b97\u7684\u9876\u70b9\u6700\u540e\u90fd\u4f1a\u88ab\u88c1\u51cf\u6389\uff0c\u6709\u4e00\u4e9b\u6d6a\u8d39\u3002"),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"bigmesh",src:r(3464).Z,width:"494",height:"374"})," ",(0,n.kt)("img",{alt:"batchmesh",src:r(8889).Z,width:"494",height:"374"})))}d.isMDXComponent=!0},8889:(e,t,r)=>{r.d(t,{Z:()=>a});const a=r.p+"assets/images/BatchMesh-750e6e9e501409d9cd97e091eaea0ae6.png"},3464:(e,t,r)=>{r.d(t,{Z:()=>a});const a=r.p+"assets/images/BigMesh-ac06d3de110849ac6ddd3072ca42b033.png"},6216:(e,t,r)=>{r.d(t,{Z:()=>a});const a=r.p+"assets/images/staticBatch-81b60e54bf8c1f13659b9bf75d5bd8e2.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunkryao_blog=self.webpackChunkryao_blog||[]).push([[2693],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var c=n.createContext({}),s=function(e){var t=n.useContext(c),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},p=function(e){var t=s(e.components);return n.createElement(c.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,l=e.originalType,c=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),u=s(r),m=a,f=u["".concat(c,".").concat(m)]||u[m]||d[m]||l;return r?n.createElement(f,i(i({ref:t},p),{},{components:r})):n.createElement(f,i({ref:t},p))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=r.length,i=new Array(l);i[0]=m;var o={};for(var c in t)hasOwnProperty.call(t,c)&&(o[c]=t[c]);o.originalType=e,o[u]="string"==typeof e?e:a,i[1]=o;for(var s=2;s{r.r(t),r.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>d,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var n=r(7462),a=(r(7294),r(3905));const l={sidebar_position:4},i="\u5173\u4e8eDrawCall",o={unversionedId:"gpu-series/draw_call",id:"gpu-series/draw_call",title:"\u5173\u4e8eDrawCall",description:"DrawCall \u662f\u5728\u6027\u80fd\u4f18\u5316\u7684\u65f6\u5019\u7ecf\u5e38\u8ba8\u8bba\u5230\u7684\u4e1c\u897f\uff0cDrawCall \u8fc7\u591a\u4f1a\u5bfc\u81f4 CPU \u9700\u8981\u7ec4\u88c5\u5f88\u591a\u6e32\u67d3\u6307\u4ee4\uff0c\u51c6\u5907\u5f88\u591a\u6570\u636e\uff0c\u5f53\u8d85\u8fc7\u4e00\u5b9a\u6570\u91cf\u540e\uff0c\u4f1a\u5bfc\u81f4 CPU \u4e0e GPU \u901a\u4fe1\u51fa\u73b0\u74f6\u9888\uff0c\u5f71\u54cd\u6027\u80fd\u3002\u6240\u4ee5\u4e00\u822c\u6765\u8bf4\u4f1a\u9700\u8981\u7528\u6279\u91cf\u6e32\u67d3\uff08\u201c\u5408\u6279\u201d\uff09\u6280\u672f\uff0c\u901a\u8fc7\u51cf\u5c11CPU\u5411GPU\u53d1\u9001\u6e32\u67d3\u547d\u4ee4\uff08DrawCall\uff09\u7684\u6b21\u6570\uff0c\u4ee5\u53ca\u51cf\u5c11GPU\u5207\u6362\u6e32\u67d3\u72b6\u6001\u7684\u6b21\u6570\uff0c\u5c3d\u91cf\u8ba9GPU\u4e00\u6b21\u591a\u505a\u4e00\u4e9b\u4e8b\u60c5\uff0c\u6765\u63d0\u5347\u903b\u8f91\u7ebf\u548c\u6e32\u67d3\u7ebf\u7684\u6574\u4f53\u6548\u7387\u3002",source:"@site/docs/gpu-series/draw_call.md",sourceDirName:"gpu-series",slug:"/gpu-series/draw_call",permalink:"/docs/gpu-series/draw_call",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/gpu-series/draw_call.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"[GPU Gems 3\u7b14\u8bb0] Part V-4: Broad-Phase Collision Detection with CUDA",permalink:"/docs/gpu-series/gems-5-4"},next:{title:"JointSolver\u7cfb\u5217",permalink:"/docs/category/jointsolver\u7cfb\u5217"}},c={},s=[{value:"\u9759\u6001\u5408\u6279",id:"\u9759\u6001\u5408\u6279",level:2},{value:"\u5408\u5e76\u9636\u6bb5",id:"\u5408\u5e76\u9636\u6bb5",level:3},{value:"\u6279\u5904\u7406\u9636\u6bb5",id:"\u6279\u5904\u7406\u9636\u6bb5",level:3},{value:"\u5c0fTips",id:"\u5c0ftips",level:3}],p={toc:s},u="wrapper";function d(e){let{components:t,...l}=e;return(0,a.kt)(u,(0,n.Z)({},p,l,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"\u5173\u4e8edrawcall"},"\u5173\u4e8eDrawCall"),(0,a.kt)("p",null,"DrawCall \u662f\u5728\u6027\u80fd\u4f18\u5316\u7684\u65f6\u5019\u7ecf\u5e38\u8ba8\u8bba\u5230\u7684\u4e1c\u897f\uff0cDrawCall \u8fc7\u591a\u4f1a\u5bfc\u81f4 CPU \u9700\u8981\u7ec4\u88c5\u5f88\u591a\u6e32\u67d3\u6307\u4ee4\uff0c\u51c6\u5907\u5f88\u591a\u6570\u636e\uff0c\u5f53\u8d85\u8fc7\u4e00\u5b9a\u6570\u91cf\u540e\uff0c\u4f1a\u5bfc\u81f4 CPU \u4e0e GPU \u901a\u4fe1\u51fa\u73b0\u74f6\u9888\uff0c\u5f71\u54cd\u6027\u80fd\u3002\u6240\u4ee5\u4e00\u822c\u6765\u8bf4\u4f1a\u9700\u8981\u7528\u6279\u91cf\u6e32\u67d3\uff08\u201c\u5408\u6279\u201d\uff09\u6280\u672f\uff0c\u901a\u8fc7\u51cf\u5c11CPU\u5411GPU\u53d1\u9001\u6e32\u67d3\u547d\u4ee4\uff08DrawCall\uff09\u7684\u6b21\u6570\uff0c\u4ee5\u53ca\u51cf\u5c11GPU\u5207\u6362\u6e32\u67d3\u72b6\u6001\u7684\u6b21\u6570\uff0c\u5c3d\u91cf\u8ba9GPU\u4e00\u6b21\u591a\u505a\u4e00\u4e9b\u4e8b\u60c5\uff0c\u6765\u63d0\u5347\u903b\u8f91\u7ebf\u548c\u6e32\u67d3\u7ebf\u7684\u6574\u4f53\u6548\u7387\u3002"),(0,a.kt)("admonition",{type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"\u4e0d\u8fc7\u8fd9\u79cd\u505a\u6cd5\u662f\u5728GPU\u7b97\u529b\u6ca1\u6709\u88ab\u5145\u5206\u5229\u7528\uff0c\u800cCPU\u628a\u66f4\u591a\u7684\u65f6\u95f4\u90fd\u8017\u8d39\u5728\u6e32\u67d3\u547d\u4ee4\u7684\u63d0\u4ea4\u4e0a\u65f6\uff0c\u624d\u6709\u610f\u4e49\u3002\u5982\u679c\u74f6\u9888\u5728GPU\uff0c\u6bd4\u5982GPU\u6027\u80fd\u504f\u5dee\uff0c\u6216\u7247\u6bb5\u7740\u8272\u5668\u8fc7\u4e8e\u590d\u6742\u7b49\uff0c\u90a3\u4e48\u6ca1\u51c6\u9002\u5f53\u51cf\u5c11\u6279\u5904\u7406\uff0c\u53cd\u800c\u80fd\u8fbe\u5230\u4f18\u5316\u7684\u6548\u679c\u3002\u5f53\u7136\uff0c\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u786e\u5b9e\u662f\u4ee5CPU\u51fa\u73b0\u74f6\u9888\u66f4\u4e3a\u5e38\u89c1\u3002")),(0,a.kt)("h2",{id:"\u9759\u6001\u5408\u6279"},"\u9759\u6001\u5408\u6279"),(0,a.kt)("p",null,"\u9759\u6001\u5408\u6279\u53ef\u4ee5\u5206\u4e3a\u9884\u5904\u7406\u9636\u6bb5\u7684\u5408\u5e76\u548c\u8fd0\u884c\u9636\u6bb5\u7684\u6279\u5904\u7406\u3002"),(0,a.kt)("h3",{id:"\u5408\u5e76\u9636\u6bb5"},"\u5408\u5e76\u9636\u6bb5"),(0,a.kt)("p",null,"\u5c06\u7b26\u5408\u5408\u6279\u6761\u4ef6\u7684\u7f51\u683c\u53d6\u51fa\uff0c\u5bf9\u7f51\u683c\u4e0a\u7684\u9876\u70b9\u8fdb\u884c\u7a7a\u95f4\u53d8\u6362\uff0c\u53d8\u6362\u5230\u5408\u5e76\u6839\u8282\u70b9\u7684\u5750\u6807\u7cfb\u4e0b\uff0c\u518d\u5408\u5e76\u6210\u4e00\u4e2a\u65b0\u7684\u7f51\u683c\u3002"),(0,a.kt)("p",null,"\u8fd9\u6837\u505a\u7684",(0,a.kt)("strong",{parentName:"p"},"\u76ee\u7684"),"\u662f\u4e3a\u4e86\u201c\u56fa\u5316\u201d\u9876\u70b9\u7f13\u51b2\u533a\u548c\u7d22\u5f15\u7f13\u51b2\u533a\u5185\u7684\u6570\u636e\uff0c\u4f7f\u5176\u9876\u70b9\u4f4d\u7f6e\u7b49\u4fe1\u606f\u90fd\u5728\u76f8\u540c\u7684\u5750\u6807\u7cfb\u4e0b\u3002\u8fd9\u6837\u8fd0\u884c\u65f6\u5982\u679c\u9700\u8981\u5bf9\u5408\u5e76\u540e\u7684\u5bf9\u8c61\u8fdb\u884c\u7a7a\u95f4\u53d8\u6362\uff08\u624b\u52a8\u9759\u6001\u5408\u6279\u5bf9\u8c61\u7684\u6839\u8282\u70b9\u53ef\u88ab\u7a7a\u95f4\u53d8\u6362\uff09\uff0c\u5219\u65e0\u9700\u4fee\u6539\u7f13\u51b2\u533a\u5185\u7684\u9876\u70b9\u5c5e\u6027\uff0c\u53ea\u63d0\u4f9b\u6839\u8282\u70b9\u7684\u53d8\u6362\u77e9\u9635\u5373\u53ef\u3002"),(0,a.kt)("p",null,"\u4f46\u662f\u5408\u5e76\u540e\u4f1a\u81a8\u80c0\u573a\u666f\u6587\u4ef6\uff0c\u4f1a\u5728\u4e00\u5b9a\u7a0b\u5ea6\u4e0a\u5f71\u54cd\u573a\u666f\u7684\u52a0\u8f7d\u65f6\u95f4\uff0c\u53e6\u4e00\u65b9\u9762\uff0c\u4e0d\u540c\u5e73\u53f0\u5bf9\u4e8e\u5408\u5e76\u7684\u9876\u70b9\u548c\u7d22\u5f15\u6570\u91cf\u6709\u9650\u5236\uff0c\u8d85\u8fc7\u8fd9\u4e2a\u9650\u5236\u5c31\u4f1a\u5408\u5e76\u6210\u591a\u4e2a\u65b0\u7f51\u683c"),(0,a.kt)("h3",{id:"\u6279\u5904\u7406\u9636\u6bb5"},"\u6279\u5904\u7406\u9636\u6bb5"),(0,a.kt)("p",null,"\u5982\u679c\u4f7f\u7528\u76f8\u540c\u7684\u6750\u8d28\uff0c\u90a3\u4e48\u5728\u8fd0\u884c\u65f6\u5c31\u53ef\u4ee5\u5408\u6279\u6210\u529f\u3002\u624b\u52a8\u4fee\u6539\u6750\u8d28\u548c\u4fee\u6539\u6e32\u67d3\u5668\u4f7f\u7528\u7684\u7f51\u683c\uff08\u4e0d\u518d\u4f7f\u7528\u5408\u6279\u540e\u7684\u5927\u7f51\u683c\uff09\u90fd\u4f1a\u6253\u65ad\u6279\u5904\u7406\u3002"),(0,a.kt)("h3",{id:"\u5c0ftips"},"\u5c0fTips"),(0,a.kt)("p",null,"\u4e00\u6b21\u9759\u6001\u5408\u6279\uff0c\u5e76\u4e0d\u8868\u793a\u4e00\u5b9a\u53ea\u6709\u4e00\u6b21 DrawCall \u547d\u4ee4\u7684\u8c03\u7528\u3002"),(0,a.kt)("p",null,"\u5408\u5e76\u53d1\u751f\u540e\uff0c\u6bcf\u4e2a\u53c2\u4e0e\u5408\u6279\u7684\u7f51\u683c\u4fe1\u606f\uff08\u9876\u70b9\u3001\u7d22\u5f15\u7b49\uff09\u5c31\u4f1a\u88ab\u6700\u7ec8\u786e\u5b9a\uff0c\u4e0d\u518d\u88ab\u4fee\u6539\u3002\u5f53\u4e00\u4e2a\u53c2\u4e0e\u5408\u5e76\u7684\u5355\u4f4d\u4e0d\u663e\u793a\u65f6\uff0c\u5982\u88ab\u8bbe\u7f6e\u4e3a\u9690\u85cf\u6216\u88ab\u89c6\u690e\u4f53\u5254\u9664\uff0c\u5f15\u64ce\u5e76\u4e0d\u4f1a\u4fee\u6539\u9876\u70b9\u7f13\u51b2\u533a\u548c\u7d22\u5f15\u7f13\u51b2\u533a\u7684\u5185\u5bb9\uff0c\u800c\u4f1a\u62c6\u5206\u82e5\u5e72\u4e2a\u5c0f\u7684 DrawCall \u6765\u5206\u6b21\u6e32\u67d3\u3002\u901a\u8fc7\u8c03\u6574\u6bcf\u4e2a DrawCall \u7684\u7d22\u5f15\uff08\u8d77\u59cb\u7d22\u5f15\u3001\u7d22\u5f15\u4e2a\u6570\uff09\u6765\u8df3\u8fc7\u4e0d\u5e94\u8be5\u88ab\u663e\u793a\u7684\u5355\u4f4d\u3002"),(0,a.kt)("center",null,(0,a.kt)("p",null,(0,a.kt)("img",{alt:"staticBatch",src:r(6216).Z,width:"1080",height:"608"}))),(0,a.kt)("p",null,"\u6240\u4ee5\u4e5f\u53ef\u4ee5\u770b\u89c1\u9759\u6001\u5408\u6279\u548c\u76f4\u63a5\u4f7f\u7528\u5927\u7f51\u683c\u662f\u4e0d\u4e00\u6837\u7684\u3002"),(0,a.kt)("p",null,"\u5176\u4e00\uff0c\u6001\u5408\u6279\u5728\u8fd0\u884c\u65f6\uff0c\u7531\u4e8e\u6bcf\u4e2a\u53c2\u4e0e\u5408\u5e76\u7684\u5bf9\u8c61\u53ef\u4ee5\u901a\u8fc7\u8d77\u59cb\u7d22\u5f15\u7b49\u5f7c\u6b64\u533a\u5206\uff0c\u56e0\u6b64\u53ef\u4ee5\u901a\u8fc7\u4e0a\u8ff0\u591a\u6b21DrawCall\u7684\u7b56\u7565\uff0c\u5b9e\u73b0\u9690\u85cf\u6307\u5b9a\u7684\u5bf9\u8c61\uff1b\u800c\u76f4\u63a5\u4f7f\u7528\u5927\u7f51\u683c\uff0c\u5219\u65e0\u6cd5\u505a\u5230\u8fd9\u4e00\u70b9\u3002"),(0,a.kt)("p",null,"\u5176\u4e8c\uff0c\u9759\u6001\u5408\u6279\u53ef\u4ee5\u6709\u6548\u53c2\u4e0eCPU\u7684\u89c6\u9525\u5254\u9664\u3002\u5f53\u6709\u5254\u9664\u53d1\u751f\u65f6\uff0c\u88ab\u9001\u8fdb\u6e32\u67d3\u7ba1\u7ebf\u7684\u9876\u70b9\u6570\u91cf\u5c31\u4f1a\u51cf\u5c11\uff08\u901a\u8fc7\u53c2\u6570\u63a7\u5236\uff09\uff0c\u4e5f\u5c31\u610f\u5473\u7740\u88ab\u9876\u70b9\u7740\u8272\u5668\u5904\u7406\u7684\u9876\u70b9\u4f1a\u51cf\u5c11\uff0c\u63d0\u5347\u4e86GPU\u7684\u6548\u7387\uff1b\u800c\u4f7f\u7528\u5927\u7f51\u683c\u6e32\u67d3\u65f6\uff0c\u7531\u4e8e\u6574\u4e2a\u7f51\u683c\u90fd\u4f1a\u88ab\u9001\u8fdb\u6e32\u67d3\u7ba1\u7ebf\uff0c\u56e0\u6b64\u6bcf\u4e00\u4e2a\u9876\u70b9\u90fd\u9700\u8981\u88ab\u9876\u70b9\u7740\u8272\u5668\u5904\u7406\uff0c\u5982\u679c\u6444\u50cf\u673a\u53ea\u80fd\u7167\u5230\u4e00\u70b9\u70b9\uff0c\u90a3\u4e48\u7edd\u5927\u591a\u6570\u53c2\u4e0e\u8ba1\u7b97\u7684\u9876\u70b9\u6700\u540e\u90fd\u4f1a\u88ab\u88c1\u51cf\u6389\uff0c\u6709\u4e00\u4e9b\u6d6a\u8d39\u3002"),(0,a.kt)("center",null,(0,a.kt)("img",{src:"./img/bigMesh.png",width:"300"})," ",(0,a.kt)("img",{src:"./img/BatchMesh.png",width:"300"})))}d.isMDXComponent=!0},6216:(e,t,r)=>{r.d(t,{Z:()=>n});const n=r.p+"assets/images/staticBatch-81b60e54bf8c1f13659b9bf75d5bd8e2.png"}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.8d3d63b6.js b/assets/js/runtime~main.23e4df70.js similarity index 98% rename from assets/js/runtime~main.8d3d63b6.js rename to assets/js/runtime~main.23e4df70.js index 10bf2c4..3192416 100644 --- a/assets/js/runtime~main.8d3d63b6.js +++ b/assets/js/runtime~main.23e4df70.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,f,c,t,r={},d={};function b(e){var a=d[e];if(void 0!==a)return a.exports;var f=d[e]={id:e,loaded:!1,exports:{}};return r[e].call(f.exports,f,f.exports,b),f.loaded=!0,f.exports}b.m=r,b.c=d,e=[],b.O=(a,f,c,t)=>{if(!f){var r=1/0;for(i=0;i=t)&&Object.keys(b.O).every((e=>b.O[e](f[o])))?f.splice(o--,1):(d=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[f,c,t]},b.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return b.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var t=Object.create(null);b.r(t);var r={};a=a||[null,f({}),f([]),f(f)];for(var d=2&c&&e;"object"==typeof d&&!~a.indexOf(d);d=f(d))Object.getOwnPropertyNames(d).forEach((a=>r[a]=()=>e[a]));return r.default=()=>e,b.d(t,r),t},b.d=(e,a)=>{for(var f in a)b.o(a,f)&&!b.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce(((a,f)=>(b.f[f](e,a),a)),[])),b.u=e=>"assets/js/"+({53:"935f2afb",139:"bde6b81b",496:"8c0e717c",511:"853d7296",519:"ec51423d",533:"b2b675dd",604:"22155049",832:"87ae2219",893:"7839d40f",1138:"c5a29527",1266:"125ec243",1477:"b2f554cd",1713:"a7023ddc",1834:"aacd0ad1",2024:"03f56988",2366:"f967e78e",2476:"2c8e7084",2535:"814f3328",2693:"76550e64",3025:"6865f350",3074:"21c60786",3085:"1f391b9e",3089:"a6aa9e1f",3475:"be874743",3538:"5bf72342",3608:"9e4087bc",3703:"ad587386",3805:"a265bca8",3910:"3396d5c1",3988:"c6d6d603",4013:"01a85c17",4195:"c4f5d8e4",4322:"f5e3a597",4716:"8ba634d3",4900:"ea0f3eb2",5090:"e7738e50",5150:"9c876981",5210:"ac5f2f59",5432:"ec723fe8",5788:"4c30d0fe",6103:"ccc49370",6171:"6fca9af4",6208:"3f4ca0c2",6219:"575e4a60",6474:"33ab39d4",6947:"7ad2b423",7008:"bb21d70f",7206:"5c3b3aa9",7311:"6d47f0aa",7343:"892abff3",7412:"7ea8078a",7414:"393be207",7561:"bd84e224",7918:"17896441",8259:"1a816252",8401:"22db39bf",8610:"6875c492",8950:"bc522f7e",9514:"1be78505",9581:"314205e2",9671:"0e384e19",9817:"14eb3368"}[e]||e)+"."+{53:"478413eb",139:"ea1302cc",496:"ec606d54",511:"15b16dca",519:"394ad217",533:"1d592338",604:"23cdd8d7",832:"6ea223d1",893:"2a63fb9c",1138:"3651cb71",1266:"f8ed7755",1477:"26951624",1713:"a657fce9",1834:"337124c6",2024:"6f6ee2e4",2366:"999c26d8",2476:"4a54dffd",2529:"1a976a99",2535:"b653edbf",2693:"70031a83",3025:"afb191e9",3074:"f5ab1051",3085:"60c3856e",3089:"54ae5a34",3475:"943cb370",3538:"cc30440c",3608:"45ecb1b6",3703:"7c52d699",3805:"80c166eb",3910:"b671f4bf",3946:"fe29b4d4",3988:"18eb2e9a",4013:"b24b06c3",4195:"7d326ab3",4322:"53d2d560",4716:"3f6d0941",4900:"079317d1",4972:"ffa93c54",5090:"17426698",5150:"8e7e6577",5210:"903f3267",5432:"4d933abf",5788:"a5662690",6103:"9afd9042",6171:"9c354001",6208:"13233aa9",6219:"8b3efbc2",6474:"0c39d46b",6947:"0ebba826",7008:"b2b55af3",7206:"5a928f9b",7311:"cc155c8d",7343:"a88d1807",7412:"4a0719df",7414:"a52c3f0f",7561:"90d69d8e",7918:"5722986e",8259:"cbe87a39",8401:"3c3a7c30",8610:"c824e621",8950:"834efd11",9514:"aa7a9923",9581:"6c0d6443",9671:"eee46400",9817:"e4b6cfb0"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},t="ryao-blog:",b.l=(e,a,f,r)=>{if(c[e])c[e].push(a);else{var d,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{d.onerror=d.onload=null,clearTimeout(s);var t=c[e];if(delete c[e],d.parentNode&&d.parentNode.removeChild(d),t&&t.forEach((e=>e(f))),a)return a(f)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=u.bind(null,d.onerror),d.onload=u.bind(null,d.onload),o&&document.head.appendChild(d)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.p="/",b.gca=function(e){return e={17896441:"7918",22155049:"604","935f2afb":"53",bde6b81b:"139","8c0e717c":"496","853d7296":"511",ec51423d:"519",b2b675dd:"533","87ae2219":"832","7839d40f":"893",c5a29527:"1138","125ec243":"1266",b2f554cd:"1477",a7023ddc:"1713",aacd0ad1:"1834","03f56988":"2024",f967e78e:"2366","2c8e7084":"2476","814f3328":"2535","76550e64":"2693","6865f350":"3025","21c60786":"3074","1f391b9e":"3085",a6aa9e1f:"3089",be874743:"3475","5bf72342":"3538","9e4087bc":"3608",ad587386:"3703",a265bca8:"3805","3396d5c1":"3910",c6d6d603:"3988","01a85c17":"4013",c4f5d8e4:"4195",f5e3a597:"4322","8ba634d3":"4716",ea0f3eb2:"4900",e7738e50:"5090","9c876981":"5150",ac5f2f59:"5210",ec723fe8:"5432","4c30d0fe":"5788",ccc49370:"6103","6fca9af4":"6171","3f4ca0c2":"6208","575e4a60":"6219","33ab39d4":"6474","7ad2b423":"6947",bb21d70f:"7008","5c3b3aa9":"7206","6d47f0aa":"7311","892abff3":"7343","7ea8078a":"7412","393be207":"7414",bd84e224:"7561","1a816252":"8259","22db39bf":"8401","6875c492":"8610",bc522f7e:"8950","1be78505":"9514","314205e2":"9581","0e384e19":"9671","14eb3368":"9817"}[e]||e,b.p+b.u(e)},(()=>{var e={1303:0,532:0};b.f.j=(a,f)=>{var c=b.o(e,a)?e[a]:void 0;if(0!==c)if(c)f.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var t=new Promise(((f,t)=>c=e[a]=[f,t]));f.push(c[2]=t);var r=b.p+b.u(a),d=new Error;b.l(r,(f=>{if(b.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var t=f&&("load"===f.type?"missing":f.type),r=f&&f.target&&f.target.src;d.message="Loading chunk "+a+" failed.\n("+t+": "+r+")",d.name="ChunkLoadError",d.type=t,d.request=r,c[1](d)}}),"chunk-"+a,a)}},b.O.j=a=>0===e[a];var a=(a,f)=>{var c,t,r=f[0],d=f[1],o=f[2],n=0;if(r.some((a=>0!==e[a]))){for(c in d)b.o(d,c)&&(b.m[c]=d[c]);if(o)var i=o(b)}for(a&&a(f);n{"use strict";var e,a,f,c,t,r={},d={};function b(e){var a=d[e];if(void 0!==a)return a.exports;var f=d[e]={id:e,loaded:!1,exports:{}};return r[e].call(f.exports,f,f.exports,b),f.loaded=!0,f.exports}b.m=r,b.c=d,e=[],b.O=(a,f,c,t)=>{if(!f){var r=1/0;for(i=0;i=t)&&Object.keys(b.O).every((e=>b.O[e](f[o])))?f.splice(o--,1):(d=!1,t0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[f,c,t]},b.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return b.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var t=Object.create(null);b.r(t);var r={};a=a||[null,f({}),f([]),f(f)];for(var d=2&c&&e;"object"==typeof d&&!~a.indexOf(d);d=f(d))Object.getOwnPropertyNames(d).forEach((a=>r[a]=()=>e[a]));return r.default=()=>e,b.d(t,r),t},b.d=(e,a)=>{for(var f in a)b.o(a,f)&&!b.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce(((a,f)=>(b.f[f](e,a),a)),[])),b.u=e=>"assets/js/"+({53:"935f2afb",139:"bde6b81b",496:"8c0e717c",511:"853d7296",519:"ec51423d",533:"b2b675dd",604:"22155049",832:"87ae2219",893:"7839d40f",1138:"c5a29527",1266:"125ec243",1477:"b2f554cd",1713:"a7023ddc",1834:"aacd0ad1",2024:"03f56988",2366:"f967e78e",2476:"2c8e7084",2535:"814f3328",2693:"76550e64",3025:"6865f350",3074:"21c60786",3085:"1f391b9e",3089:"a6aa9e1f",3475:"be874743",3538:"5bf72342",3608:"9e4087bc",3703:"ad587386",3805:"a265bca8",3910:"3396d5c1",3988:"c6d6d603",4013:"01a85c17",4195:"c4f5d8e4",4322:"f5e3a597",4716:"8ba634d3",4900:"ea0f3eb2",5090:"e7738e50",5150:"9c876981",5210:"ac5f2f59",5432:"ec723fe8",5788:"4c30d0fe",6103:"ccc49370",6171:"6fca9af4",6208:"3f4ca0c2",6219:"575e4a60",6474:"33ab39d4",6947:"7ad2b423",7008:"bb21d70f",7206:"5c3b3aa9",7311:"6d47f0aa",7343:"892abff3",7412:"7ea8078a",7414:"393be207",7561:"bd84e224",7918:"17896441",8259:"1a816252",8401:"22db39bf",8610:"6875c492",8950:"bc522f7e",9514:"1be78505",9581:"314205e2",9671:"0e384e19",9817:"14eb3368"}[e]||e)+"."+{53:"478413eb",139:"ea1302cc",496:"ec606d54",511:"15b16dca",519:"394ad217",533:"1d592338",604:"23cdd8d7",832:"6ea223d1",893:"2a63fb9c",1138:"3651cb71",1266:"f8ed7755",1477:"26951624",1713:"a657fce9",1834:"337124c6",2024:"6f6ee2e4",2366:"999c26d8",2476:"4a54dffd",2529:"1a976a99",2535:"b653edbf",2693:"70b68e55",3025:"afb191e9",3074:"f5ab1051",3085:"60c3856e",3089:"54ae5a34",3475:"943cb370",3538:"cc30440c",3608:"45ecb1b6",3703:"7c52d699",3805:"80c166eb",3910:"b671f4bf",3946:"fe29b4d4",3988:"18eb2e9a",4013:"b24b06c3",4195:"7d326ab3",4322:"53d2d560",4716:"3f6d0941",4900:"079317d1",4972:"ffa93c54",5090:"17426698",5150:"8e7e6577",5210:"903f3267",5432:"4d933abf",5788:"a5662690",6103:"9afd9042",6171:"9c354001",6208:"13233aa9",6219:"8b3efbc2",6474:"0c39d46b",6947:"0ebba826",7008:"b2b55af3",7206:"5a928f9b",7311:"cc155c8d",7343:"a88d1807",7412:"4a0719df",7414:"a52c3f0f",7561:"90d69d8e",7918:"5722986e",8259:"cbe87a39",8401:"3c3a7c30",8610:"c824e621",8950:"834efd11",9514:"aa7a9923",9581:"6c0d6443",9671:"eee46400",9817:"e4b6cfb0"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},t="ryao-blog:",b.l=(e,a,f,r)=>{if(c[e])c[e].push(a);else{var d,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{d.onerror=d.onload=null,clearTimeout(s);var t=c[e];if(delete c[e],d.parentNode&&d.parentNode.removeChild(d),t&&t.forEach((e=>e(f))),a)return a(f)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=u.bind(null,d.onerror),d.onload=u.bind(null,d.onload),o&&document.head.appendChild(d)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.p="/",b.gca=function(e){return e={17896441:"7918",22155049:"604","935f2afb":"53",bde6b81b:"139","8c0e717c":"496","853d7296":"511",ec51423d:"519",b2b675dd:"533","87ae2219":"832","7839d40f":"893",c5a29527:"1138","125ec243":"1266",b2f554cd:"1477",a7023ddc:"1713",aacd0ad1:"1834","03f56988":"2024",f967e78e:"2366","2c8e7084":"2476","814f3328":"2535","76550e64":"2693","6865f350":"3025","21c60786":"3074","1f391b9e":"3085",a6aa9e1f:"3089",be874743:"3475","5bf72342":"3538","9e4087bc":"3608",ad587386:"3703",a265bca8:"3805","3396d5c1":"3910",c6d6d603:"3988","01a85c17":"4013",c4f5d8e4:"4195",f5e3a597:"4322","8ba634d3":"4716",ea0f3eb2:"4900",e7738e50:"5090","9c876981":"5150",ac5f2f59:"5210",ec723fe8:"5432","4c30d0fe":"5788",ccc49370:"6103","6fca9af4":"6171","3f4ca0c2":"6208","575e4a60":"6219","33ab39d4":"6474","7ad2b423":"6947",bb21d70f:"7008","5c3b3aa9":"7206","6d47f0aa":"7311","892abff3":"7343","7ea8078a":"7412","393be207":"7414",bd84e224:"7561","1a816252":"8259","22db39bf":"8401","6875c492":"8610",bc522f7e:"8950","1be78505":"9514","314205e2":"9581","0e384e19":"9671","14eb3368":"9817"}[e]||e,b.p+b.u(e)},(()=>{var e={1303:0,532:0};b.f.j=(a,f)=>{var c=b.o(e,a)?e[a]:void 0;if(0!==c)if(c)f.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var t=new Promise(((f,t)=>c=e[a]=[f,t]));f.push(c[2]=t);var r=b.p+b.u(a),d=new Error;b.l(r,(f=>{if(b.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var t=f&&("load"===f.type?"missing":f.type),r=f&&f.target&&f.target.src;d.message="Loading chunk "+a+" failed.\n("+t+": "+r+")",d.name="ChunkLoadError",d.type=t,d.request=r,c[1](d)}}),"chunk-"+a,a)}},b.O.j=a=>0===e[a];var a=(a,f)=>{var c,t,r=f[0],d=f[1],o=f[2],n=0;if(r.some((a=>0!==e[a]))){for(c in d)b.o(d,c)&&(b.m[c]=d[c]);if(o)var i=o(b)}for(a&&a(f);n - +
- + \ No newline at end of file diff --git a/blog/archive.html b/blog/archive.html index 52006a8..89e2acf 100644 --- a/blog/archive.html +++ b/blog/archive.html @@ -9,13 +9,13 @@ - +

Archive

Archive

- + \ No newline at end of file diff --git a/blog/tags.html b/blog/tags.html index 1d569f5..99fe7a7 100644 --- a/blog/tags.html +++ b/blog/tags.html @@ -9,13 +9,13 @@ - +

Tags

- + \ No newline at end of file diff --git "a/blog/tags/\347\241\225\345\243\253\347\224\237\346\264\273.html" "b/blog/tags/\347\241\225\345\243\253\347\224\237\346\264\273.html" index fadd2e6..b8a4b57 100644 --- "a/blog/tags/\347\241\225\345\243\253\347\224\237\346\264\273.html" +++ "b/blog/tags/\347\241\225\345\243\253\347\224\237\346\264\273.html" @@ -9,13 +9,13 @@ - +

One post tagged with "硕士生活"

View All Tags
- + \ No newline at end of file diff --git a/blog/wait-for-a-stroy.html b/blog/wait-for-a-stroy.html index ecbab78..ec7374d 100644 --- a/blog/wait-for-a-stroy.html +++ b/blog/wait-for-a-stroy.html @@ -9,13 +9,13 @@ - +
- + \ No newline at end of file diff --git a/docs/algorithm-series/heap_sort.html b/docs/algorithm-series/heap_sort.html index 0b033d1..f9e5f53 100644 --- a/docs/algorithm-series/heap_sort.html +++ b/docs/algorithm-series/heap_sort.html @@ -9,14 +9,14 @@ - +

堆排序

参考: 【排序算法:堆排序【图解+代码】】 https://www.bilibili.com/video/BV1fp4y1D7cj/?share_source=copy_web&vd_source=ee33f825ba0d9765eddc91a10101fa78

#include <iostream>
#include <vector>

using namespace std;

// 交换数组中两个元素的位置
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}


// 维护堆的性质
// n: 数组长度
// i: 要维护的节点
void heapify(vector<int>& arr, int n, int i)
{
int largest = i;
int lson = 2 * i + 1;
int rson = 2 * i + 2;

if (lson < n && arr[lson] > arr[largest])
largest = lson;
if (rson < n && arr[rson] > arr[largest])
largest = rson;

if (largest != i)
{
swap(arr[largest], arr[i]);
heapify(arr, n, largest);
}
}

// 堆排序接口
void heapSort(vector<int>& arr)
{
// 建堆
int n = arr.size();
for (int i = n / 2 - 1; i >= 0; i--)
{
// 从第一个最后一个非叶子节点开始,维护堆的性质
heapify(arr, n, i);
}

// 堆排序
while (n > 1)
{
swap(arr[0], arr[--n]);
heapify(arr, n, 0);
}

}

// 测试
int main() {
vector<int> arr = { 7, 2, 1, 6, 8, 5, 3, 4 };
heapSort(arr);
cout << "排序结果:";
for (int num : arr) {
cout << num << " ";
}
cout << endl;

return 0;
}
- + \ No newline at end of file diff --git a/docs/algorithm-series/merge_sort.html b/docs/algorithm-series/merge_sort.html index edcceeb..4a6852d 100644 --- a/docs/algorithm-series/merge_sort.html +++ b/docs/algorithm-series/merge_sort.html @@ -9,13 +9,13 @@ - +

归并排序

参考:【排序算法:归并排序【图解+代码】】 https://www.bilibili.com/video/BV1Pt4y197VZ/?share_source=copy_web&vd_source=ee33f825ba0d9765eddc91a10101fa78

// 合并
void merge(vector<int>& arr, vector<int>& tempArr, int low, int mid, int high)
{
// 标记左右半区第一个未排序的元素
// 临时数组的下标
int lPtr = low;
int rPtr = mid + 1;
int p = low;

// 合并
while (lPtr <= mid && rPtr <= high)
{
if (arr[lPtr] < arr[rPtr])
tempArr[p++] = arr[lPtr++];
else
tempArr[p++] = arr[rPtr++];
}

// 合并左半区剩余的元素
while (lPtr <= mid)
tempArr[p++] = arr[lPtr++];

// 合并右半区剩余的元素
while (rPtr <= high)
tempArr[p++] = arr[rPtr++];

// 把临时数组中合并后的元素复制粘贴到原数组中
for (int i = low; i <= high; i++)
{
arr[i] = tempArr[i];
}
}

void mergeSort(vector<int>& arr, vector<int>& tempArr, int low, int high)
{
// 只有一个元素就不划分
if (low < high)
{
int mid = (low + high) / 2;
// 递归划分
mergeSort(arr, tempArr, low, mid);
mergeSort(arr, tempArr, mid + 1, high);
// 合并左右半区
merge(arr, tempArr, low, mid, high);
}
}

void mergeSort(vector<int>& arr)
{
int size = arr.size();
vector<int> tempArr(size);
mergeSort(arr, tempArr, 0, size - 1);
}

// 测试
int main() {
vector<int> arr = { 5,1,1,2,0,0 };
mergeSort(arr);
return 0;
}
- + \ No newline at end of file diff --git a/docs/algorithm-series/quick_sort.html b/docs/algorithm-series/quick_sort.html index 06d1bac..ecaea72 100644 --- a/docs/algorithm-series/quick_sort.html +++ b/docs/algorithm-series/quick_sort.html @@ -9,13 +9,13 @@ - +

快排

#include <iostream>
#include <vector>

using namespace std;

// 交换数组中两个元素的位置
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}

// 分割数组并返回分割点的索引
int partition(vector<int>& arr, int low, int high) {
int pivot = arr[high]; // 数组最后一位为基准值
int smallRightBound = low - 1; // 小于基准值的区域的有边界,初始时认为没有任何值小于基准值,所以边界是-1

for (int i = low; i < high; ++i)
{
if (arr[i] < pivot)
{
++smallRightBound;
swap(arr[smallRightBound], arr[i]); // 将小于基准值的值swap到右边界
}
}
swap(arr[smallRightBound + 1], arr[high]); // 将基准值放到右边界的右侧
return smallRightBound + 1;
}

// 快速排序的递归函数
void quickSort(vector<int>& arr, int low, int high) {
if (low < high) // 退出条件
{
int pivot = partition(arr, low, high);
quickSort(arr, pivot + 1, high);
quickSort(arr, low, pivot - 1);
}
}

// 快速排序的接口函数
void quickSort(vector<int>& arr) {
int size = arr.size();
quickSort(arr, 0, size - 1);
}

// 测试
int main() {
vector<int> arr = { 7, 2, 1, 6, 8, 5, 3, 4 };
quickSort(arr);

cout << "排序结果:";
for (int num : arr) {
cout << num << " ";
}
cout << endl;

return 0;
}
- + \ No newline at end of file diff --git a/docs/algorithm-series/union_find.html b/docs/algorithm-series/union_find.html index ce72b0c..7862177 100644 --- a/docs/algorithm-series/union_find.html +++ b/docs/algorithm-series/union_find.html @@ -9,14 +9,14 @@ - +

并查集

适合用于检查两个元素是否属于一个集合,以及两个元素之间是否有连通路径(连通路径还可以用广度优先搜索和深度优先搜索来查)。 一个例子:

unionFind

class UnionFind {
public:
UnionFind(int n) {
parent = vector<int>(n);
rank = vector<int>(n);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}

void uni(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] > rank[rooty]) {
parent[rooty] = rootx;
} else if (rank[rootx] < rank[rooty]) {
parent[rootx] = rooty;
} else {
parent[rooty] = rootx;
rank[rootx]++;
}
}
}

int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}

bool connect(int x, int y) {
return find(x) == find(y);
}
private:
vector<int> parent;
vector<int> rank;
};

class Solution {
public:
bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
if (source == destination) {
return true;
}
UnionFind uf(n);
for (auto edge : edges) {
uf.uni(edge[0], edge[1]);
}
return uf.connect(source, destination);
}
};
- + \ No newline at end of file diff --git "a/docs/category/cuda\347\263\273\345\210\227.html" "b/docs/category/cuda\347\263\273\345\210\227.html" index 26c3d99..cc3b60d 100644 --- "a/docs/category/cuda\347\263\273\345\210\227.html" +++ "b/docs/category/cuda\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/c\347\263\273\345\210\227.html" "b/docs/category/c\347\263\273\345\210\227.html" index 895a2fa..974ab1d 100644 --- "a/docs/category/c\347\263\273\345\210\227.html" +++ "b/docs/category/c\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/gpu\347\263\273\345\210\227.html" "b/docs/category/gpu\347\263\273\345\210\227.html" index 68c554e..fdcec0f 100644 --- "a/docs/category/gpu\347\263\273\345\210\227.html" +++ "b/docs/category/gpu\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/jointsolver\347\263\273\345\210\227.html" "b/docs/category/jointsolver\347\263\273\345\210\227.html" index 941b69d..d27dd62 100644 --- "a/docs/category/jointsolver\347\263\273\345\210\227.html" +++ "b/docs/category/jointsolver\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/math\347\263\273\345\210\227.html" "b/docs/category/math\347\263\273\345\210\227.html" index 4071514..2bb6dc6 100644 --- "a/docs/category/math\347\263\273\345\210\227.html" +++ "b/docs/category/math\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/pbd\347\263\273\345\210\227.html" "b/docs/category/pbd\347\263\273\345\210\227.html" index 81067df..3ae1430 100644 --- "a/docs/category/pbd\347\263\273\345\210\227.html" +++ "b/docs/category/pbd\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/ue\347\263\273\345\210\227.html" "b/docs/category/ue\347\263\273\345\210\227.html" index 1daddea..0081995 100644 --- "a/docs/category/ue\347\263\273\345\210\227.html" +++ "b/docs/category/ue\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/unity\347\263\273\345\210\227.html" "b/docs/category/unity\347\263\273\345\210\227.html" index 0047016..bc9541a 100644 --- "a/docs/category/unity\347\263\273\345\210\227.html" +++ "b/docs/category/unity\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/\344\273\277\347\234\237\344\270\255\347\232\204\346\234\254\346\236\204\346\250\241\345\236\213.html" "b/docs/category/\344\273\277\347\234\237\344\270\255\347\232\204\346\234\254\346\236\204\346\250\241\345\236\213.html" index 5111a22..a711382 100644 --- "a/docs/category/\344\273\277\347\234\237\344\270\255\347\232\204\346\234\254\346\236\204\346\250\241\345\236\213.html" +++ "b/docs/category/\344\273\277\347\234\237\344\270\255\347\232\204\346\234\254\346\236\204\346\250\241\345\236\213.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/\347\242\260\346\222\236\347\263\273\345\210\227.html" "b/docs/category/\347\242\260\346\222\236\347\263\273\345\210\227.html" index 53424fb..e8618aa 100644 --- "a/docs/category/\347\242\260\346\222\236\347\263\273\345\210\227.html" +++ "b/docs/category/\347\242\260\346\222\236\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git "a/docs/category/\347\256\227\346\263\225\347\263\273\345\210\227.html" "b/docs/category/\347\256\227\346\263\225\347\263\273\345\210\227.html" index fc4c2e9..de8a7d2 100644 --- "a/docs/category/\347\256\227\346\263\225\347\263\273\345\210\227.html" +++ "b/docs/category/\347\256\227\346\263\225\347\263\273\345\210\227.html" @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/collision-series/introduction.html b/docs/collision-series/introduction.html index 8323a2a..851c567 100644 --- a/docs/collision-series/introduction.html +++ b/docs/collision-series/introduction.html @@ -9,13 +9,13 @@ - +

碰撞处理简介

碰撞检测之后,我们希望让物体回到合法的位置,主要有两种处理的思路:

内点法(Interior Point Methods)

- + \ No newline at end of file diff --git a/docs/collision-series/vertex_face_collision_energy.html b/docs/collision-series/vertex_face_collision_energy.html index df69771..e3086b1 100644 --- a/docs/collision-series/vertex_face_collision_energy.html +++ b/docs/collision-series/vertex_face_collision_energy.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/constitutive-model-series/arap.html b/docs/constitutive-model-series/arap.html index 9104a72..67c16a0 100644 --- a/docs/constitutive-model-series/arap.html +++ b/docs/constitutive-model-series/arap.html @@ -9,14 +9,14 @@ - +

ARAP

参考论文:Sorkine, O. and M. Alexa (2007). As-rigid-as-possible surface modeling. In Eurog. Symposium on Geometry processing, Volume 4.

ARAP(As-Rigid-As-Possible)的定义和弹簧质点模型中的能量非常相似,被称作是“Most-Spring-Mass-Like in F-based World”,具体如下所示:

ΨARAP=μ2FRF2\Psi_{\text{ARAP}} =\frac{\mu}{2}||{\bf F}-{\bf R}||_F^2

其中的R\bf R使用极分解得到。

形变梯度的极分解

对于一个形变梯度F\bf F,我们可以对其进行分解,得到一个正交矩阵R\bf R和一个半正定矩阵S\bf S

F=RS{\bf F} = {\bf R}{\bf S}

其中正交矩阵R\bf R是形变梯度中的旋转,S\bf S则是其中的非旋转部分(缩放)。

在具体实现中,可以使用svd分解来获得正交矩阵R\bf R,然后计算ARAP的能量密度:

REAL ARAP::psi(const MATRIX3 &U, const VECTOR3 &Sigma, const MATRIX3 &V) const {
const MATRIX3 F = U * Sigma.asDiagonal() * V.transpose();
// R = U * V.transpose()
return 0.5 * _mu * (F - U * V.transpose()).squaredNorm();
}

PK1 (First Piola-Kirchhoff Stress Tensor)

ARAP的能量密度对形变梯度的一阶偏导非常简单:

PARAP(F)=μ(FR)P_{\text{ARAP}}(F) = \mu ({\bf F}-{\bf R})

Hessian

ARAP的能量密度及其对形变梯度的一阶偏导都比较简单,麻烦的是能量密度对形变梯度的二阶导(hessian),可以先尝试计算一下:

2ΨARAPF2=PARAPF=μF(FR)=μ(IRF)\frac{\partial^2\Psi_{\text{ARAP}}}{\partial {\bf F}^2}=\frac{\partial P_{\text{ARAP}}}{\partial {\bf F}}=\mu\frac{\partial}{\partial {\bf F}}({\bf F}-{\bf R})=\mu(I-\frac{\partial{\bf R}}{\partial{\bf F}})

这里面有一个正交矩阵R\bf R对形变梯度的求导,对极分解这个过程求微分是十分困难的。在Dynamic Deformables中使用了一系列不变式(invariants)来表征形变中的某些属性,然后使用不变式构成了各个本构模型的能量密度,并给出了hessian的计算通式。基于这个计算通式,我们可以成功计算出ARAP的能量密度对形变梯度的二阶偏导。(如果你还不了解这一套基于不变式的计算方法,可以查看我的这一篇文档:《不变式构造本构模型并计算Hessian》

首先使用三个不变式来重写ARAP能量:

ΨARAP=I22I1+3\Psi_{\text{ARAP}}=I_2-2I_1+3

从而可以计算得到能量密度函数相对各个不变式的一阶、二阶导:

ΨARAPI1=22ΨARAPI12=0ΨARAPI2=12ΨARAPI22=0ΨARAPI3=02ΨARAPI32=0\begin{aligned} &\frac{\partial \Psi_{\text{ARAP}}}{\partial I_1} = -2 \qquad & &\frac{\partial^2 \Psi_{\text{ARAP}}}{\partial I_1^2} = 0\\ &\frac{\partial \Psi_{\text{ARAP}}}{\partial I_2} = 1 \qquad & &\frac{\partial^2 \Psi_{\text{ARAP}}}{\partial I_2^2} = 0\\ &\frac{\partial \Psi_{\text{ARAP}}}{\partial I_3} = 0 \qquad & &\frac{\partial^2 \Psi_{\text{ARAP}}}{\partial I_3^2} = 0 \end{aligned}

最后使用计算通式就可以直接获得Hessian:

vec(2ΨF2)=i=132ΨIi2gigiT+ΨIiHi=2I9×92H1\operatorname{vec}\left(\frac{\partial^2 \Psi}{\partial {\bf F}^2}\right)=\sum_{i=1}^3\frac{\partial^2\Psi}{\partial I_i^2}{\bf g}_i{\bf g}_i^T+\frac{\partial \Psi}{\partial I_i}{\bf H}_i=2{\bf I}_{9\times9}-2{\bf H_1}

代码中的实现如下:

    MATRIX9 dPdF;
dPdF.setIdentity();
// in the paper(Dynamic Deformables, Siggraph Course 2022),
// 5.5.2 p73
dPdF *= _mu;

// calculate the hessian
dPdF -= lambda0 * (q0 * q0.transpose());
dPdF -= lambda1 * (q1 * q1.transpose());
dPdF -= lambda2 * (q2 * q2.transpose());
dPdF *= 2;
- + \ No newline at end of file diff --git a/docs/constitutive-model-series/eigen_system.html b/docs/constitutive-model-series/eigen_system.html index 2f17263..d415c90 100644 --- a/docs/constitutive-model-series/eigen_system.html +++ b/docs/constitutive-model-series/eigen_system.html @@ -9,7 +9,7 @@ - + @@ -82,7 +82,7 @@ s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7 c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z M834 80h400000v40h-400000z">1U001000100VT

后面三个特征值对应的特征矩阵为:

Qi{6,7,8}=j=02zjDjwhere{z0=σxσz+σyλiz1=σyσz+σxλiz2=λi2=σz2{\bf Q}_{i\in\{6,7,8\}}=\sum_{j=0}^2z_j{\bf D}_j \qquad \text{where} \left \{ \begin{aligned} &z_0=\sigma_x\sigma_z+\sigma_y\lambda_i\\ &z_1=\sigma_y\sigma_z+\sigma_x\lambda_i\\ &z_2=\lambda_i^2=\sigma_z^2 \end{aligned} \right.

其中,矩阵Dj{\bf D}_j为:

D0=U[100000000]VTD1=U[000010000]VTD2=U[000000001]VT{\bf D_0}={\bf U}\begin{bmatrix} 1&0&0\\0&0&0\\0&0&0 \end{bmatrix}{\bf V}^T \qquad {\bf D_1}={\bf U}\begin{bmatrix} 0&0&0\\0&1&0\\0&0&0 \end{bmatrix}{\bf V}^T \qquad {\bf D_2}={\bf U}\begin{bmatrix} 0&0&0\\0&0&0\\0&0&1 \end{bmatrix}{\bf V}^T

不过话说回来,为什么要算特征矩阵呢?不是只要把所有负的特征值映射到上就可以了么?是因为我们在映射之后还需要使用特征值和特征矩阵组合起来得到新的能量密度的Hessian去参与线性系统的解算。重组Hessian的公式为:

vec(RF)=i=02λivec(Qi)vec(Qi)T\operatorname{vec}\left(\frac{\partial {\bf R}}{\partial {\bf F}}\right)= \sum_{i=0}^2\lambda_i\operatorname{vec}({\bf Q}_i)\operatorname{vec}({\bf Q}_i)^T
- + \ No newline at end of file diff --git a/docs/constitutive-model-series/invariants.html b/docs/constitutive-model-series/invariants.html index 7c4ad13..ddd1f09 100644 --- a/docs/constitutive-model-series/invariants.html +++ b/docs/constitutive-model-series/invariants.html @@ -9,7 +9,7 @@ - + @@ -51,7 +51,7 @@ c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z M834 80h400000v40h-400000z">1U001000100VTλ3...8=0Q3...8=subspace orthogonal to Q0,1,2

那么根据特征值系统的性质就可以得到:

vec(RF)=i=02λivec(Qi)vec(Qi)T\operatorname{vec}\left(\frac{\partial {\bf R}}{\partial {\bf F}}\right)= \sum_{i=0}^2\lambda_i\operatorname{vec}({\bf Q}_i)\operatorname{vec}({\bf Q}_i)^T

结论

基于上述内容,可以给出本构模型的计算通用方法如下:

g1=vec(R)H1=i=02λivec(Qi)vec(Qi)Tg2=vec(2F)H2=2I9×9g3=[f1×f2f2×f0f0×f1]H3=[03×3f^2f^1f^203×3f^0f^1f^003×3]\begin{aligned} &{\bf g}_1=\operatorname{vec}({\bf R}) &{\bf H}_1=\sum_{i=0}^2\lambda_i\operatorname{vec}({\bf Q}_i)\operatorname{vec}({\bf Q}_i)^T\\ &{\bf g}_2=\operatorname{vec}(2{\bf F}) &{\bf H}_2=2{\bf I}_{9\times9}\\ &{\bf g}_3=\left[ \begin{array}{c|c|c} {\bf f}_1\times{\bf f}_2 & {\bf f}_2\times{\bf f}_0 & {\bf f}_0\times{\bf f}_1 \end{array} \right] &{\bf H}_3=\begin{bmatrix} {\bf 0}_{3\times3} & -\hat{\bf f}_2 & \hat{\bf f}_1\\ \hat{\bf f}_2 & {\bf 0}_{3\times3} & -\hat{\bf f}_0\\ -\hat{\bf f}_1 & \hat{\bf f}_0 & {\bf 0}_{3\times3} \end{bmatrix} \end{aligned}
  1. 使用不变式I1,I2I_1, I_2I3I_3来重写能量密度函数Ψ\Psi
  2. 计算能量密度函数相对不变量的标量导数:ΨI1\frac{\partial \Psi}{\partial I_1}2ΨI12\frac{\partial^2\Psi}{\partial I_1^2}ΨI2\frac{\partial \Psi}{\partial I_2}2ΨI22\frac{\partial^2\Psi}{\partial I_2^2}ΨI3\frac{\partial \Psi}{\partial I_3}2ΨI32\frac{\partial^2\Psi}{\partial I_3^2}
  3. 使用下面两个通式来计算能量密度的和Hessian: vec(2ΨF2)=i=132ΨIi2gigiT+ΨIiHi\operatorname{vec}\left(\frac{\partial^2 \Psi}{\partial {\bf F}^2}\right)=\sum_{i=1}^3\frac{\partial^2\Psi}{\partial I_i^2}{\bf g}_i{\bf g}_i^T+\frac{\partial \Psi}{\partial I_i}{\bf H}_i
- + \ No newline at end of file diff --git a/docs/cpp-series/rvalue_reference.html b/docs/cpp-series/rvalue_reference.html index 6750a48..17eaedc 100644 --- a/docs/cpp-series/rvalue_reference.html +++ b/docs/cpp-series/rvalue_reference.html @@ -9,7 +9,7 @@ - + @@ -18,7 +18,7 @@ move这个词看上去像是做了资源的移动,但是没有,move其实就是一个类型转换。如产品preference所说:

In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.

move(x)产生一个将亡值(xvalue)表达式来标识其参数x。他就完全等同于 static_cast<T&&>(x)。所以说,move 并不作任何的资源转移操作。单纯的move(x)不会有任何的性能提升,不会有任何的资源转移。它的作用仅仅是产生一个标识x的右值表达式。因为它会返回一个右值,所以可以和一个右值引用进行绑定:

int a = 2;
int&& rref = std::move(a);

它们有什么用?

到这里,可能会发现右值引用以及 move 好像都也没什么用,凸显不出它跟左值引用有什么特殊点。其实他们主要用在函数参数里面,下面是一个cppreference的例子:

void f(int& x)
{
std::cout << "lvalue reference overload f(" << x << ")\n";
}

void f(const int& x)
{
std::cout << "lvalue reference to const overload f(" << x << ")\n";
}

void f(int&& x)
{
std::cout << "rvalue reference overload f(" << x << ")\n";
}

int main()
{
int i = 1;
const int ci = 2;
f(i); // calls f(int&)
f(ci); // calls f(const int&)
f(3); // calls f(int&&)
// would call f(const int&) if f(int&&) overload wasn't provided
f(std::move(i)); // calls f(int&&)

// rvalue reference variables are lvalues when used in expressions
int&& x = 1;
f(x); // calls f(int& x)
f(std::move(x)); // calls f(int&& x)
}

当函数参数既有左值引用重载,又有右值引用重载的时候,我们得到重载规则如下:

  • 若传入参数是非const左值,调用非const左值引用重载函数
  • 若传入参数是const左值,调用const左值引用重载函数
  • 若传入参数是右值,调用右值引用重载函数(即使是有 const 左值引用重载的情况下) 因此,f(3)f(std::move(i))会调用f(int&&),因为他们提供的入参都是右值。

所以,通过 move 语义 和 右值引用的配合,我们能提供右值引用的重载函数。这给我们一个机会,一个可以利用右值的机会。特别是对于 xvalue(将亡值)来说,他们都是即将销毁的资源,如果我们能最大程度利用这些资源的话,这显然会极大的增加效率、节省空间。

移动构造函数

之前提到,单纯的 move 不会带来任何资源转移,那么要怎么实现转移函数呢? 考虑一个简单的string类,提供了构造函数和拷贝构造函数:

class string {
string(const char* a, length) {
m_length = length;
m_ptr = malloc(m_length);
memcpy(a, m_ptr, length);
}

string(const string& b) {
m_length = b.m_length;
m_ptr = malloc(m_length);
memcpy(m_ptr, b.m_ptr, b.length);
}

char* m_ptr;
int m_length;
};

注意,由于类中使用了指针m_ptr,所以在拷贝构造函数里面要使用深拷贝,即重新申请内存空间,并将其内存数据用memcpy拷贝过来。

如果我们在程序中需要构建一个存储了这个 string 类的数组,可能需要这么做:

vector<string> list;
string a("hello world", 11);
// 这里会调用拷贝构造函数, 将 a 对象拷贝一份,vector 再把这个副本添加到 vector 中
list.push_back(a);

加入到数组后,a这个对象就没有用了,那么我们希望能够把a对象的资源移动,而不是重新拷贝一份,这样的话相比能够提高效率。有两个问题:

  • push_back 函数如何通过入参来区分对象是应该拷贝资源还是应该移动资源
  • 如何用已有的 string 对象通过资源转移构造出另一个 string,而不是调用拷贝构造函数

关于问题一,事实上我们知道右值可以用来标识对象即将要销毁,所以只要能够区分参数是右值还是左值就可以知道用移动还是构造了。根据之前提到的重载规则,我们需要为push_back提供右值引用的重载,从而右值会优先调用到右值引用参数的函数。

void push_back(string&& v) {
// ...
}

那么要如何产生右值来调用重载的函数呢?使用 move 语义就可以,std::move(a)会产生一个将亡值。

接下来思考问题二,我们使用右值引用作为参数来重载构造函数来解决该问题:

string(string&& b) {
m_length = b.m_length;
m_ptr = b.m_ptr;
b.m_ptr = nullptr;
}

这个函数就叫做移动构造函数。它的参数是右值引用,并且从实现中可以看到,并没有像拷贝构造函数那样重新调用 malloc 申请资源,而是直接用了另一个对象的堆上的资源。也就是在移动构造函数中,才真正完成了资源的转移。根据前面左右引用函数重载的规则,要想调用移动构造函数,那么必须传入参数为右值才行。使用 move 可以将左值转换为右值:

string a("hello world", 11);
list.push_back(std::move(a));

事实上,STL中的 vector 容器已经提供了右值引用的push_back重载,不需要我们来自己实现。

什么时候需要实现移动构造函数?

对比之前给出的移动构造函数和拷贝构造函数,可以发现它们大多数地方都是相同的复制操作。其实,只要是栈上的资源,都是采用复制的方式,只有堆上的资源,才能够复用旧的对象的资源

为什么栈上的资源不能复用,而要重新复制一份?因为你不知道旧的对象何时析构,旧的对象一旦析构,其栈上所占用的资源也会完全被销毁掉,新的对象如果复用的这些资源就会产生崩溃。

为什么堆上的资源可以复用?因为堆上的资源不会自动释放,除非你手动去释放资源。可以看到,在移动构造函数特意将旧对象的m_ptr指针置为 null,就是为了预防外面对其进行 delete 释放资源。

所以说,只有当你的类申请到了堆上的内存资源的时候,才需要专门实现移动构造函数,否则其实没有必要,因为他的消耗跟拷贝构造函数是一模一样的。

- + \ No newline at end of file diff --git a/docs/cpp-series/template.html b/docs/cpp-series/template.html index 12f7408..fc24aad 100644 --- a/docs/cpp-series/template.html +++ b/docs/cpp-series/template.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/cpp-series/virtual_table.html b/docs/cpp-series/virtual_table.html index 6b5fb79..4cde34c 100644 --- a/docs/cpp-series/virtual_table.html +++ b/docs/cpp-series/virtual_table.html @@ -9,14 +9,14 @@ - +

多态和虚函数表

最近连续几次面试都问了这个问题,于是将其记录到博客中。

什么是多态

“多态”(polymorphism),是指计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单地来说,就是“在用父类指针调用函数时,实际调用的是指针指向的实际类型(子类)的成员函数”。多态性使得程序调用的函数是在运行时动态确定的,而不是在编译时静态确定的。

举例:

class Base {
public:
virtual void vir_func() { cout << "virtual function, this is base class!" << endl; }
void func() { cout << "normal function, this is base class!" << endl; }
}

class A : public Base {
virtual void vir_func() { cout << "virtual function, this is A class!" << endl; }
void func() { cout << "normal function, this is A class!" << endl; }
}

class B : public Base {
virtual void vir_func() { cout << "virtual function, this is B class!" << endl; }
void func() { cout << "normal function, this is B class!" << endl; }
}

int main() {
Base* base = new Base();
Base* a = new A();
Base* b = new B();
base->func();
a->func();
b->func();
cout << "========================================" << endl;
base->vir_func();
a->vir_func();
b->vir_func();
}

代码运行的结果为:

normal function, this is base class!
normal function, this is base class!
normal function, this is base class!
========================================
virtual function, this is base class!
virtual function, this is A class!
virtual function, this is B class!

总结一下上面的规律:当使用基类的指针调用成员函数的时候,普通函数由指针的类型来决定,虚函数由指针指向的实际类型决定。这个功能是通过虚函数表来实现的。

虚函数表

解释虚函数表的原理之前,先介绍一下类的内存分布,对于一个不包含静态变量和虚函数的类:

class noVir{
public:
void func_a();
void func_b();
int var;
}

它的内存分布是这样的:

noVirtualMemory

其中成员函数放在代码区,为该类的所有对象公有,即不管新建多少个该类的对象,所对应的都是同一个函数存储区的函数。而成员变量则为各个对象所私有,即每新建一个对象都会新建一块内存区用来存储var值。在调用成员函数时,程序会根据类的类型,找到对应代码区所对应的函数并进行调用。在文章开头的例子中,base、a、b都是Base类型的指针。调用普通函数时,程序根据指针的类型到类Base所对应的代码区找到所对应的函数,所以都调用了类Base的func函数,即指针的类型决定了普通函数的调用。

而带有虚函数的类的内存分布是这样的:

class withVir{
public:
void func_a();
virtual void func_b();
int var;
}

virtualMemory

如果使用sizeof(withVir)可以发现,withVir类会比noVir类大四个字节,多出来的这部分内容就是指针vptr,该指针叫做虚函数表指针,它指向一个名为虚函数表(vtbl)的表。 虚函数表实际上一个数组,数组里面的每个元素都是一个函数指针。上例中,虚函数表里就存储了虚函数func_b()具体实现所对应的位置。

注意,普通函数、虚函数、虚函数表都是同一个类的所有对象公有的,只有成员变量和虚函数表指针是每个对象私有的,sizeof的值也只包括vptr和var所占内存的大小,并且vptr通常会在对象内存的最起始位置。

不论一个类中有多少个虚函数,类的实例中也只会有一个vptr指针,增多虚函数,变化的是该类所对应的虚函数表的长度,即其中所存储的指向虚函数的函数指针的数量。

那么可以总结出虚函数的实现原理:通过对象内存中的vptr找到虚函数表vtbl,接着通过vtbl找到对应虚函数的实现区域并进行调用。如开头例子中,当调用vir_func函数时,分别通过base、a、b指针找到对应的vptr,然后找到各自的虚函数表vtbl,最后通过vtbl找到各自虚函数的具体实现。所以虚函数的调用时由指针所指向内存块的具体类型决定的。

构造函数和析构函数可以是虚函数吗?

给出结论:构造函数不能是虚函数,析构函数可以是、且推荐写为虚函数

为什么构造函数不能是虚函数?我们已经知道虚函数的实现则是通过对象内存中的vptr来实现的。而构造函数是用来实例化一个对象的,通俗来讲就是为对象内存中的值做初始化操作。那么在构造函数完成之前,vptr是没有值的,也就无法通过vptr找到作为虚函数的构造函数所在的代码区,所以构造函数只能作为普通函数存放在类所指定的代码区中。

为什么析构函数推荐最好设置为虚函数?如文章开头的例子中,当我们delete(a)的时候,如果析构函数不是虚函数,那么调用的将会是基类Base的析构函数。而当继承的时候,通常派生类会在基类的基础上定义自己的成员,基类的析构函数并不知道派生类中有什么新的成员,自然也无法将它们的内存释放,所以说析构函数会被推荐写为虚函数。

- + \ No newline at end of file diff --git a/docs/cuda-series/CUDA_framework.html b/docs/cuda-series/CUDA_framework.html index 33ae340..6097eaf 100644 --- a/docs/cuda-series/CUDA_framework.html +++ b/docs/cuda-series/CUDA_framework.html @@ -9,13 +9,13 @@ - +

单文件CUDA程序的基本框架

单文件情况下,一个典型的CUDA程序的基本框架如下:

// include headers
// define constant or macro
// C++ function and CUDA kernel function declare

int main() {
// 1. Allocate memory (host and device)
// 2. Initialize the data in the host
// 3. Copy some data from host to device
// 4. Call kernel function
// 5. Copy some data from device to host
// 6. Free memory (host and device)
}

// C++ function and CUDA kernel function define
- + \ No newline at end of file diff --git a/docs/cuda-series/CUDA_thread_organization.html b/docs/cuda-series/CUDA_thread_organization.html index 7751dfb..760bb51 100644 --- a/docs/cuda-series/CUDA_thread_organization.html +++ b/docs/cuda-series/CUDA_thread_organization.html @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ 所以,在上述程序中,主机只 指派了设备的一个线程,网格大小和线程块大小都是 11,即 1×1=11\times 1=1

grid&amp;block&amp;thread

这里的网格、线程块和线程大致与硬件结构中的GPU、SM(流式多处理器)和SP(流式处理器)一一对应:

hardware

调用核函数后,程序调用了一个CUDA运行时API函数cudaDeviceSynchronize(),该函数能够促使缓冲区刷新,从而将之前存放在缓冲区的输出流的内容输出出来。

多线程

一般来说,总的线程数大于计算核心数的时候才能够更充分地利用GPU中的计算资源,因为这会让计算和内存访问合理地重叠,从而减小计算核心空闲的时间。

通过改变网格大小(线程块数量)和线程块大小(单个块中线程数量)能够改变指派的线程数量。 核函数中代码的执行方式是“单指令-多线程:,即每一个线程都执行同一串指令。所以通过下述代码就可以在屏幕上打印8行同样的文字。

hello_from_gpu<<<2, 4>>>();

Tips: 从开普勒架构开始,最大允许的线程块大小是1024,一维网格的最大允许的网格大小是23112^{31}-1。虽然一个核函数允许指派的线程数目是巨大的,但是执行时能够同时活跃的线程数是由硬件(CUDA核心数)和软件(核函数中的代码)共同决定的。所以为了高效率的运行代码,除了指派足够的线程,还需要一些写核函数的技巧。

线程索引

每个线程在核函数里都有一个唯一的身份标识,该身份标识由两个参数决定:

  • blockIdx.x:这个变量代表一个当前线程在一个网格中所属于的线程块的编号,取值范围是[0, gridDim.x-1]gridDim.x即之前指派的网格大小。
  • threadIdx.x:这个变量代表一个当前线程在所属于的线程块中的编号,取值范围是[0, blockDim.x-1]blockDim.x即之前指派的线程块大小。

推广到多维网格

从之前索引中出现的.x应该就可以猜到,之前的定义的网格都是一维的结构。并且gridDimblockIdxblockDimthreadIdx都是结构体。

blockIdxthreadIdx的类型为uint3,该类型为一个结构体,具有x、y、z三个成员,其定义为:

struct __device_builtin__ uint3 {
unsigned int x, y, z;
};
typedef __device_builtin__ struct uint3 uint3

gridDimblockDim的类型为dim3,该类型也为一个结构体,具有x、y、z三个成员,并且还包括了一些成员函数。

在之前的例子中,我们使用的执行配置只使用了两个整数,这两个这整数的值会分别赋给内建变量gridDim.xblockDim.x,其他未被指定的成员则默认为1。这个情况下,网格和线程块都是一维的。我们可以给dim3的三个成员全部赋值然后实现多维的网格和线程块:

//任何未被指定的成员都会默认为1
dim3 grid_size(Gx, Gy, Gz);
dim3 block_size(Bx, By, Bz);

多维的网格和线程块本质还是一维的(和数组一样),我们可以这样计算出一个线程的一维编号(在线程块中):

int tid = threadIdx.z * blockDim.x * blockDim.y +
threadIdx.y * blockDim.x + threadIdx.x;

这里需要注意,这样的一维编号定义并不能扩展到一维线程块中去,因为各个线程块的执行是相互独立的。

instance

对于不同的代码需求,有时候可能会需要不同的符合线程索引。

Tips: CUDA中对能够定义的网格大小和线程块大小做了限制,对任何从开普勒到安培架构的GPU来说,网格大小在x、y、z这3个方向上的最大允许值分别为23112^{31}-165535655356553565535。线程块在x、y、z这3个方向上的最大允许值分别为1024、1024和64,并且要求线程块的总大小,即blockDim.xblockDim.yblockDim.z的乘积不能大于1024。

线程束(thread warp)

一个线程块还可以细分成多个线程束,一个线程束(也就是一束线程)是一个线程块里面相邻的warpSize个线程。warpSize也是一个内建变量,其值对于目前所有的GPU架构都是32。所以,一个线程束就是连续的32个线程。

一般来说,希望线程块的大小是warpSize的整数倍,否则系统会自动为剩下的n个线程补齐32-n个线程,形成一个完整的线程束,而这32-n个线程并不会被核函数调用,从而闲置。

- + \ No newline at end of file diff --git a/docs/gpu-series/draw_call.html b/docs/gpu-series/draw_call.html index 3448f3b..924ce18 100644 --- a/docs/gpu-series/draw_call.html +++ b/docs/gpu-series/draw_call.html @@ -9,13 +9,13 @@ - +
-

关于DrawCall

DrawCall 是在性能优化的时候经常讨论到的东西,DrawCall 过多会导致 CPU 需要组装很多渲染指令,准备很多数据,当超过一定数量后,会导致 CPU 与 GPU 通信出现瓶颈,影响性能。所以一般来说会需要用批量渲染(“合批”)技术,通过减少CPU向GPU发送渲染命令(DrawCall)的次数,以及减少GPU切换渲染状态的次数,尽量让GPU一次多做一些事情,来提升逻辑线和渲染线的整体效率。

tip

不过这种做法是在GPU算力没有被充分利用,而CPU把更多的时间都耗费在渲染命令的提交上时,才有意义。如果瓶颈在GPU,比如GPU性能偏差,或片段着色器过于复杂等,那么没准适当减少批处理,反而能达到优化的效果。当然,通常情况下,确实是以CPU出现瓶颈更为常见。

静态合批

静态合批可以分为预处理阶段的合并和运行阶段的批处理。

合并阶段

将符合合批条件的网格取出,对网格上的顶点进行空间变换,变换到合并根节点的坐标系下,再合并成一个新的网格。

这样做的目的是为了“固化”顶点缓冲区和索引缓冲区内的数据,使其顶点位置等信息都在相同的坐标系下。这样运行时如果需要对合并后的对象进行空间变换(手动静态合批对象的根节点可被空间变换),则无需修改缓冲区内的顶点属性,只提供根节点的变换矩阵即可。

但是合并后会膨胀场景文件,会在一定程度上影响场景的加载时间,另一方面,不同平台对于合并的顶点和索引数量有限制,超过这个限制就会合并成多个新网格

批处理阶段

如果使用相同的材质,那么在运行时就可以合批成功。手动修改材质和修改渲染器使用的网格(不再使用合批后的大网格)都会打断批处理。

小Tips

一次静态合批,并不表示一定只有一次 DrawCall 命令的调用。

合并发生后,每个参与合批的网格信息(顶点、索引等)就会被最终确定,不再被修改。当一个参与合并的单位不显示时,如被设置为隐藏或被视椎体剔除,引擎并不会修改顶点缓冲区和索引缓冲区的内容,而会拆分若干个小的 DrawCall 来分次渲染。通过调整每个 DrawCall 的索引(起始索引、索引个数)来跳过不应该被显示的单位。

staticBatch

所以也可以看见静态合批和直接使用大网格是不一样的。

其一,态合批在运行时,由于每个参与合并的对象可以通过起始索引等彼此区分,因此可以通过上述多次DrawCall的策略,实现隐藏指定的对象;而直接使用大网格,则无法做到这一点。

其二,静态合批可以有效参与CPU的视锥剔除。当有剔除发生时,被送进渲染管线的顶点数量就会减少(通过参数控制),也就意味着被顶点着色器处理的顶点会减少,提升了GPU的效率;而使用大网格渲染时,由于整个网格都会被送进渲染管线,因此每一个顶点都需要被顶点着色器处理,如果摄像机只能照到一点点,那么绝大多数参与计算的顶点最后都会被裁减掉,有一些浪费。

bigmesh batchmesh

- +

关于DrawCall

DrawCall 是在性能优化的时候经常讨论到的东西,DrawCall 过多会导致 CPU 需要组装很多渲染指令,准备很多数据,当超过一定数量后,会导致 CPU 与 GPU 通信出现瓶颈,影响性能。所以一般来说会需要用批量渲染(“合批”)技术,通过减少CPU向GPU发送渲染命令(DrawCall)的次数,以及减少GPU切换渲染状态的次数,尽量让GPU一次多做一些事情,来提升逻辑线和渲染线的整体效率。

tip

不过这种做法是在GPU算力没有被充分利用,而CPU把更多的时间都耗费在渲染命令的提交上时,才有意义。如果瓶颈在GPU,比如GPU性能偏差,或片段着色器过于复杂等,那么没准适当减少批处理,反而能达到优化的效果。当然,通常情况下,确实是以CPU出现瓶颈更为常见。

静态合批

静态合批可以分为预处理阶段的合并和运行阶段的批处理。

合并阶段

将符合合批条件的网格取出,对网格上的顶点进行空间变换,变换到合并根节点的坐标系下,再合并成一个新的网格。

这样做的目的是为了“固化”顶点缓冲区和索引缓冲区内的数据,使其顶点位置等信息都在相同的坐标系下。这样运行时如果需要对合并后的对象进行空间变换(手动静态合批对象的根节点可被空间变换),则无需修改缓冲区内的顶点属性,只提供根节点的变换矩阵即可。

但是合并后会膨胀场景文件,会在一定程度上影响场景的加载时间,另一方面,不同平台对于合并的顶点和索引数量有限制,超过这个限制就会合并成多个新网格

批处理阶段

如果使用相同的材质,那么在运行时就可以合批成功。手动修改材质和修改渲染器使用的网格(不再使用合批后的大网格)都会打断批处理。

小Tips

一次静态合批,并不表示一定只有一次 DrawCall 命令的调用。

合并发生后,每个参与合批的网格信息(顶点、索引等)就会被最终确定,不再被修改。当一个参与合并的单位不显示时,如被设置为隐藏或被视椎体剔除,引擎并不会修改顶点缓冲区和索引缓冲区的内容,而会拆分若干个小的 DrawCall 来分次渲染。通过调整每个 DrawCall 的索引(起始索引、索引个数)来跳过不应该被显示的单位。

staticBatch

所以也可以看见静态合批和直接使用大网格是不一样的。

其一,态合批在运行时,由于每个参与合并的对象可以通过起始索引等彼此区分,因此可以通过上述多次DrawCall的策略,实现隐藏指定的对象;而直接使用大网格,则无法做到这一点。

其二,静态合批可以有效参与CPU的视锥剔除。当有剔除发生时,被送进渲染管线的顶点数量就会减少(通过参数控制),也就意味着被顶点着色器处理的顶点会减少,提升了GPU的效率;而使用大网格渲染时,由于整个网格都会被送进渲染管线,因此每一个顶点都需要被顶点着色器处理,如果摄像机只能照到一点点,那么绝大多数参与计算的顶点最后都会被裁减掉,有一些浪费。

+ \ No newline at end of file diff --git a/docs/gpu-series/gems-5-1.html b/docs/gpu-series/gems-5-1.html index 8070ce8..092fe9d 100644 --- a/docs/gpu-series/gems-5-1.html +++ b/docs/gpu-series/gems-5-1.html @@ -9,14 +9,14 @@ - +

[GPU Gems 3笔记] Part V-1: Real-Time Rigid Body Simulation on GPUs

刚体模拟基础

刚体运动主要包括位移和旋转两个部分。其中位移非常简单,就是质心的移动。当一个力FF作用在一个刚体上,这会引起其动量(linear momentum)PP的变化,具体地:

dPdt=F.\frac{dP}{dt} = F.

根据动量可以获得速度:

v=PMv = \frac{P}{M}

关于旋转,这个力FF作用在刚体上同样也会带来角动量(angular momentum)LL的变化,具体地:

dLdt=r×F\frac{dL}{dt} = r \times F

其中rr是力的作用点和质心的相对位置。

根据角动量可以获得角速度ω\omega

ω=I(t)1L\omega = I(t)^{-1}L

其中I(t)1I(t)^{-1}是刚体在时间t的惯性张量,它是一个3×33\times3的矩阵。惯性张量是会随着刚体的姿态变化的,所以我们需要在每一个仿真步长对其进行更新。而具体到每个时间t的惯性张量的力,可以用下式得到:

I(t)1=R(t)I(0)1R(t)TI(t)^{-1} = R(t)I(0)^{-1}R(t)^T

其中R(t)R(t)是时间t时旋转矩阵,一般来说我们会使用四元数来存储旋转,所以这一步需要一些转换。而四元数的计算可以由角速度得到:

dq=[cos(θ/2),asin(θ/2)]dq = [\operatorname{cos}(\theta/2), a\cdot \operatorname{sin}(\theta / 2)]

其中a=ω/ωa=\omega/|\omega|是旋转轴,θ=ωt\theta = \omega t是旋转角。

刚体形状表达

为了加速碰撞运算,本文选择使用一系列粒子来表示刚体。

具体做法:首先使用3D体素来近似的表示这个rigidbody(通过划分3D网格),然后在每一个体素放一个粒子。这个生成过程可以在GPU中进行加速,首先打一组平行光到刚体上,光线到刚体上的第一个交点构成了一个深度图,第二个交点构成了第二个深度图。那么很明显第一个深度图就表示刚体正面,第二个深度图表示刚体的反面。那么我们将体素作为输入,通过检测这些体素的深度,哪些体素的深度在两个深度图之间,哪些体素就在刚体内,那么就可以在这里生成一个粒子。

depthpeeling

碰撞检测

将刚体用粒子进行表示之后,碰撞检测就被简化为了粒子之间的碰撞检测。这样有一个好处就是碰撞检测很简单,另一个好处就是碰撞检测的精度和速度都是可控的,如果要更大的精度,就可以调小粒子半径,如果要更快的速度就可以用更大的粒子半径。

另一方面,可以使用空间哈希来进行优化,通过选择合适的网格大小,能够让计算效率最大化,一般来说网格的边长是粒子的半径的两倍。

碰撞响应

粒子之间的碰撞力使用离散元(DEM)方法计算得到,这是一种用于计算颗粒材料的方法。粒子之间的斥力fi,sf_{i,s}由一个线性弹簧进行模拟,阻尼力fi,df_{i,d}用一个阻尼器来进行模拟。对于一组碰撞粒子i和j,这些力的计算方法如下:

fi,s=k(drij)rijrijfi,d=ηvijf_{i,s} = -k(d-|r_{ij}|)\frac{r_{ij}}{|r_{ij}|}\\ f_{i,d}=\eta v_{ij}

其中的k,η,d,rij,vijk,\eta,d,r_{ij},v_{ij}分别是弹簧弹性系数,阻尼系数,粒子直径,粒子的相对位置和相对速度。 同时还可以模拟剪切力,它与相对切向速度vij,tv_{ij,t}成正比:

fi,t=ktvij,tf_{i,t}=k_tv_{ij,t}

其中这个相对切向速度的计算方法为:

vij,t=vij(vijrijrij)rijrijv_{ij,t}=v_{ij}-\left(v_{ij}\cdot\frac{r_{ij}}{|r_{ij}|}\right)\frac{r_{ij}}{|r_{ij}|}

通过将力累积就可以获得作用与当前刚体的碰撞力和力矩:

Fc=iRigidBody(fi,s+fi,d+fi,t)Tc=iRigidBody(ri×(fi,s+fi,d+fi,t))F_c = \sum_{i\in RigidBody}(f_{i,s}+f_{i,d}+f_{i,t})\\ T_c = \sum_{i\in RigidBody}(r_i\times (f_{i,s}+f_{i,d}+f_{i,t}))

其中rir_i是当前粒子i相对刚体质心的相对位置。

GPU上的刚体模拟

GPUFramework

具体算法的流程图如上,主要包括:

  1. Computation of particle values
  2. Grid generation
  3. Collision detection and reaction
  4. Computation of momenta
  5. Computation of position and quaternion

其中大部分工作都是内存排不相关的处理,暂不做过多了解。

应用场景

  1. 用于模拟颗粒材料,力直接驱动粒子的位移
  2. 流体模拟,加速SPH的粒子邻近搜索
  3. 流固耦合
- + \ No newline at end of file diff --git a/docs/gpu-series/gems-5-4.html b/docs/gpu-series/gems-5-4.html index 0fd0a30..59ad3bd 100644 --- a/docs/gpu-series/gems-5-4.html +++ b/docs/gpu-series/gems-5-4.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

[GPU Gems 3笔记] Part V-4: Broad-Phase Collision Detection with CUDA

常用Broad-Phase碰撞检测算法

Sort and Sweep

将各个物体的Bounding Volume投影到x、y或者z轴上,在各个轴上生成一个一维碰撞区间[bi,ei][b_i,e_i]。如果这个碰撞区间在三个轴上都没有相交,那么就不可能产生碰撞。具体的检测过程可以对碰撞区间进行排序来完成。很适合用于静态场景的检测。

sap

Spatial Subdivision

空间哈希,一般来说网格会比最大的那个物体要大一些。通过选择一个合适的网格大小,对一个物体,就只需要检测他所在的网格以及与其相邻的网格。如果场景中的物体大小差别巨大,那就效率就会降低。 在比较理想的情况下,碰撞检测仅在以下情况才会进行:物体i和物体j出现在同一个网格中,并且至少有一个物体的中心点也在这个网格中时,才会进行碰撞检测。

spatialhash

Parallel Spatial Subdivision

并行化会使算法变得稍微复杂。

第一个复杂之处在于,如果单个对象与多个网格重叠且这些网格被并行处理,那么该对象可能同时参与多个碰撞测试。因此,必须存在某种机制,以防止两个或多个计算线程同时更新同一对象的状态。为了解决这个问题,我们需要控制每个网格的大小(大于最大物体的bounding volume),由于每个网格的大小至少与计算中最大对象的边界体积相同,因此只要在计算过程中处理的每个网格都与同一时间处理的其他网格至少相隔一个网格,就可以保证每个对象只有一个包含它的网格会被更新。 在2D中,这意味着需要进行四个计算过程来覆盖所有可能的网格;在3D中,需要进行八个过程。

parallelsh

- + \ No newline at end of file diff --git a/docs/gpu-series/gems-5.html b/docs/gpu-series/gems-5.html index 418140c..528554e 100644 --- a/docs/gpu-series/gems-5.html +++ b/docs/gpu-series/gems-5.html @@ -9,13 +9,13 @@ - +

[GPU Gems 3笔记] Part V: Physics Simulation

章节原文内容来自于:https://developer.nvidia.com/gpugems/gpugems3/part-v-physics-simulation

物理模拟是一种高度数据并行化和计算密集型的任务,适合用于GPU计。另一方面,物理仿真计算得到的结果也会直接被GPU用于可视化,所以直接在GPU中进行计算,在graphics memory中生成结果也是很有意义的。

GPU Gems3关于物理模拟的章节和对应笔记:

- + \ No newline at end of file diff --git a/docs/intro.html b/docs/intro.html index a47c9f6..30de987 100644 --- a/docs/intro.html +++ b/docs/intro.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/joint_solver-series/fake_cloth.html b/docs/joint_solver-series/fake_cloth.html index bdde87e..e1836b5 100644 --- a/docs/joint_solver-series/fake_cloth.html +++ b/docs/joint_solver-series/fake_cloth.html @@ -9,13 +9,13 @@ - + - + \ No newline at end of file diff --git a/docs/joint_solver-series/joint_base.html b/docs/joint_solver-series/joint_base.html index c8ee0c1..4ded379 100644 --- a/docs/joint_solver-series/joint_base.html +++ b/docs/joint_solver-series/joint_base.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ A点是世界坐标原点,B是骨架的根节点,C是手肘关节节点的父节点,D是手肘关节节点。我们考虑D的变换,那么有:

  • D相对A的变换就是世界空间下的变换
  • D相对C的变换就是本地空间下的变换
  • D相对B的变换就是组件空间下的变换

需要注意,全文所有求解过程都是在组件空间下完成的。

成员函数

初始化 Init()

首先需要对求解器去做一个初始化,在基类中需要初始化的内容不多,将成员变量赋初始值/分配容量就行。由于不同的求解器可能有不同的属性要进行初始化,所以应当被声明为虚函数。

virtual void Init() {
_jointPosition.resize(_dataInterface->GetJointNumber());
_jointRotations.resize(_dataInterface->GetJointNumber());
_subStep = _dataInterface->GetSubStep();
}

_alpha在什么地方初始化?

初始化Transform InitTransform()

接下来在求解之前,首先我们要获得当前骨架中各个骨骼的位置和旋转信息,赋值给Joint Solver的成员变量。

void InitTransform() {
for (int jointIndex = 0; jointIndex < jointNumber; jointIndex++) {
_jointPositions[jointIndex] = _dataInterface->GetInputJointPosition(jointIndex);
_jointRotations[jointIndex] = _dataInterface->GetInputJointRotation(jointIndex);
}
}

重置约束 ResetConstrains()

然后根据在InitTransform()中更新的骨架初始位置和旋转对场景中存在的各个约束进行重置。另外,在重置约束的时候,可能时间步长也是需要的信息之一。

void ResetConstrains() {
for (int constrainIndex = 0; constraintIndex < constraintNumber; constraintIndex++) {
_dataInterface->GetConstrain(constrainIndex)->Reset(_jointPositions, _jointRotations, _deltaT);
}
}

隐式求解 SolveImpl()

接下来就可以直接开始求解得到新的位置和旋转了,这一块就需要具体的求解器,即框架的子类,来具体实现了,我们一般可以定义其为纯虚函数。

virtual void SolveImpl() = 0;

为约束和碰撞求解做准备 PrepareSubStep()

和模拟里面的思路类似,首先先计算得到了一个单次更新的位置和旋转之后,就可以进行碰撞和约束求解(碰撞也定义为一种约束)了。 那么在进行subStep的计算之前,首先要做一些准备,之前的求解中,我们只计算并且更新了骨架中的骨骼的位置和旋转,接下来我们要在这里一并把骨骼上附加的碰撞体也去做对应的位置和旋转的更新。

更新的过程主要是遍历场景中所有的碰撞体,然后记录下这个碰撞体在时间步长开始前、以及SolveImpl之后的碰撞体的Transform,然后使用alpha进行插值,获得在当前这个subStep的Transform估计值,以参与后续的碰撞和约束相关的运算。

各个求解器可能会在该函数中做一些其他运算,所以定义为虚函数。

virtual void PrepareSubStep() {
for (int colliderIndex = 0; colliderIndex < colliderNumber; colliderIndex++) {
Transform colliderTransform, preColliderTransform;
Collider* collider = _dataInterface->GetCollider(colliderIndex);
// 在设置骨骼的碰撞体的时候,该碰撞体本身可能就会有一个和骨骼之间的relative transform
// 所以在collider中会设置一个变量attachOffsetTransform,将这个relative transform给记录下来
// 同样,collider中也会有一个变量attachBoneIndex来记住它所依附的骨骼的编号
colliderTransform = collider->attachOffsetTransform *
_dataInterface->GetJointTransform(collider->attachBoneIndex);
preColliderTransform = collider->attachOffsetTransform *
_dataInterface->GetInputJointTransform(collider->attachBoneIndex);
// 插值,找到对应的时刻的Transform, _alpha在Solve中进行计算
colliderTransform = Lerp(colliderTransform, preColliderTransform, 1.0 - _alpha);
}
}

求解约束 SolveConstrains()

接下来就要对约束进行求解了,遍历每个约束然后计算求解即可,各个求解器可能会在该函数中做一些其他运算,所以定义为虚函数。

virtual void SolveConstrains() {
for (int constrainIndex = 0; constraintIndex < constraintNumber; constraintIndex++) {
_dataInterface->GetRegularConstrain(constrainIndex)->SolveConstrain(_dataInterface, _jointPositions, _jointRotations, _deltaT);
}
}

在函数中我们还为Constrain提供了数据的接口_dataInterface,这是因为约束需要对包括joint的位置和旋转等数据进行读写操作,所以我们需要将接口交给它(类似权限交接,暂时给予)。

求解碰撞约束 SolveCollisions()

碰撞也是一类约束,所以操作和约束求解一样,遍历每个约束然后计算求解即可,各个求解器可能会在该函数中做一些其他运算,所以定义为虚函数。

virtual void SolveCollisions() {
for (int constrainIndex = 0; constraintIndex < constraintNumber; constraintIndex++) {
_dataInterface->GetCollisionConstrain(constrainIndex)->SolveConstrain(_dataInterface, _jointPositions, _jointRotations, _deltaT);
}
}

更新subStep相关数据 UpdateBySubStep()

TODO.... 基类中无实现,定义为虚函数在子类中重写,但是因为不是必须环节,所以不是纯虚函数。

void UpdateBySubStep() {}

更新关节的变换 UpdateJointTransforms()

之前的计算都只算出了新的joint位置和旋转,而并没有计算出最终的变换,我们在本函数中进行Transform的更新。

这里主要注意一点,在Solver的结算过程中,为了让求解过程更加简化和自由,我们并没有考虑各个节点之间的连接关系,所以在这个函数中很重要的一个点就是维持各个节点之间的连接关系,具体请看代码的实现。

// 首先用一个新变量将之前的求解结果全部存储下来
vector<Transform> newJointTransforms(_dataInterface->GetJointNumber(), Transform::Identity);
for (int jointIndex = 0; jointIndex < JointNumber; jointIndex++) {
newJointTransforms[jointIndex].SetTranslation(_jointPositions[jointIndex]);
newJointTransforms[jointIndex].SetRotation(_jointRotations[jointIndex]);
}

// 接下来开始恢复骨架节点之间的连接关系
// 首先以关节中的链为单位,定位到链结构中的骨骼节点
int chainNumber = _dataInterface->GetChainNumber();
for (int chainIndex = 0; chainIndex < chainNumber; chainIndex++) {
int chainLength = _dataInterface->GetChainLength(chainIndex);
for (int chainNodeIndex = 0; chainNodeIndex < chainLength; chainNodeIndex++) {
int jointIndex = _dataInterface->GetChainNodeIndex(chainIndex, chainNodeIndex);
// 接下来找到定位到的节点的子节点(如果该节点是多个链的根节点怎么办?)
int childIndex = _dataInterface->GetChild(jointIndex);
Transform jointTransform = _dataInterface->GetJointTransform(jointIndex);

// 如果joint已经是叶节点了,没有需要恢复的东西,开始其他链的修正
if (childIndex == -1)
continue;

// 找到子节点的初始变换
Transform childTransform = _dataInterface->GetJointTransform(childIndex);

// 找到更新后,joint-child链的旋转关系
VECTOR vecBefore = chileTransform.GetTranslation() - jointTransform.GetTranslation();
VECTOR vecAfter = newJointTransform[childIndex].getTranslation() - newJointTransform[jointIndex].getTranslation();
QUAT parentRotation = QUAT::FindBetween(vecBefore, vecAfter);

// 更新组件空间的旋转,恢复链接关系
jointTransform.setRotation(parentRotation * newJointTransform[jointIndex].getRotation());
jointTransform.setTranslation(newJointTransform[jointIndex].GetTranslation());

// 应用更新
newJointTransform[jointIndex] = jointTransform;
}
}

// 将newJointTransform中的信息更新到Simulation中的信息里面去
for (int jointIndex = 0; jointIndex < JointNumber; jointIndex++) {
Transform jointTransform = newJointTransform[jointIndex];
_dataInterface->SetJointTransform(jointIndex, jointTransform);
// 用作下一步迭代的输入
_dataInterface->SetInputJointTransform(jointIndex, jointTransform);
}

到这里需要的功能基本都实现了,接下来使用一个Solve()来包装前边的内容,让求解的流程有一个更清晰的逻辑链。

求解 Solve()

基本的逻辑如下:

void Solve() {
InitTransform();
ResetConstrains();
SolveImpl();

for (int i = 0; i < _subStep; i++) {
// 终于到了,alpha的更新公式
_alpha = (i + 1.0f) / _subStep;
PrepareSubStep();
SolveConstrains();
SolveCollisions();
}

if (_subStep > 1)
UpdateSubStep();

UpdateJointTransforms();
}
- + \ No newline at end of file diff --git a/docs/joint_solver-series/kawaii.html b/docs/joint_solver-series/kawaii.html index a85e1ed..474cea4 100644 --- a/docs/joint_solver-series/kawaii.html +++ b/docs/joint_solver-series/kawaii.html @@ -9,7 +9,7 @@ - + @@ -17,7 +17,7 @@

Kawaii Joint Solver

Kawaii的网址:link

本文中的Kawaii Joint Solver为上一章节Joint Solver 整体框架中提到的类(我们将其命名为JointSolverBase)的子类。

class KawaiiJointSolver : public JointSolverVase { ... }

Kawaii本质上是一种利用父关节带动子关节运动的一种dynamics,结果会叠加在原动画上。

成员变量

首先我们可以先将Kawaii求解过程中需要的骨骼节点数据打包成一个结构体,方便后面的讲解:

struct KawaiiBoneData {
int boneIndex = -1;
int parentIndex = -1;
VECTOR3 location;
VECTOR3 preLocation;
VECTOR3 poseLocation;
KawaiiJointAttr* jointAttr;
}

其中需要注意,其中的locationpreLocation指的是仿真得到的数据,poseLocation是动画的数据。要注意区分仿真动画。 然后jointAttr是一个指向Kawaii求解器所管理的所有joint的相关属性,这些属性一般是由用户设定的,我们使用jointAttr这个指针来访问它。他一般包括下面这些内容:

struct KawaiiJointAttr {
float damping;
float worldLocDamping;
float worldRotDamping;
float stiffness;
float airflowDamping;
float limitAngle;
}

KawaiiJointSolver的成员变量包括:

    array<KawaiiBoneData>   _kawaiiBones;
VECTOR3 _compMoveVector;
QUAT _compMoveRotation;
Transform _preCompTransform;
float _exponent;
float _teleportDisThreshold; // 根节点的位移上限
float _teleportRotThreshold; // 根节点的旋转上限
float _windSpeed;
VECTOR3 _windDirection;
VECTOR3 _gravity;

// 基类中的成员变量:
// vector<VECTOR3> _jointPositions;
// vector<QUAT> _jointRotations;
// int _subStep;
// float _deltaT;
// float _alpha;
// DataInterface* _dataInterface;

成员函数

为了更好的实现功能,Kawaii求解器中定义了一些功能函数,我们首先对它们进行一些介绍。之后再来看看基类中的虚函数是怎么被实现的。

初始化骨骼节点的数据 InitBoneData()

之前我们将所有的骨骼节点的数据都封装进了一个结构体KawaiiBoneData中,所以首先我们当然需要对其进行一次初始化。

for (int jointIndex = 0; jointIndex < JointNumber; jointIndex++) {
int parentIndex = _dataInterface->GetJointParent(jointIndex);
KawaiiBoneData newBone;
newBone.boneIndex = jointIndex;
Transform boneTransform = _dataInterface->GetJointTransform(jointIndex);
newBone.location = boneTransform.GetLocation();
newBone.poseLocation = newBone.location;
newBone.preLocation = newBone.location;
newBone.jointAttr = (KawaiiJointAttr*)_dataInterface->GetJointAttr(jointIndex);
if (parentIndex < 0) {
// 如果不存在父节点
newBone.parentIndex = -1;
} else {
newBone.parentIndex = parentIndex;
}
_kawaiiBones.add(newBone);
}

修正根节点过大的位移和旋转 UpdateMovement(Transform& transform)

为了让整个模型更加稳定,我们会人为约束两帧之间根节点的位移和旋转,这个约束的阈值就是成员变量中的_teleportDisThreshold_teleportRotThreshold。 函数的输入transform就是当前根节点的运动信息,用于和_preCompTransform进行比对来找到两帧之间的位移和旋转,_preCompTransform的初始化在函数Init()中。

怎么找到相对的位移和旋转?

知道节点在某个坐标系下,两帧的旋转和位移之后,怎么计算得到两帧之间相对的旋转和位移?后一帧的位移/旋转叠加上前一帧的位移/旋转的逆运动即可。

void UpdateMovement(Transform& transform) {
// InverseTransformPosition 叠加逆位移运动
_compMoveVector = transform.InverseTransformPosition(_preCompTransform.getLocation());
if (_compMoveVector.Squared() > _teleportDisThreshold * _teleportDisThreshold) {
_compMoveVector = VECTOR3(0.0, 0.0, 0.0);
}
// InverseTransformRotation 叠加逆旋转运动
_compMoveRotation = transform.InverseTransformRotation(_preCompTransform.getRotation());
if (_compMoveRotation.GetAngle() > _compMoveRotation * _compMoveRotation) {
_compMoveRotation = QUAT::Identity;
}
// 更新_preCompTransform
// TODO 为什么不用_compMoveVector和_compMoveRotation构成新的_preCompTransform?
_preCompTransform = transform;
}

计算惯性和风力 UpdatePose()

如标题所示,计算惯性和风力并更新,有点类似于XPBD在求解约束之前,要先进行一次x=x+vtx=x+vt

for (int jointIndex = 0; jointIndex < JointNumber; jointIndex++) {
// 首先先将对应骨骼的数据取出来,由于我们是要对数据进行更新的,所以需要用引用
auto& bone = _kawaiiBones[jointIndex];
// 获取动画数据和仿真数据
bone.poseLocation = _dataInterface->GetJointTransform(bone.boneIndex);
bone.location = _dataInterface->GetJointSimTransform(bone.boneIndex);

if (bone.parentIndex < 0) {
// 如果没有父节点,那就让其直接跟随用户k帧的动画
bone.preLocation = bone.location;
bone.location = bone.poseLocation;
continue;
}

// 更新风力和damping的作用
VECTOR3 velocity = (bone.location - bone.preLocation) / _deltaT;
bone.preLocation = bone.location;
velocity *= (1.0f - bone.jointAttr->damping);

VECTOR3 windVelocity = _windSpeed * _windDirection;
velocity += windVelocity;

bone.location += velocity * _deltaT;

// 跟随根节点进行运动
bone.location += _compMoveVector * (1.0 - bone.jointAttr->worldLocDamping);
bone.location += (_compMoveRotation.RotateVector(bone.preLocation)-bone.preLocation) * (1.0 - bone.jointAttr->worldRotDamping);

// 重力
bone.location += 0.5 * _gravity * _deltaT * _deltaT;
}

约束仿真结果和动画之间的角度 AdjustAngle(...)

有时候动画师并不希望使用纯仿真的结果,他们希望动画的一切效果还是以自己k出来的为主,仿真只是锦上添花,所以说我们需要将仿真计算出来的结果进行约束。

void AdjustAngle(float limitAngle, VECTOR3& location, VECTOR3& parentLocation, VECTOR3 poseLocation, VECTOR3 parentPoseLocation) {
VECTOR3 boneDir = (location - parentLocation).Normalized();
VECTOR3 poseDir = (poseLocation - parentPoseLocation).Normalized();
VECTOR3 axis = VECTOR3::CrossProduct(poseDir, boneDir);
float angle = Atan2(axis.Length(), VECTOR3::DotProduct(poseDir, boneDir));
float angleOverLimit = angle - limitAngle;

if (angleOverLimit > 0.0f) {
// 将多余的部分转回去
boneDir = boneDir.RotateAngleAxis(_angleOverLimit, axis);
location = boneDir * (location - parentLocation).Length() + parentLocation;
}
}

接下来就是基类原有框架下的重写部分了。

初始化 Init()

除开基类已有的功能外,Kawaii的初始化中的额外工作就是将所有骨骼节点数据调用InitBoneData()进行初始化。

JointSolverBase::Init();
_kawaiiBones.Empty(); // 先清空
if (_kawaiiBones.Num() == 0) {
InitBoneData();
_preCompTransform = _dataInterface->GetComponentTransform();
}

隐式求解 SolveImpl()

Kawaii的隐式求解的过程大致如下:

  • 首先获取当前的根节点的位移和旋转,如果过大就将其修正
  • 应用惯性、重力和风力
  • 将仿真结果叠加到动画效果上
void SolveImpl() {
Transform componentTransform = _dataInterface->GetComponentTransform();
UpdateMovement(componentTransform);
UpdatePose();

int chainNumber = _dataInterface->GetChainNumber();
for (int chainIndex = 0; chainIndex < chainNumber; chainIndex++) {
int chainLength = _dataInterface->GetChainLength(chainIndex);
for (int chainNodeIndex = 0; chainNodeIndex < chainLength; chainNodeIndex++) {
int jointIndex = _dataInterface->GetChainNodeIndex(chainIndex, chainNodeIndex);
// 首先先将对应骨骼的数据取出来,由于我们是要对数据进行更新的,所以需要用引用
auto& bone = _kawaiiBones[jointIndex];
if (bone.parentIndex < 0)
continue;

auto& parentBone = _kawaiiBones[bone.parentIndex];
VECTOR3 poseLocation = bone.poseLocation;
VECTOR3 parentPoseLocation = parentBone.poseLocation;

// 如果没有仿真,本节点应该在的位置
VECTOR3 idealLocation = parentBone.location + (poseLocation - parentPoseLocation);
// 根据关节的刚性进行位置修正
bone.location += (idealLocation - bone.location) * (1.0 - pow(1.0 - bone.jointAttr->stiffness, _exponent));

// 修正角度
AdjustAngle(bone.jointAttr->limitAngle,
bone.location, parentBone.location,
bone.poseLocation, parentBone.poseLocation);

// 恢复长度
float boneLength = (poseLocation - parentPoseLocation).Length();
bone.location = (bone.location - parentBone.location).Normalized() * boneLength + parentBone.location;
}
}
}

为约束和碰撞求解做准备 PrepareSubStep()

在基类的方法中,对碰撞体的transform进行了插值,这里我们还需要对我们定义的骨骼节点数据进行插值:

virtual void PrepareSubStep() {
JointSolverBase::PrepareSubStep();
for (int jointIndex = 0; jointIndex < JointNumber; jointIndex++) {
auto* bone = &_kawaiiBones[jointIndex];
VECTOR3 targetLocation = bone->location;
VECTOR3 preLocation = bone->preLocation;
VECTOR3 dir = (targetLocation - preLocation) / (float)_subStep;
_jointPositions[jointIndex] += dir;
}
}

求解约束 SolveConstrains()

在约束求解以后,还需要对角度进行一次约束。

void SolveConstrains() {
JointSolverBase::SolveConstrains();
int chainNumber = _dataInterface->GetChainNumber();
for (int chainIndex = 0; chainIndex < chainNumber; chainIndex++) {
int chainLength = _dataInterface->GetChainLength(chainIndex);
for (int chainNodeIndex = 0; chainNodeIndex < chainLength; chainNodeIndex++) {
int jointIndex = _dataInterface->GetChainNodeIndex(chainIndex, chainNodeIndex);
auto& bone = _kawaiiBones[jointIndex];
if (bone.parentIndex < 0)
continue;
int parentIndex = bone.parentIndex;
auto& parentBone = _kawaiiBones[parentIndex];
AdjustAngle(bone.jointAttr->limitAngle,
_jointPositions[jointIndex], _jointPositions[parentIndex],
bone.poseLocation, parentBone.poseLocation);
}
}
}

基于上述所有内容,将对应的函数套用到JointSolverBase的求解框架中去,就可以使用Kawaii Joint Solver进行求解了。

- + \ No newline at end of file diff --git a/docs/math-series/tensor_stuff.html b/docs/math-series/tensor_stuff.html index 504c572..ec6ba3e 100644 --- a/docs/math-series/tensor_stuff.html +++ b/docs/math-series/tensor_stuff.html @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ 形变梯度对位置中的每一个分量进行求导可以得到一个3x3的矩阵,那么完整的偏导就是12个3x3的矩阵的几何,这是一个三维张量,其维度为R3×3×12\mathbb{R}^{3\times3\times12}3ordertensor

这里只展示了四个矩阵,实际上应该有12个

我们也可以将其视作一个由矩阵构成的向量,用我们更熟悉的二维的形式来表示三维张量: 3ordervector

这里只展示了四个矩阵,实际上应该有12个

从张量计算的角度出发,之前的计算公式可以重写为:

Ψx=FxΨF\frac{\partial \Psi}{\partial \bf x}=\frac{\partial \bf F}{\partial \bf x}:\frac{\partial \Psi}{\partial \bf F}

这里的“:”代表张量缩并。

该重写方法仅适用于当前场景

该方法只是一个方便理解和代码实现的一个小技巧,并不是一个定理。据我现在的了解,目前这个结论只能够适用于三维张量和二维矩阵这一个场景中,实际情况需要实际分析,绝对不可以盲目套用!

三维张量缩并(张量形式)

张量缩并是向量点积的推广,向量点积是:

xTy=[x0x1x2]T[y0y1y2]=x0y0+x1y1+x2y2{\bf x}^T{\bf y}= \begin{bmatrix} x_0 \\ x_1 \\ x_2 \end{bmatrix}^T \begin{bmatrix} y_0 \\ y_1 \\ y_2 \end{bmatrix}= x_0y_0+x_1y_1+x_2y_2

矩阵缩并所做的事情也差不多:

A:B=[a00a01a10a11]:[b00b01b10b11]=a00b00+a01b01+a10b10+a11b11{\bf A}:{\bf B}= \begin{bmatrix} a_{00} & a_{01} \\ a_{10} & a_{11} \end{bmatrix}: \begin{bmatrix} b_{00} & b_{01} \\ b_{10} & b_{11} \end{bmatrix}= a_{00}b_{00}+a_{01}b_{01}+a_{10}b_{10}+a_{11}b_{11}

我们再进一步扩展到张量:

A:B=[[a0a1a2a3][a4a5a6a7][a8a9a10a11]]:[b0b1b2b3]=[a0b0+a1b1+a2b2+a3b3a4b0+a5b1+a6b2+a7b3a8b0+a9b1+a10b2+a11b3]{\bf A}:{\bf B}= \begin{bmatrix} \begin{bmatrix} a_0 & a_1\\ a_2 & a_3 \end{bmatrix}\\ \\ \begin{bmatrix} a_4 & a_5\\ a_6 & a_7 \end{bmatrix} \\ \\ \begin{bmatrix} a_8 & a_9\\ a_{10} & a_{11} \end{bmatrix} \end{bmatrix}: \begin{bmatrix} b_{0} & b_{1} \\ b_{2} & b_{3} \end{bmatrix}= \begin{bmatrix} a_0b_0+a_1b_1+a_2b_2+a_3b_3\\ a_4b_0+a_5b_1+a_6b_2+a_7b_3\\ a_8b_0+a_9b_1+a_{10}b_2+a_{11}b_3 \end{bmatrix}

flattened

可能这样的计算方法对我们来说还不够容易接受,那么其实不论是几维的张量,我们都可以将其平坦化(flattened,对高维张量来说)或向量化(vectorized,对矩阵来说),然后我们就可以在熟悉的领域去进行计算了。

平坦化和向量化

这里我们引入vec()\text{vec}(\cdot)算子,它能够将将矩阵变换为向量,将任意高阶张量变换为矩阵。首先我们来看看他是怎么向量化矩阵的:

vec(A)=vec([a0a1a2a3])=[a0a2a1a3]\text{vec}({\bf A})=\text{vec}\left( \begin{bmatrix} a_0 & a_1\\ a_2 & a_3 \end{bmatrix} \right)= \begin{bmatrix} a_0\\ a_2 \\ a_1 \\ a_3 \end{bmatrix}

他的逻辑就是:我们将一个矩阵中所有的列按照顺序堆叠起来,其对应的代码是:

static Vector9 flatten (const Matrix3x3& A) {
Vector9 flattened ;
int index = 0;
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++, index++)
flattened [index] = A(x, y);
return flattened;
}

接下来试试将高维张量给平坦化:

vec(A)=vec[[A][B][C]]=[vec(A)vec(B)vec(C)]\text{vec}({\bf \mathbb{A}})=\text{vec}\begin{bmatrix} \begin{bmatrix} {\bf A} \end{bmatrix} \\ \\ \begin{bmatrix} {\bf B} \end{bmatrix}\\ \\ \begin{bmatrix} {\bf C} \end{bmatrix} \end{bmatrix} = \begin{bmatrix} \text{vec}({\bf A}) & \text{vec}({\bf B}) & \text{vec}({\bf C}) \end{bmatrix}

首先我们先将一个三维张量按照之前的思路,将所有列堆叠起来构成单独的一列(我们这里总共就一列),之后再放倒,转换为一行,然后对其中的元素逐一进行向量化:

vec[[a0a1a2a3][a4a5a6a7][a8a9a10a11]]=[vec[a0a1a2a3]vec[a4a5a6a7]vec[a8a9a10a11]]=[a0a4a8a2a6a10a1a5a9a3a7a11]\text{vec} \begin{bmatrix} \begin{bmatrix} a_0 & a_1\\ a_2 & a_3 \end{bmatrix} \\ \\ \begin{bmatrix} a_4 & a_5\\ a_6 & a_7 \end{bmatrix} \\ \\ \begin{bmatrix} a_8 & a_9\\ a_{10} & a_{11} \end{bmatrix} \end{bmatrix} = \begin{bmatrix} \text{vec} \begin{bmatrix} a_0 & a_1\\ a_2 & a_3 \end{bmatrix} & \text{vec} \begin{bmatrix} a_4 & a_5\\ a_6 & a_7 \end{bmatrix} & \text{vec} \begin{bmatrix} a_8 & a_9\\ a_{10} & a_{11} \end{bmatrix} \end{bmatrix}= \begin{bmatrix} a_0 & a_4 &a_8\\ a_2 & a_6 & a_{10}\\ a_1 & a_5 & a_9\\ a_3 & a_7 & a_{11} \end{bmatrix}

三维张量缩并(平坦化形式)

刚刚介绍的一大堆平坦化的东西虽然看起来能够将复杂数据转化为一种更加清晰的形式,但是实际有什么用呢?至少在张量缩并的计算中,我们可以直接给出结论:

A:B=vec(A)Tvec(B){\bf A}:{\bf B} = \text{vec}({\bf A})^T\text{vec}({\bf B})

下面来进行一次推导:

vec(A)Tvec(B)=(vec[[a0a1a2a3][a4a5a6a7][a8a9a10a11]])Tvec[b0b1b2b3]=[a0a4a8a2a6a10a1a5a9a3a7a11]T[b0b2b1b3]=[a0b0+a1b1+a2b2+a3b3a4b0+a5b1+a6b2+a7b3a8b0+a9b1+a10b2+a11b3]=A:B\begin{aligned} \text{vec}({\bf A})^T\text{vec}({\bf B})&= \left( \text{vec} \begin{bmatrix} \begin{bmatrix} a_0 & a_1\\ a_2 & a_3 \end{bmatrix} \\ \\ \begin{bmatrix} a_4 & a_5\\ a_6 & a_7 \end{bmatrix} \\ \\ \begin{bmatrix} a_8 & a_9\\ a_{10} & a_{11} \end{bmatrix} \end{bmatrix} \right)^T \text{vec} \begin{bmatrix} b_0 & b_1 \\ b_2 & b_3 \end{bmatrix} \\ &=\begin{bmatrix} a_0 & a_4 &a_8\\ a_2 & a_6 & a_{10}\\ a_1 & a_5 & a_9\\ a_3 & a_7 & a_{11} \end{bmatrix}^T \begin{bmatrix} b_0 \\ b_2 \\ b_1 \\ b_3 \end{bmatrix}\\ &=\begin{bmatrix} a_0b_0+a_1b_1+a_2b_2+a_3b_3\\ a_4b_0+a_5b_1+a_6b_2+a_7b_3\\ a_8b_0+a_9b_1+a_{10}b_2+a_{11}b_3 \end{bmatrix}\\ &={\bf A}:{\bf B} \end{aligned}
仅可用于三维张量的缩并

据我现在的了解,目前这个结论只能够适用于三维张量和二维矩阵这一个场景中,实际情况需要实际分析,绝对不可以盲目套用!在下面的四维张量的计算中就已经不能适用了,但是flatten的思路是可以使用的。

拓展到力的微分(四维张量)

在隐式积分求解的过程中需要计算能量密度的hessian,也就是力的微分取负,其公式如下:

2Ψx2=FxT2ΨF2Fx\frac{\partial^2\Psi}{\partial {\bf x}^2} = \frac{\partial {\bf F}}{\partial {\bf x}}^T\frac{\partial^2 \Psi}{\partial {\bf F}^2}\frac{\partial {\bf F}}{\partial {\bf x}}

这里面需要注意的是2ΨF2\frac{\partial^2 \Psi}{\partial {\bf F}^2}这一项,能量密度对形变梯度求一次导可以得到一个3x3的矩阵,再求一次导就是一个3x3x3x3的四维张量了。但是实际上和三维张量也没有什么区别,它的平坦化为:

A=[[a0a1a2a3][a4a5a6a7][a8a9a10a11][a12a13a14a15]]=[[A00][A01][A10][A11]]{\bf A} = \begin{bmatrix} \begin{bmatrix} a_0 & a_1\\ a_2 & a_3 \end{bmatrix} & \begin{bmatrix} a_4 & a_5\\ a_6 & a_7 \end{bmatrix} \\ \\ \begin{bmatrix} a_8 & a_9\\ a_{10} & a_{11} \end{bmatrix} & \begin{bmatrix} a_{12} & a_{13}\\ a_{14} & a_{15} \end{bmatrix} \end{bmatrix} = \begin{bmatrix} [{\bf A}_{00}] & [{\bf A}_{01}]\\ [{\bf A}_{10}] & [{\bf A}_{11}] \end{bmatrix}
vec(A)=[vec(A00)vec(A10)vec(A01)vec(A11)]=[a0a8a4a12a2a10a6a14a1a9a5a13a3a11a7a15]\begin{aligned} \text{vec}({\bf A}) &= \begin{bmatrix} \text{vec}({\bf A}_{00}) & \text{vec}({\bf A}_{10}) & \text{vec}({\bf A}_{01}) & \text{vec}({\bf A}_{11}) \end{bmatrix}\\ & = \begin{bmatrix} a_0 & a_8 & a_4 & a_{12}\\ a_2 & a_{10} & a_6 & a_{14}\\ a_1 & a_9 & a_5 & a_{13} \\ a_3 & a_{11} & a_7 & a_{15} \end{bmatrix} \end{aligned}

在计算hessian的情况下,使用平坦化之后的张量进行计算的公式是:

2Ψx2=vec(Fx)Tvec(2ΨF2)vec(Fx)\frac{\partial^2\Psi}{\partial {\bf x}^2} = \text{vec}\left(\frac{\partial {\bf F}}{\partial {\bf x}}\right)^T\text{vec}\left(\frac{\partial^2 \Psi}{\partial {\bf F}^2}\right)\text{vec}\left(\frac{\partial {\bf F}}{\partial {\bf x}}\right)
- + \ No newline at end of file diff --git a/docs/pbd-series/pbd-xpbd-framework.html b/docs/pbd-series/pbd-xpbd-framework.html index 3f89382..776a978 100644 --- a/docs/pbd-series/pbd-xpbd-framework.html +++ b/docs/pbd-series/pbd-xpbd-framework.html @@ -9,13 +9,13 @@ - +

PBD, XPBD仿真框架

intro...

PBD

ω˙i=Ii1(τi(ωi×(Iiωi)))\dot{\omega}_i={\bf I}_i^{-1}(\tau_i-(\omega_i\times ({\bf I}_i\omega_i)))

XPBD

- + \ No newline at end of file diff --git a/docs/unity-series/CollisionIntro.html b/docs/unity-series/CollisionIntro.html index aa724a1..e443531 100644 --- a/docs/unity-series/CollisionIntro.html +++ b/docs/unity-series/CollisionIntro.html @@ -9,14 +9,14 @@ - +

碰撞系统介绍

碰撞体 Collider

Unity中使用碰撞体来表达物理碰撞的计算中,Object的具体形状。碰撞体是不可见的,并且也不需要和游戏中Object的网格体具有一样的形状。

Unity下的碰撞体有以下几种:

  • Primitive Collider:最简单的碰撞体,3D情况下,Unity为用户提供了Box、Sphere和Capsule三种;
  • Compound Collider:由多个Primitive碰撞体构成的复合碰撞体,一般用于Primitive Collider无法近似Object形状的情况,仅适用于Rigidbody component,并且碰撞体需要放在GameObject的root层级;
  • Mesh Collider:网格体碰撞体,会带来比较大的开销,需要谨慎使用。

碰撞体之间的交互方式主要由Rigidbody component的设置来控制,主要可以设置为以下三类:

  • Static Collider:静态碰撞体,是一种具有碰撞体但是没有Rigid component的GameObject。一般用于场景中不会运动的物体,例如墙壁和地面等,静态碰撞体可以与动态碰撞体产生交互,但是静态碰撞体本身并不会受到碰撞响应的影响;
  • Rigidbody Collider:动态碰撞体,与静态碰撞体相对,是一种带有没有开启kinematic的Rigidbody组件的GameObject,会受到碰撞响应的影响,其行为完全由物理引擎接管,是最常用的碰撞体;
  • Kinematic Rigidbody Collider:是一种带有开启kinematic的Rigidbody组件的GameObject,这一类碰撞体不像Rigidbody Collider一样会对碰撞或者力有响应,而是使用脚本计算Transform Component来控制运动。通过改变Rigidbody Component中变量IsKinematic的值,可以让碰撞体在Rigidbody Collider和Kinematic Rigidbody Collider之间切换,一个常见的例子是布娃娃,正常情况下角色的肢体根据预设动画正常移动,当遇到碰撞或爆炸时,关闭所有肢体的IsKinematic,角色将被表现为一个physics object,以一个比较自然的动作被抛飞。

物理材质 Physics material

用于定义碰撞过程中,碰撞表面的一些行为,主要包括摩擦和弹性(反弹的力,不会发生形变)。

触发器 Trigger

用于触发一些碰撞事件,通过trigger object脚本中的OnTriggerEnter来定义。

碰撞回调函数

当碰撞第一次被发现的时候,会触发OnCollisionEnter函数;在“碰撞被检测到”到“碰撞对被分离”之间的过程中(可能会有好几帧),会触发OnCollisionStay函数;当碰撞对被分离,会触发OnCollisionExit函数。

Trigger可以调用类似的OnTriggerEnterOnTriggerStayOnTriggerExit函数。

这些回调函数的更多细节和用例在MonoBehaviour

注意事项

对于非trigger的碰撞,如果碰撞对中所有object都开启了动力学(IsKinematic = true),那么这些回调函数不会被调用。这样设计也合理,因为总需要一个东西来触发碰撞体的脚本。

碰撞行为表

当两个对象发生碰撞时,根据碰撞对象刚体的配置,可能会发生许多不同的脚本事件。 下图给出了根据附加到对象的组件调用哪些事件函数的详细信息。 有些组合只会导致两个对象之一受到碰撞影响,但一般规则是物理不会应用于未附加 Rigidbody 组件的对象。 collisionmatrix

- + \ No newline at end of file diff --git a/docs/unreal-series/bone-anim.html b/docs/unreal-series/bone-anim.html index f2270f5..8308d48 100644 --- a/docs/unreal-series/bone-anim.html +++ b/docs/unreal-series/bone-anim.html @@ -9,13 +9,13 @@ - +

骨骼动画

参考资料:

UE4/UE5 动画的原理和性能优化:link

骨骼动画的思想

一个Mesh想要动起来,那么就需要去对每个顶点做Transform(位移/旋转/缩放),那么每一帧都存这么多Transform,1秒24帧(或更多),一整段动画要存很多数据量,所以就有了骨骼这个概念。

骨骼这个概念,本质上就是压缩相同顶点的Transform的一种方式。具体来说,就是把Mesh上一部分的顶点和其中一个或多个骨骼做绑定,那么我们只要记录这个骨骼的Transform就好了。Mesh上的顶点会有对应骨骼的weight,每一帧只要将对应的骨骼的Transform做一个加权求和就能够得到该顶点的Transform。

所以整个动画分成两个阶段:

  1. 现在游戏线程中的TickComponent里面求得当前帧的Pose(Pose:每个骨骼的Transform)
  2. 渲染线程中根据最终Pose做CPUSkin或GPUSkin算出顶点信息,并进行绘制

先骨骼,后render mesh(skinned mesh)。

TickComponent

TickComponent是UActorComponent类的成员函数,该函数会在每一帧被调用,以计算对应的组件在这一帧中的行为。

Game Thread

Game Thread

- + \ No newline at end of file diff --git a/docs/unreal-series/bounds.html b/docs/unreal-series/bounds.html index 886214c..e0f3ccb 100644 --- a/docs/unreal-series/bounds.html +++ b/docs/unreal-series/bounds.html @@ -9,7 +9,7 @@ - + @@ -22,7 +22,7 @@ useBoundsFromMasterPoseComponent

  • 所在类:USkinnedMeshComponent
  • 路径:Engine\Source\Runtime\Engine\Classes\Components\SkinnedMeshComponent.h
  • 用途:如果标记为true,会在USkinnedMeshComponent::CalcMeshBound计算SkinnedMesh组件的Bound时候使用MasterPoseComponentInst的Bound
  • bSkipBoundsUpdateWhenInterpolating bSkipBoundsUpdateWhenInterpolating

    • 所在类:USkeletalMeshComponent
    • 路径:Engine\Source\Runtime\Engine\Classes\Components\SkeletalMeshComponent.h
    • 用途:如果设置为true,那么只会在tick计算完成后更新Bound,中间插值的时候就不更新了
  • bComponentUseFixedSkelBounds bComponentUseFixedSkelBounds

    • 所在类:USkinnedMeshComponent
    • 路径:Engine\Source\Runtime\Engine\Classes\Components\SkinnedMeshComponent.h
    • 用途:如果设置为true,那么该组件就会使用一个固定的Bound。这会带来两个影响:1)Bound不会根据Mesh的旋转等操作更新,减少计算量;2)计算得到的Bound会比自动的要大,这是为了确保Mesh能够被包住
  • - + \ No newline at end of file diff --git a/docs/unreal-series/resource.html b/docs/unreal-series/resource.html index 5013733..244fef3 100644 --- a/docs/unreal-series/resource.html +++ b/docs/unreal-series/resource.html @@ -9,13 +9,13 @@ - +
    - + \ No newline at end of file diff --git a/index.html b/index.html index 5ed770d..2672fca 100644 --- a/index.html +++ b/index.html @@ -9,13 +9,13 @@ - +

    Ryao's Blog

    少一些判断,多一些旁观和理解,多挑战自己的认知盲区而不是道德边界

    Focus on What Matters

    你好!👋

    我是Ryao,目前在上海交通大学控制科学与工程专业攻读硕士学位。
    我目前主要研究方向是计算机图形学(模拟、渲染)和数字孪生建模。
    我的邮箱:lilkotyo@gmail.com, lil-kotyo@sjtu.edu.cn.
    如果你对图形学感兴趣,欢迎联系我!

    Powered by React

    Hi there 👋

    I'm Ryao, a master student in control engineering.
    I'm currently working on Computer Graphics (simulation, rendering) and Digital Twins Modeling.
    How to reach me: lilkotyo@gmail.com or lil-kotyo@sjtu.edu.cn.
    Contact me if you are interested in graphics and have related topics to discuss!

    - + \ No newline at end of file diff --git a/markdown-page.html b/markdown-page.html index 72d5acc..352bfef 100644 --- a/markdown-page.html +++ b/markdown-page.html @@ -9,13 +9,13 @@ - +

    Markdown page example

    You don't need React to write simple standalone pages.

    - + \ No newline at end of file