[{"data":1,"prerenderedAt":1528},["ShallowReactive",2],{"page-UE4-6":3,"page-count-UE4":1527},[4,392,671,842,1341],{"id":5,"title":6,"body":7,"date":368,"description":13,"extension":369,"meta":370,"navigation":373,"path":385,"seo":386,"stem":387,"tags":388,"__hash__":391},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-note-lightmass-deep-dive.md","深入LightMass系列笔记",{"type":8,"value":9,"toc":341},"minimark",[10,14,25,28,31,34,39,42,47,52,55,62,66,69,74,77,85,88,92,95,100,103,107,110,115,118,121,127,130,135,139,142,145,148,151,156,160,163,166,170,173,176,179,183,186,189,192,195,200,203,208,211,214,217,221,224,229,232,236,239,242,247,251,254,259,263,266,271,274,279,282,287,290,293,296,301,304,307,326,329,332,335,338],[11,12,13],"p",{},"UE4使用Lightmass对光照进行预计算，以节省动态光照计算的成本。",[11,15,16,17,24],{},"本文是EpicJapan的LightMass Deep Dive系列的总结，与之前更新的文章有重叠的地方可能会略过，如有需要可以参看[",[18,19,23],"a",{"href":20,"rel":21},"https:\u002F\u002Fwww.slideshare.net\u002FEpicGamesJapan\u002Flightmass-lightmap-epic-games-japan",[22],"nofollow","原始的PPT","]。",[11,26,27],{},"内容本身的UE4版本为4.13，当前UE4版本4.18。",[11,29,30],{},"其实这个系列讲的内容并不多，只是使用了很多配图，让人能非常直观的对Light Mass的计算进行理解。",[11,32,33],{},"预计算光照主要的组成部分有3个：Light Map、Shadow Map和Precomputed Light Volume。",[35,36,38],"h2",{"id":37},"light-map","Light Map",[11,40,41],{},"光照贴图是静态光照最重要的部分，当前关卡生成的光照贴图可以在World Settings一栏里面看到。",[43,44,46],"h3",{"id":45},"pointspotdirectional-light","Point\u002FSpot\u002FDirectional Light",[48,49,51],"h4",{"id":50},"direct-light","Direct Light",[11,53,54],{},"直接从光源所在处打出射线进行计算，对于有光源半径的光而言会多做一些计算。",[11,56,57],{},[58,59],"img",{"alt":60,"src":61},"image","\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb.png",[48,63,65],{"id":64},"indiret-light","Indiret Light",[11,67,68],{},"间接光照的计算核心是光子映射算法，详细的实现分为以下步骤",[70,71,73],"h5",{"id":72},"photon-mapping","Photon Mapping",[11,75,76],{},"首先从光源向周围放射光子，对命中表面的光子进行记录，并按照一定的条件进行光子反射。",[11,78,79,82],{},[58,80],{"alt":60,"src":81},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-1.png",[58,83],{"alt":60,"src":84},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-2.png",[11,86,87],{},"这个反射过程会重复多次，最终形成在物体表面的很多光子数据。",[70,89,91],{"id":90},"final-gathering","Final Gathering",[11,93,94],{},"从纹素出发，沿着光子的反射路径回溯性的收集光照信息。",[11,96,97],{},[58,98],{"alt":60,"src":99},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-3.png",[11,101,102],{},"在收集过程中并不会从自己周围收集，因为这部分的光照已经在直接光照中计算过了。",[70,104,106],{"id":105},"irradiance-caching","Irradiance Caching",[11,108,109],{},"要对场景中所有的纹素进行收集的话，不仅花费时间，且由于反弹并不是均匀的所以会有很多噪点。",[11,111,112],{},[58,113],{"alt":60,"src":114},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-4.png",[11,116,117],{},"所以这个时候就需要将收集的光照缓存起来，然后以插值的方式来收集光照数据。",[11,119,120],{},"当然上面的只是大致过程，其中还有很多细节上的优化，例如：",[11,122,123],{},[124,125,126],"strong",{},"Adpative Samping",[11,128,129],{},"如果在收集时，光子路径的相邻点间隔较大的情况，会增加额外的射线进行光照数据收集。",[11,131,132],{},[58,133],{"alt":60,"src":134},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-5.png",[43,136,138],{"id":137},"sky-light","Sky Light",[11,140,141],{},"天空光照稍微有些不同，并不会进行Photon Mapping。",[48,143,51],{"id":144},"direct-light-1",[11,146,147],{},"在Final Gathering时直接将SkyLight当作二次反射的光源进行光照收集。",[11,149,150],{},"对于场景中窗口过小导致收集到的间接光照不足时，可以添加LightMass Portal以放出更多的收集射线。",[11,152,153],{},[58,154],{"alt":60,"src":155},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-6.png",[48,157,159],{"id":158},"indirect-light","Indirect Light",[11,161,162],{},"天空光的间接光照则是对光照进行一个更小规模的MiniFinalGather。",[11,164,165],{},"不过这个说法可能不适用于当前版本，因为从4.18开始，天空光也可以接收多次反弹的间接光照了。",[35,167,169],{"id":168},"shadow-map","Shadow Map",[11,171,172],{},"这个系列并没有介绍阴影贴图，Shadow Map并不是为Static Light准备的，而是为Stational Light准备的。",[11,174,175],{},"在进行光照计算时，每一个动态的物体，都会根据自己的bound box与stational light的朝向关系，将Shadow Map合成到光照运算结果中去。",[11,177,178],{},"因此，如果Stational Light影响的动态物体特别多的时候，其效率是非常低下的。有时候考虑直接换为Dynamic Light反而更好。",[35,180,182],{"id":181},"precomputed-light-volume","Precomputed Light Volume",[11,184,185],{},"这个主要是在场景中生成预计算的间接光照缓存点，这样动态的物体就可以从中取得数据并插值得到自己的间接光照。",[11,187,188],{},"插值的方式中，ILCQ Point是根据物体当前的位置进行的，所以如果物体有一定的大小且移动速度较慢的话，就容易产生可见的阴影跳变。相对的ILCQ Volume由于使用多个点进行插值处理，运算成本会变高，尤其是对于非常大的物体。",[43,190,191],{"id":191},"缓存数据",[11,193,194],{},"PLV的数据是具有方向性的球谐函数",[11,196,197],{},[58,198],{"alt":60,"src":199},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-7.png",[11,201,202],{},"在计算的时候分为上下两个半球进行计算",[11,204,205],{},[58,206],{"alt":60,"src":207},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-8.png",[11,209,210],{},"计算方法是，在进行光照预计算的时候，对指定的缓存点，将直接光照与Final Gathering的值缓存起来。",[43,212,213],{"id":213},"缓存点的生成",[11,215,216],{},"生成方式总共分为三种",[48,218,220],{"id":219},"surface-light-sample","Surface Light Sample",[11,222,223],{},"这个是最单纯的，从每个Mesh的表面沿着法线方向进行缓存点添加。",[11,225,226],{},[58,227],{"alt":60,"src":228},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-9.png",[11,230,231],{},"可以通过配置文件进行行为修改。",[48,233,235],{"id":234},"detail-volume-sample","Detail Volume Sample",[11,237,238],{},"在世界中拖放Lightmass Character Indirect Detail Volume生成的缓存点。",[11,240,241],{},"这个Volume拖放之后会在内部生成缓存点。",[11,243,244],{},[58,245],{"alt":60,"src":246},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-10.png",[48,248,250],{"id":249},"uniform-volume-sample","Uniform Volume Sample",[11,252,253],{},"Lightmass Importance Sample这个Volume是在场景内全局性的生成缓存点",[11,255,256],{},[58,257],{"alt":60,"src":258},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-11.png",[35,260,262],{"id":261},"usage","Usage",[11,264,265],{},"演讲中介绍了不少静态光照的配置技巧，这里只列出之前没有列出过的。",[11,267,268],{},[124,269,270],{},"反光板",[11,272,273],{},"直接将聚光灯打到一块板子上，只让间接光照计算影响到静态光照计算。",[11,275,276],{},[124,277,278],{},"Compress Lightmaps",[11,280,281],{},"这个光照贴图的压缩是有损的，如果关掉的话效果会变好，但是贴图体积会放大4倍左右。",[11,283,284],{},[124,285,286],{},"Lightmap Resolution",[11,288,289],{},"光照贴图的分辨率在配置时要注意，比最小的纹素小的光照细节无法被捕捉。",[11,291,292],{},"Static Lighting Level Scale",[11,294,295],{},"这个配置会影响到整个LightMass的计算，同样的如果导致小于RecordRadius的光照细节无法被捕捉。",[11,297,298],{},[124,299,300],{},"Use Emissive For Static Lighting",[11,302,303],{},"这个的光照计算和天空光一样是不会进行Photon Mapping的。",[11,305,306],{},"以及最后给出的配置和性能影响",[308,309,310,314,317,320,323],"ul",{},[311,312,313],"li",{},"Static Lighting Level Scale = 1→ 0.1",[311,315,316],{},"Num Indirect Lighting Bounces 👨→20",[311,318,319],{},"Indirect Lighting Quality = 1→10",[311,321,322],{},"Indirect Lighting Smoothness=1.0→1.0",[311,324,325],{},"Lighting Quality = Production Build Time",[11,327,328],{},"光照计算时间15m57s →4h10m24s",[11,330,331],{},"不过这个的演讲者是做建筑展示的，所以对静态光照的要求比较高，并没有考虑到游戏运行的成本。",[35,333,334],{"id":334},"总结",[11,336,337],{},"了解了静态光照的计算方法之后，在对其进行调整，尤其是要修改config.ini的时候非常的有帮助。",[11,339,340],{},"至少比盲目的调节选项然后等待漫长的光照计算之后来观察结果要好很多。",{"title":342,"searchDepth":343,"depth":344,"links":345},"",2,3,[346,357,358,366,367],{"id":37,"depth":343,"text":38,"children":347},[348,353],{"id":45,"depth":344,"text":46,"children":349},[350,352],{"id":50,"depth":351,"text":51},4,{"id":64,"depth":351,"text":65},{"id":137,"depth":344,"text":138,"children":354},[355,356],{"id":144,"depth":351,"text":51},{"id":158,"depth":351,"text":159},{"id":168,"depth":343,"text":169},{"id":181,"depth":343,"text":182,"children":359},[360,361],{"id":191,"depth":344,"text":191},{"id":213,"depth":344,"text":213,"children":362},[363,364,365],{"id":219,"depth":351,"text":220},{"id":234,"depth":351,"text":235},{"id":249,"depth":351,"text":250},{"id":261,"depth":343,"text":262},{"id":334,"depth":343,"text":334},"2018-01-01","md",{"layout":371,"status":372,"published":373,"author":374,"author_login":376,"author_email":377,"wordpress_id":378,"wordpress_url":379,"date_gmt":380,"excerpt":381},"post","publish",true,{"display_name":375,"login":376,"email":377,"url":342},"风铃","flinkor","flinkor@foxmail.com",2239,"\u002F\u002F?p=2239","2018-01-01 15:59:33 +0000",{"type":8,"value":382},[383],[11,384,13],{},"\u002F2018-01-01-note-lightmass-deep-dive",{"title":6,"description":13},"_legacy\u002F2018\u002F2018-01-01-note-lightmass-deep-dive",[389,390],"UE4","Lighting","lNxcndA-W2UxZRHbVV_MbKunnp78cQrGi-WboMRX8J8",{"id":393,"title":394,"body":395,"date":368,"description":399,"extension":369,"meta":657,"navigation":373,"path":666,"seo":667,"stem":668,"tags":669,"__hash__":670},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-ue4-material-deepdive-note.md","深入UE4材质管理",{"type":8,"value":396,"toc":638},[397,400,403,406,409,412,416,419,422,426,429,432,435,438,441,443,446,449,452,455,458,462,465,470,473,476,479,482,486,489,494,498,501,506,509,512,515,519,522,526,529,534,537,542,547,550,553,556,561,564,567,572,575,580,583,588,591,595,600,603,608,611,616,619,623,626,629,632,635],[11,398,399],{},"UE4的材质系统使用起来虽然很方便，但是如果不进行有效的管理，很快就会导致项目的体积变得不可控制，材质编译时间也会变成痛苦的折磨。",[11,401,402],{},"本文是EpicJapan UE4 DeepDive系列中材质管理部分的总结，对应UE4版本为4.12~14。部分截图来自4.18。",[35,404,405],{"id":405},"抽离共通逻辑",[11,407,408],{},"为了减少材质的编译尺寸，首先要做的就是将共通的逻辑都抽出来。",[11,410,411],{},"在材质系统中一共有以下两种做法",[43,413,415],{"id":414},"material-function","Material Function",[11,417,418],{},"材质函数可以将一些共同的操作逻辑抽取出来，这样在进行材质的编译的时候，就不会产生重复的部分了。",[11,420,421],{},"UE4本身就提供了大量的材质函数来方便进行材质的表现。",[43,423,425],{"id":424},"material-instance","Material Instance",[11,427,428],{},"材质实例通过将通用的材质统合起来，有效的减少材质的数量。",[11,430,431],{},"使用上，如果材质之间就只有贴图或者参数不同的话，就可以构建一个基础的材质，然后将需要变化的材质进行参数化。",[11,433,434],{},"并创建材质实例以应对不同的情况。",[35,436,437],{"id":437},"选项优化",[11,439,440],{},"材质在使用时有的选项需要进行优化，否则就会生成很多不会使用到的部分。导致编译时间变长且无端的占用发布体积。",[43,442,262],{"id":261},[11,444,445],{},"材质的选项中有一个Usage的选项，通常会自动勾选上Automatically Set Usage in Editor。",[11,447,448],{},"这个选项会在材质被特定的Usage使用的时候自动打开，针对每一种Usage会生成新的Shader来进行对应。",[11,450,451],{},"但是，Usage被打开后，如果不再在对应的Usage中使用的话，引擎并不会自动的关闭这个Usage，所以需要手动的进行关闭。",[11,453,454],{},"这个选项的作用在极端情况下特别大：使用全部Usage时1837K的材质在仅使用Static Light时却只有97K的。",[11,456,457],{},"因此在使用的时候还是需要进行额外的注意的，如果默认去掉这个选项的话，就可以有效的避免误操作导致的勾选了。当然使用起来就会有些麻烦。",[43,459,461],{"id":460},"shader-permutation-reduction","Shader Permutation Reduction",[11,463,464],{},"在项目设置>渲染中，能够看到",[11,466,467],{},[58,468],{"alt":60,"src":469},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb.png",[11,471,472],{},"这个也是类似的选项，在没有使用对应的功能的时候去掉就好了。",[11,474,475],{},"这个如果去掉了却在使用对应的功能的话，编辑器内会有屏幕输出提示，可以放心使用。",[35,477,478],{"id":478},"材质实例关系",[11,480,481],{},"材质实例的继承关系会很大的影响到材质的编译过程，部分继承的属性重写是会导致材质实例需要生成新的Shader的。",[43,483,485],{"id":484},"override-properties","Override properties",[11,487,488],{},"材质属性覆盖会导致需要为实例生成新的Shader",[11,490,491],{},[58,492],{"alt":60,"src":493},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-1.png",[43,495,497],{"id":496},"static-switch-parameter","Static Switch Parameter",[11,499,500],{},"开关是需要额外的生成Shader的",[11,502,503],{},[58,504],{"alt":60,"src":505},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-2.png",[11,507,508],{},"当然也包括Static Component Mask Parameter。",[11,510,511],{},"因此在进行材质实例的继承的时候，应当进行规划，首先分支出Switch等需要重新生成Shader的材质实例后，再继承其他的如贴图、颜色这些不会导致重新生成的材质实例的继承。",[11,513,514],{},"同时，由于材质实例不携带Usage属性，所以这个问题上只能一开始就进行分开。",[35,516,518],{"id":517},"tips","Tips",[11,520,521],{},"这个是SE的演讲者给出的一些操作建议。",[43,523,525],{"id":524},"textrue","Textrue",[11,527,528],{},"尽量减少图形的分辨率，在使用单色的Textrue时，使用1x1的图片即可，在需要过渡特效时，使用一个小条就可以了。",[11,530,531],{},[58,532],{"alt":60,"src":533},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-3.png",[11,535,536],{},"而有对称性的图片，可以通过调整Tilling Method来减小尺寸。",[11,538,539],{},[58,540],{"alt":60,"src":541},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-4.png",[11,543,544],{},[124,545,546],{},"Mipmap",[11,548,549],{},"有一个ComputeMipLevel的节点可以用于将Mipmap进行可视化调节。",[11,551,552],{},"Mipmap虽然可以在贴图的选项中关掉，但是这样会导致原始贴图常驻内存，运行时消耗很大。",[11,554,555],{},"Texture的Uv入口被用于缩放时并不会对应到MipLevel上，使用时需要注意。",[11,557,558],{},[124,559,560],{},"Power",[11,562,563],{},"不要使用Power来进行2.2次方的运算而是直接相乘。也可以直接将同一个数相乘写成一个材质函数，方便使用。",[11,565,566],{},"因为两个的计算几乎无法目视区分，却有着明显的性能消耗。",[11,568,569],{},[124,570,571],{},"vector displacement map",[11,573,574],{},"矢量置换贴图采用BC7压缩可以大幅减小尺寸？",[11,576,577],{},[124,578,579],{},"Maximum Texture Size",[11,581,582],{},"比LOD Bias能够更好的对贴图尺寸进行控制。",[11,584,585],{},[124,586,587],{},"Never Stream",[11,589,590],{},"会一次性载入所有的MipLevel贴图，在使用频率很高的粒子的贴图上可以打开。",[43,592,594],{"id":593},"material","Material",[11,596,597],{},[124,598,599],{},"除0対策",[11,601,602],{},"为了防止0被传到除法运算上，可以考虑加上0.0001，虽然严格意义上应当使用Max或者Min来进行控制，但是Add的效率更高",[11,604,605],{},[124,606,607],{},"Texture参数",[11,609,610],{},"即便两个参数指定的是同一个Texture，也会进行两次采样。",[11,612,613],{},[124,614,615],{},"SamplerType",[11,617,618],{},"SamplerType选择为Alpha时在有的平台实际上用的确实R通道",[43,620,622],{"id":621},"trick","Trick",[11,624,625],{},"DynamicParameter 对于float2、float3、float4，如果实际上需要的参数是一样的话，是可以用float来代替的，会自动的被变为对应的类型。",[11,627,628],{},"ParticleColor 可以在EmissiveColor不需要通过这个参数指定时用于其他用途",[11,630,631],{},"ParticleRandom是GPU粒子独有的节点，值在0~1之间，可以用于添加随机特效",[11,633,634],{},"Static Component Mask Parameter可以为1张贴图提供四种变数",[11,636,637],{},"UV使用Time进行滚动时，可以先滚动再Tilling，这样的话Tilling进行调整时就不会影响UV动画的速度了。",{"title":342,"searchDepth":343,"depth":344,"links":639},[640,644,648,652],{"id":405,"depth":343,"text":405,"children":641},[642,643],{"id":414,"depth":344,"text":415},{"id":424,"depth":344,"text":425},{"id":437,"depth":343,"text":437,"children":645},[646,647],{"id":261,"depth":344,"text":262},{"id":460,"depth":344,"text":461},{"id":478,"depth":343,"text":478,"children":649},[650,651],{"id":484,"depth":344,"text":485},{"id":496,"depth":344,"text":497},{"id":517,"depth":343,"text":518,"children":653},[654,655,656],{"id":524,"depth":344,"text":525},{"id":593,"depth":344,"text":594},{"id":621,"depth":344,"text":622},{"layout":371,"status":372,"published":373,"author":658,"author_login":376,"author_email":377,"wordpress_id":659,"wordpress_url":660,"date_gmt":661,"excerpt":662},{"display_name":375,"login":376,"email":377,"url":342},2192,"\u002F\u002F?p=2192","2017-12-31 16:02:08 +0000",{"type":8,"value":663},[664],[11,665,399],{},"\u002F2018-01-01-ue4-material-deepdive-note",{"title":394,"description":399},"_legacy\u002F2018\u002F2018-01-01-ue4-material-deepdive-note",[389,594],"fKGSuTdtw5DEXqYUD_-VBIoQmLlq-aYwVtmxYL8eN0Q",{"id":672,"title":673,"body":674,"date":368,"description":678,"extension":369,"meta":826,"navigation":373,"path":835,"seo":836,"stem":837,"tags":838,"__hash__":841},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-ue4-project-notes.md","UE4项目笔记汇总",{"type":8,"value":675,"toc":815},[676,679,682,685,688,691,694,697,705,708,711,715,718,721,724,727,730,733,736,746,749,752,755,758,761,767,770,774,777,781,784,787,793,796,800,803,806,812],[11,677,678],{},"这里是过去一段与项目有关的笔记汇总，把一些零零碎碎的给合在了一起。",[11,680,681],{},"由于项目一直停留在4.15，所以本文所有内容都是基于UE4.15.3的。",[35,683,684],{"id":684},"物理相关",[11,686,687],{},"由于做的项目对运动的精度要求比较高，所以和物理打了很长一段时间的交道。",[11,689,690],{},"在用UE4之前基本没接触过3D的物理引擎。好在之前有用过Box2D，一些基本的物理引擎原理还是有些了解的。",[43,692,693],{"id":693},"球体运动",[11,695,696],{},"遇到的第一个问题是球体运动相关的，Physx对球体运动的模拟有些糟糕。",[11,698,699,700,704],{},"主要表现是Friction对球体的速度影响相当的微妙，导致球的运动很难停下来。而如果使用LinearDumping和AngularDumping来进行速度制御的话，由于其并不是匀减速的，而且即便与Friction没有关系，运动也不真实。这部分的解决方案之前已经记录在[",[18,701,703],{"href":702},"\u002F2017-05-03-ue4-physx-and-substepping\u002F","UE4中的Physx物理","]这篇文章中了，总之就是自己接管物理的部分运算。",[11,706,707],{},"更加区域的注册自己添加上摩擦力和反弹。途中遇到过很多问题，现在回头看的话，发现代码的实现还是不够简洁。",[11,709,710],{},"而且虽然一开始坚持想要存依靠公式来进行运动控制，但最后还是往里面添加了不少“黑魔法”……",[43,712,714],{"id":713},"foliage碰撞","Foliage碰撞",[11,716,717],{},"后面出现的一个需求是关于Foliage的碰撞的，总体而言就是需要树的不同部分表现出不同的碰撞特性。",[11,719,720],{},"比如树叶区域进行Overlap，而树干区域进行Block。但是由于Foliage本身是一种Instanced的Actor，UE4在实现时，对于同一个FoliageType只能使用一个碰撞配置。",[11,722,723],{},"不过Instance主要是针对渲染而进行的一种优化方案，既然Foliage是可以进行碰撞的，那么在Physx world中肯定是有注册碰撞形态的。",[11,725,726],{},"几经探索之后终于找到解决方案，通过UE4提供的Physx底层Api，将Foliage Type实例化之后的碰撞形态取出，单独将Capsule的碰撞形态重新注册到Physx中去。",[11,728,729],{},"这里本来是想直接对PxActor的碰撞配置进行修改的，但是UE4在上层有封装，无法将Overlap的物体的碰撞响应传递出去。所以只有采取取出重新注册的方案。",[11,731,732],{},"但是由于场景中的Foliage实在是太多，在地图载入时进行整体性操作会导致不可接受的时间消耗，最后采用动态转化的方案，将玩家周围的Foliage取出，并拉取出Capsule来实现。",[11,734,735],{},"这里面有遇到一个困扰了很久的问题，那就是Capsule取出之后怎么都无法找到正确的对应Roation。几经周折才发现UE4中的Capsule和Physx中的Capsule是有不同的默认朝向的，需要手动进行一次转化才行：",[737,738,743],"pre",{"className":739,"code":741,"language":742},[740],"language-text","static const PxQuat CapsuleRotator(0.f, 0.707106781f, 0.f, 0.707106781f);\n\nPxQuat ConvertToPhysXCapsuleRot(const FQuat& GeomRot)\n{\n  \u002F\u002F Rotation required because PhysX capsule points down X, we want it down Z\n  return U2PQuat(GeomRot) * CapsuleRotator;\n}\n\nFQuat ConvertToUECapsuleRot(const PxQuat & PGeomRot)\n{\n return P2UQuat(PGeomRot * CapsuleRotator.getConjugate());\n}\n","text",[744,745,741],"code",{"__ignoreMap":342},[35,747,748],{"id":748},"屏幕偏移",[11,750,751],{},"就是要让FOV的计算有一个Offset，当时查了很多资料。只在AnswerHub找到一个旧版本的不是很完全的实现。",[11,753,754],{},"好在通过那个页面里的讨论找到了解决的方向，而不用自己深入到UE4的渲染代码中去找Hack Point。",[11,756,757],{},"最主要的问题是，讨论最后给出的Viewport的转换矩阵是错的，转换之后的结果并不能让人满意。UE4的渲染实现并不“标准”，所以使用通用的摄像投影矩阵还是无法实现类似视点中心偏移的效果。",[11,759,760],{},"最后终于在代码深处的BlendCamera中找到了UE4本身对视角偏移的处理，得出了“正确”的偏移矩阵",[737,762,765],{"className":763,"code":764,"language":742},[740],"\u002F** 计算投影矩阵 *\u002F\nfloat t_fRatio = InCamera->AspectRatio;\nif (!InCamera->bConstrainAspectRatio)\n{\n t_fRatio = t_ScreenSize.X \u002F t_ScreenSize.Y;\n}\n\nfloat t_fFov = InCamera->FieldOfView;\nfloat t_fNear = GNearClippingPlane;\n\nresult = FReversedZPerspectiveMatrix(t_fFov * PI \u002F 360.0f, t_fRatio, 1.0f, t_fNear);\n\nif (Offset.IsZero())\n{\n return true;\n}\n\n\u002F** 将Offset规范到百分比 *\u002F\nOffset.X \u002F= (t_ScreenSize.X \u002F 2.0f);\nOffset.Y \u002F= (t_ScreenSize.Y \u002F 2.0f);\n\n\u002F** Clamp以避免“过度”偏移 *\u002F\nOffset = FMath::Clamp(Offset, FVector2D(-1, -1), FVector2D(1, 1));\n\nconst float Left = -1.0f + Offset.X;\nconst float Right = Left + 2.0f;\nconst float Bottom = -1.0f + Offset.Y;\nconst float Top = Bottom + 2.0f;\n\nresult.M[2][0] = (Left + Right) \u002F (Left - Right);\nresult.M[2][1] = (Bottom + Top) \u002F (Bottom - Top);\n",[744,766,764],{"__ignoreMap":342},[11,768,769],{},"不过，当时由于实现的比较急切，直接沿用讨论的方向，对Viewport类进行了重载，也使用了一些“黑魔法，虽然至今没有观测到副作用，但是现在想来，其实还是有其他的解决方案的。",[35,771,773],{"id":772},"ui相关","UI相关",[11,775,776],{},"UMG只实现了默认的几种常见的Widget，所以有很多需要实现的自定义控件。这个也算是UI开发的常态了，但是当时有两个问题还是困扰了一段时间。",[43,778,780],{"id":779},"slate的序列帧","Slate的序列帧",[11,782,783],{},"UMG的序列帧实现在社区可以找到，但是当需要在LoadingScreen上播放序列帧时就会有麻烦。因为那个时候UMG系统还没有初始化完成，只能使用Slate。",[11,785,786],{},"在参考了UE4的进度条实现之后，找到了相关的接口：",[737,788,791],{"className":789,"code":790,"language":742},[740],"FVector2D Min(FrameSize.X * Column, FrameSize.Y * Row);\nFVector2D Max = Min + FrameSize;\nFBox2D UVCoordinates(Min \u002F TextureSize, Max \u002F TextureSize);\nUVCoordinates.bIsValid = true;\n\nBrush.SetUVRegion(MoveTemp(UVCoordinates));\n",[744,792,790],{"__ignoreMap":342},[11,794,795],{},"这样就可以实现序列帧的切换了。",[43,797,799],{"id":798},"_2d画线","2D画线",[11,801,802],{},"FSlateDrawElement::MakeLines有一个问题，那就是不知为何最后实现的时候Thickness这个参数是不起作用的。",[11,804,805],{},"因此要画粗线就必须自己想办法，虽然试过很多方法，包括参考UE4内部的线段AA实现，但是最后还是采用了最不优雅的解决方案：多画几次。",[737,807,810],{"className":808,"code":809,"language":742},[740],"FVector2D DrawPosition;\nfor (; aTimes > 0; --aTimes)\n{\n DrawPosition = FVector2D::ZeroVector;\n if (m_bIsVert) DrawPosition.Y = aTimes;\n else DrawPosition.X = aTimes;\n\n FSlateDrawElement::MakeLines(\n  InContext.OutDrawElements,\n  InContext.MaxLayer,\n  InContext.AllottedGeometry.ToPaintGeometry(DrawPosition, FVector2D(1.0f,1.0f), 1.0f),\n  tLine,\n  InContext.MyClippingRect,\n  ESlateDrawEffect::None,\n  color,\n  true,\n  0.1f);\n}\n",[744,811,809],{"__ignoreMap":342},[11,813,814],{},"因为这样画出来的才是最平滑的，好在对粗细的要求并不高，否则可能会加大绘制的负担。",{"title":342,"searchDepth":343,"depth":344,"links":816},[817,821,822],{"id":684,"depth":343,"text":684,"children":818},[819,820],{"id":693,"depth":344,"text":693},{"id":713,"depth":344,"text":714},{"id":748,"depth":343,"text":748},{"id":772,"depth":343,"text":773,"children":823},[824,825],{"id":779,"depth":344,"text":780},{"id":798,"depth":344,"text":799},{"layout":371,"status":372,"published":373,"author":827,"author_login":376,"author_email":377,"wordpress_id":828,"wordpress_url":829,"date_gmt":830,"excerpt":831},{"display_name":375,"login":376,"email":377,"url":342},2170,"\u002F\u002F?p=2170","2017-12-31 16:01:50 +0000",{"type":8,"value":832},[833],[11,834,678],{},"\u002F2018-01-01-ue4-project-notes",{"title":673,"description":678},"_legacy\u002F2018\u002F2018-01-01-ue4-project-notes",[389,839,840],"UMG","物理","8GIMwwB_ylWoPujHfm3zYlq3ZQOiZ_ohYp777ox0kVk",{"id":843,"title":844,"body":845,"date":368,"description":849,"extension":369,"meta":1326,"navigation":373,"path":1335,"seo":1336,"stem":1337,"tags":1338,"__hash__":1340},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-ue4-rendering-code-view-01.md","UE4渲染代码逻辑总结（上）",{"type":8,"value":846,"toc":1300},[847,850,853,856,859,863,866,869,874,877,880,883,888,891,894,897,900,905,908,911,916,919,922,925,928,932,935,940,943,948,951,956,959,964,967,971,974,977,1029,1032,1037,1040,1043,1048,1051,1054,1058,1062,1065,1068,1074,1077,1080,1086,1089,1092,1096,1099,1105,1108,1111,1117,1120,1124,1127,1130,1133,1136,1140,1143,1146,1150,1153,1156,1159,1163,1169,1172,1175,1181,1184,1187,1193,1196,1202,1205,1209,1215,1218,1221,1227,1230,1233,1237,1240,1249,1252,1255,1258,1264,1267,1270,1276,1279,1285,1288,1291,1297],[11,848,849],{},"本文是对UE4中渲染代码逻辑的总结。",[11,851,852],{},"当前使用的UE4版本为4.18.3。",[11,854,855],{},"本文内容是之前提到的这段时间的笔记的总结，内容主要来自于社区文章、官方文档以及引擎源码的阅读。",[11,857,858],{},"由于渲染系统比较复杂，没有时间遍历所有的代码，很多地方掺杂了自己的臆测，如有错误，欢迎指正。",[35,860,862],{"id":861},"fshader","FShader",[11,864,865],{},"FShader是负责Shader的代码端基类，继承自FDeferredCleanupInterface。",[43,867,868],{"id":868},"结构",[11,870,871],{},[124,872,873],{},"FDeferredCleanupInterface",[11,875,876],{},"FDeferredCleanupInterface这个基类是用于游戏线程与渲染线程的同步的，只有一个纯虚函数：FinishCleanup。",[11,878,879],{},"由于渲染用的资源在两边都有在使用，所以当要删除一个资源时，会向渲染线程推送资源删除请求，同时通过BeginCleanup将其加入待删除列表，等到渲染线程结束时FinishCleanup就会被调用，这时候就可以安全的在游戏线程中完全释放资源了。",[11,881,882],{},"除了FShader之外，其他与渲染有关的资源如FLightMap以及FShadowMap等都有继承自这个类。",[11,884,885],{},[124,886,887],{},"FShaderResource",[11,889,890],{},"这个类也继承自FDeferredCleanupInterface，如其名称，它负责保管Shader编译之后的资源。",[11,892,893],{},"为了减小材质编译等对资源的占用，同一个FShaderResource会被多个FShader引用。例如Material Function就利用了这个机制。",[43,895,896],{"id":896},"使用",[11,898,899],{},"FShader共有两种类型的之类，分别是FGlobalShader与FMaterialShader，对应不同的使用用途。",[11,901,902],{},[124,903,904],{},"FGlobalShader",[11,906,907],{},"这个是全局的Shader，只允许存在一个实例。",[11,909,910],{},"渲染的核心Shader部分有许多就是FGlobalShader。",[11,912,913],{},[124,914,915],{},"FMaterialShader",[11,917,918],{},"这个是用于具体材质的Shader，会拥有很多实例。",[11,920,921],{},"进一步被实现为FMeshMaterialShader，可以将Mesh的顶点数据引入到Shader端。",[35,923,924],{"id":924},"渲染数据",[11,926,927],{},"渲染数据通过FPrimitiveSceneProxy经由结构FVertexFactory绑定到Shader上。",[43,929,931],{"id":930},"fvertexfactory","FVertexFactory",[11,933,934],{},"这个类负责将顶点数据从C++端带到Shader端，继承自FRenderResource，是渲染资源的一种。",[11,936,937],{},[124,938,939],{},"FLocalVertexFactory",[11,941,942],{},"提供本地空间到全局空间的转换，Static Mesh以及Cables、Procedual Mesh等都使用的是它。",[11,944,945],{},[124,946,947],{},"FGPUBaseSkinVertexFactory",[11,949,950],{},"这个是Skeletel Mesh用的，因为需要一些更多的数据。但是似乎还要配合继承自LocalVertexFactory的FGPUSkinPassthroughVertexFactory。",[11,952,953],{},[124,954,955],{},"FLandscapeVertexFactory",[11,957,958],{},"Landscape是基于VTF(Vertex Texture Fetch)，使用高度图来修改顶点位置实现的，所以需要额外的处理。",[11,960,961],{},[124,962,963],{},"FParticleVertexFactoryBase",[11,965,966],{},"粒子系统用的。",[43,968,970],{"id":969},"fprimitivesceneproxy","FPrimitiveSceneProxy",[11,972,973],{},"这个类似UPrimitiveComponent在渲染线程中的对应版本，负责维护每个Component在渲染线程上需要的数据。",[11,975,976],{},"UE4的核心类大多有自己在渲染线程中的对应",[978,979,980,993],"table",{},[981,982,983],"thead",{},[984,985,986,990],"tr",{},[987,988,989],"th",{},"游戏线程",[987,991,992],{},"渲染线程",[994,995,996,1005,1013,1021],"tbody",{},[984,997,998,1002],{},[999,1000,1001],"td",{},"UWorld",[999,1003,1004],{},"FScene",[984,1006,1007,1010],{},[999,1008,1009],{},"UPrimitiveComponent",[999,1011,1012],{},"FPrimitiveSceneProxy \u002F FPrimitiveSceneInfo‬",[984,1014,1015,1018],{},[999,1016,1017],{},"ULocalPlayer",[999,1019,1020],{},"FSceneViewState",[984,1022,1023,1026],{},[999,1024,1025],{},"ULightComponent",[999,1027,1028],{},"FLightSceneProxy \u002F FLightSceneInfo",[11,1030,1031],{},"不同的UPrimitiveComponent类通过重载CreateSceneProxy()来创建自己的FPrimitiveSceneProxy。",[11,1033,1034],{},[124,1035,1036],{},"UCableComponent",[11,1038,1039],{},"非常的直观，在CreateSceneProxy()中直接创建FCableSceneProxy，然后进行初始化。",[11,1041,1042],{},"然后在SendRenderDyamicData_Concurrent()中将数据发送到渲染线程并借由SetDynamicData_RenderThread进行数据构造。",[11,1044,1045],{},[124,1046,1047],{},"UImagePlateFrustumComponent",[11,1049,1050],{},"由于始终只是在渲染面向摄像的2D材质，所以不需要VertexFactory。",[11,1052,1053],{},"所以FImagePlateFrustumSceneProxy只是在GetDynamicMeshElements时返回演算的结果。",[35,1055,1057],{"id":1056},"drawing-policy","Drawing Policy",[43,1059,1061],{"id":1060},"fdepthdrawingpolicy","FDepthDrawingPolicy",[11,1063,1064],{},"这个Drawing Policy工作于depth-only通道时，负责将Mesh的opaque和masked的深度信息写出。",[11,1066,1067],{},"通过调用",[737,1069,1072],{"className":1070,"code":1071,"language":742},[740],"VertexShader = InMaterialResource.GetShader\u003CTDepthOnlyVS\u003Cfalse> >(VertexFactory->GetType())\n",[744,1073,1071],{"__ignoreMap":342},[11,1075,1076],{},"DrawingPolicy便找到了对应的shader，并将vertex factory传了进去。",[11,1078,1079],{},"在需要Tessellation的情况下，还会另外获取HullShader和DomainShader",[737,1081,1084],{"className":1082,"code":1083,"language":742},[740],"HullShader = InMaterialResource.GetShader\u003CFDepthOnlyHS>(VertexFactory->GetType());\nDomainShader = InMaterialResource.GetShader\u003CFDepthOnlyDS>(VertexFactory->GetType());\n",[744,1085,1083],{"__ignoreMap":342},[11,1087,1088],{},"代码的分支很多，但是作用还是比较明显的。",[11,1090,1091],{},"其中还有SetSharedState和SetMeshRenderState两个函数负责传递参数。",[43,1093,1095],{"id":1094},"fbasepassdrawingpolicy","FBasePassDrawingPolicy",[11,1097,1098],{},"这个是在basepass通道时处理Mesh，根据不同的光照类型会有不同的处理",[737,1100,1103],{"className":1101,"code":1102,"language":742},[740],"template\u003Ctypename LightMapPolicyType>\nclass TBasePassDrawingPolicy : public FBasePassDrawingPolicy\n",[744,1104,1102],{"__ignoreMap":342},[11,1106,1107],{},"这个类才算是本体的感觉吧。",[11,1109,1110],{},"会根据不同的光照类型获取不同的Base pass的shader，这里的光照类型并不是单纯的编辑器中设置的类型，而是实际在Shader中用于计算的光照模型",[737,1112,1115],{"className":1113,"code":1114,"language":742},[740],"enum ELightMapPolicyType\n{\nLMP_NO_LIGHTMAP,\nLMP_PRECOMPUTED_IRRADIANCE_VOLUME_INDIRECT_LIGHTING,\nLMP_CACHED_VOLUME_INDIRECT_LIGHTING,\nLMP_CACHED_POINT_INDIRECT_LIGHTING,\nLMP_SIMPLE_NO_LIGHTMAP,\nLMP_SIMPLE_LIGHTMAP_ONLY_LIGHTING,\nLMP_SIMPLE_DIRECTIONAL_LIGHT_LIGHTING,\nLMP_SIMPLE_STATIONARY_PRECOMPUTED_SHADOW_LIGHTING,\nLMP_SIMPLE_STATIONARY_SINGLESAMPLE_SHADOW_LIGHTING,\nLMP_SIMPLE_STATIONARY_VOLUMETRICLIGHTMAP_SHADOW_LIGHTING,\nLMP_LQ_LIGHTMAP,\nLMP_HQ_LIGHTMAP,\nLMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP,\n\u002F\u002F Mobile specific\nLMP_MOBILE_DISTANCE_FIELD_SHADOWS_AND_LQ_LIGHTMAP,\nLMP_MOBILE_DISTANCE_FIELD_SHADOWS_LIGHTMAP_AND_CSM,\nLMP_MOBILE_DIRECTIONAL_LIGHT_AND_SH_INDIRECT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_AND_SH_INDIRECT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM_AND_SH_INDIRECT,\nLMP_MOBILE_DIRECTIONAL_LIGHT_CSM_AND_SH_INDIRECT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_WITH_LIGHTMAP,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM_WITH_LIGHTMAP,\n\u002F\u002F LightMapDensity\nLMP_DUMMY\n};\n",[744,1116,1114],{"__ignoreMap":342},[11,1118,1119],{},"GetUniformBasePassShaders负责完成差分，最终通过LightMapPolicyType就会得到不同的basepass的shader，不同的basepass的shader中，使用的vertex factory也就不同了。",[43,1121,1123],{"id":1122},"drawingpolicyfactory","DrawingPolicyFactory",[11,1125,1126],{},"这是一组负责生成DrawingPloicy的工厂类，但是他们都没有各自的基类，而是对应自己的功能有稍微有些不同的实现。",[11,1128,1129],{},"在FDepthDrawingPolicyFactory::AddStaticMesh能够看到一个FStaticMesh是如何被注册到FScene中去的。",[11,1131,1132],{},"而这个调用来自FStaticMesh::AddToDrawLists，在其中能够看到FStaticMesh在通过各种DrawingPolicyFactory来进行Shader的关联注册。",[11,1134,1135],{},"而之后，在渲染线程中，FDepthDrawingPolicyFactory::DrawStaticMesh之类的函数就会被调用来进行渲染。",[43,1137,1139],{"id":1138},"fprimitivesceneinfo","FPrimitiveSceneInfo",[11,1141,1142],{},"对DrawingPolicyFactory的调用最终来自FPrimitiveSceneInfo，这个类与FPrimitiveSceneProxy 是一对一的关系，是最终注册到FScene的数据结构。",[11,1144,1145],{},"在FScene::UpdatePrimitiveTransform_RenderThread中能够看到渲染线程是如何通过FPrimitiveSceneProxy ::GetPrimitiveSceneInfo来更新与FScene的关系的。",[35,1147,1149],{"id":1148},"c到shader的绑定","C++到Shader的绑定",[11,1151,1152],{},"上面这些类是在C++中负责渲染逻辑的，而为了最终代码与Shader之间能够相互交流需要进行绑定。",[43,1154,1155],{"id":1155},"绑定帮助宏",[11,1157,1158],{},"UE4中有一组宏来帮助绑定C++类与Shader代码。",[48,1160,1162],{"id":1161},"fshader绑定","FShader绑定",[737,1164,1167],{"className":1165,"code":1166,"language":742},[740],"IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)\n",[744,1168,1166],{"__ignoreMap":342},[11,1170,1171],{},"这个是实际将C++的类与Shader进行绑定的宏。",[11,1173,1174],{},"在引擎中能够看到很多这个宏，例如",[737,1176,1179],{"className":1177,"code":1178,"language":742},[740],"IMPLEMENT_MATERIAL_SHADER_TYPE(,FVelocityVS,TEXT(\"\u002FEngine\u002FPrivate\u002FVelocityShader.usf\"),TEXT(\"MainVertexShader\"),SF_Vertex);\n",[744,1180,1178],{"__ignoreMap":342},[11,1182,1183],{},"这个宏将FVelocityVS绑定到VelocityShader.usf中，而MainVertexShader是shader端的入口函数。",[11,1185,1186],{},"最后一个ShaderFrequency感觉上更加接近Shader的类型：",[737,1188,1191],{"className":1189,"code":1190,"language":742},[740],"enum EShaderFrequency\n{\nSF_Vertex            = 0,\nSF_Hull                = 1,\nSF_Domain            = 2,\nSF_Pixel            = 3,\nSF_Geometry            = 4,\nSF_Compute            = 5,\n\nSF_NumFrequencies    = 6,\n\nSF_NumBits            = 3,\n};\n",[744,1192,1190],{"__ignoreMap":342},[11,1194,1195],{},"第一个参数用于宏的进一步模板化，在上面的BasePass进行绑定的时候就能看到",[737,1197,1200],{"className":1198,"code":1199,"language":742},[740],"IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE\n",[744,1201,1199],{"__ignoreMap":342},[11,1203,1204],{},"这个宏对这个参数的使用。",[48,1206,1208],{"id":1207},"vertexfactory绑定","VertexFactory绑定",[737,1210,1213],{"className":1211,"code":1212,"language":742},[740],"IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly)\n",[744,1214,1212],{"__ignoreMap":342},[11,1216,1217],{},"这个宏将VertexFactory绑定到对应的shader中去",[11,1219,1220],{},"例如",[737,1222,1225],{"className":1223,"code":1224,"language":742},[740],"IMPLEMENT_VERTEX_FACTORY_TYPE(FGPUSkinPassthroughVertexFactory, \"\u002FEngine\u002FPrivate\u002FLocalVertexFactory.ush\", true, false, true, false, false);\n",[744,1226,1224],{"__ignoreMap":342},[11,1228,1229],{},"这个绑定使得FGPUSkinPassthroughVertexFactory与LocalVertexFactory.ush进行关联，能够看到有很多不同的VertexFactory绑定到了这里，而且VertexFatory在各个不同的ush里面都有定义。",[11,1231,1232],{},"这是因为前面有提到MeshShader是有很多实例，每一个实例会根据自己的光照类型、数据类型进行不同的绑定。",[43,1234,1236],{"id":1235},"shader-plugin","Shader Plugin",[11,1238,1239],{},"从UE4.17开始，已经可以在插件中自己定义Shader并进行调用了。不过UE4的核心渲染流程依然没有开放，所以想要自定义Shader Model之类的话还是必须对源码进行修改。",[11,1241,1242,1243,1248],{},"详细的操作可以参考[",[18,1244,1247],{"href":1245,"rel":1246},"https:\u002F\u002Fdocs-origin.unrealengine.com\u002Flatest\u002FINT\u002FProgramming\u002FRendering\u002FShaderInPlugin\u002FQuickStart\u002Findex.html",[22],"官方文档","]，不过官方文档的操作没有进行充分的解释，只是教你怎么把引擎插件的LensDistortion插件给拷贝并修改成自己的插件，不过刚好可以方便对Shader绑定进行理解。",[48,1250,1251],{"id":1251},"基础重载",[11,1253,1254],{},"ShouldCache用于定义是否在指定的平台要编译这个材质。",[11,1256,1257],{},"ModifyCompilationEnvironment用于在特定的平台上添加自己的定义，但是示例中直接就加进了两个自己的定义",[737,1259,1262],{"className":1260,"code":1261,"language":742},[740],"OutEnvironment.SetDefine(TEXT(\"GRID_SUBDIVISION_X\"), kGridSubdivisionX);\nOutEnvironment.SetDefine(TEXT(\"GRID_SUBDIVISION_Y\"), kGridSubdivisionY);\n",[744,1263,1261],{"__ignoreMap":342},[48,1265,1266],{"id":1266},"参数绑定",[11,1268,1269],{},"可以看到FLensDistortionUVGenerationShader继承自FGlobalShader，然后添加了",[737,1271,1274],{"className":1272,"code":1273,"language":742},[740],"FShaderParameter PixelUVSize;\nFShaderParameter RadialDistortionCoefs;\nFShaderParameter TangentialDistortionCoefs;\nFShaderParameter DistortedCameraMatrix;\nFShaderParameter UndistortedCameraMatrix;\nFShaderParameter OutputMultiplyAndAdd;\n",[744,1275,1273],{"__ignoreMap":342},[11,1277,1278],{},"几个成员，然后在构造中绑定",[737,1280,1283],{"className":1281,"code":1282,"language":742},[740],"PixelUVSize.Bind(Initializer.ParameterMap, TEXT(\"PixelUVSize\"));\nRadialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT(\"RadialDistortionCoefs\"));\n",[744,1284,1282],{"__ignoreMap":342},[11,1286,1287],{},"第二个参数是参数在Shader中的名称。",[11,1289,1290],{},"之后就可以通过",[737,1292,1295],{"className":1293,"code":1294,"language":742},[740],"SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue);\nSetShaderValue(RHICmdList, ShaderRHI, DistortedCameraMatrix, CompiledCameraModel.DistortedCameraMatrix);\n",[744,1296,1294],{"__ignoreMap":342},[11,1298,1299],{},"来进行修改了。",{"title":342,"searchDepth":343,"depth":344,"links":1301},[1302,1306,1310,1316],{"id":861,"depth":343,"text":862,"children":1303},[1304,1305],{"id":868,"depth":344,"text":868},{"id":896,"depth":344,"text":896},{"id":924,"depth":343,"text":924,"children":1307},[1308,1309],{"id":930,"depth":344,"text":931},{"id":969,"depth":344,"text":970},{"id":1056,"depth":343,"text":1057,"children":1311},[1312,1313,1314,1315],{"id":1060,"depth":344,"text":1061},{"id":1094,"depth":344,"text":1095},{"id":1122,"depth":344,"text":1123},{"id":1138,"depth":344,"text":1139},{"id":1148,"depth":343,"text":1149,"children":1317},[1318,1322],{"id":1155,"depth":344,"text":1155,"children":1319},[1320,1321],{"id":1161,"depth":351,"text":1162},{"id":1207,"depth":351,"text":1208},{"id":1235,"depth":344,"text":1236,"children":1323},[1324,1325],{"id":1251,"depth":351,"text":1251},{"id":1266,"depth":351,"text":1266},{"layout":371,"status":372,"published":373,"author":1327,"author_login":376,"author_email":377,"wordpress_id":1328,"wordpress_url":1329,"date_gmt":1330,"excerpt":1331},{"display_name":375,"login":376,"email":377,"url":342},2198,"\u002F\u002F?p=2198","2017-12-31 16:01:54 +0000",{"type":8,"value":1332},[1333],[11,1334,849],{},"\u002F2018-01-01-ue4-rendering-code-view-01",{"title":844,"description":849},"_legacy\u002F2018\u002F2018-01-01-ue4-rendering-code-view-01",[1339,389],"shader","rDtA9FsRgIkV52wCHpGUtcAfohNBZfb4k6UKJoiKUyo",{"id":1342,"title":1343,"body":1344,"date":1511,"description":1348,"extension":369,"meta":1512,"navigation":373,"path":1521,"seo":1522,"stem":1523,"tags":1524,"__hash__":1526},"blogs\u002F_legacy\u002F2017\u002F2017-12-16-ue4-algo-note.md","UE4算法帮助类笔记",{"type":8,"value":1345,"toc":1499},[1346,1349,1352,1355,1358,1361,1364,1368,1371,1374,1377,1380,1386,1390,1393,1396,1402,1405,1408,1411,1414,1417,1429,1432,1435,1438,1441,1444,1447,1451,1454,1459,1462,1465,1468,1477,1481,1484,1489,1492,1496],[11,1347,1348],{},"UE4有提供一套算法帮助类，命名空间为Algo，对于需要快速使用的情况可以参考。",[11,1350,1351],{},"当前使用的UE4版本为4.18.2。",[11,1353,1354],{},"整个Algo的定义分散在各个文件中，按照功能区分进行分布。",[11,1356,1357],{},"在定义上全部都是模板函数，通用性比较强。",[35,1359,1360],{"id":1360},"数据整理",[11,1362,1363],{},"这一块是对内存数据块进行整理的通用模板。",[43,1365,1367],{"id":1366},"copy","Copy",[11,1369,1370],{},"这是一个系列的拷贝模板函数，主要的工作就是对数据进行拷贝。",[11,1372,1373],{},"有一个CopyIf的变体，只会把符合条件的数据拷贝到目标中去。",[11,1375,1376],{},"通常需要拷贝的容器是Arrat，Map之类的，对于这个模板，只要保证源容器实现了range for和目标容器实现了Add函数就可以了。",[11,1378,1379],{},"当然，数据的类型必须至少是可转换的类型。",[737,1381,1384],{"className":1382,"code":1383,"language":742},[740],"TArray\u003Cint> TestArray;\nTArray\u003Cint> TestData;\nAlgo::Copy(TestData, TestArray);\n",[744,1385,1383],{"__ignoreMap":342},[43,1387,1389],{"id":1388},"transform","Transform",[11,1391,1392],{},"对数据进行某种操作，并填充到目标。",[11,1394,1395],{},"也有这Transfor_If的变体，要求传入的Transform操作必须返回要填充到目标的类型。",[737,1397,1400],{"className":1398,"code":1399,"language":742},[740],"Algo::Transform(TestData, TestArray, [](int i) { return FMath::DegreesToRadians(i); });\ncheck(TestArray.Num() == NUM_TEST_OBJECTS);\nfor (int i = 0; i \u003C TestArray.Num(); ++i)\n{\n   check(TestArray[i] == FMath::DegreesToRadians(TestData[i]));\n}\n",[744,1401,1399],{"__ignoreMap":342},[11,1403,1404],{},"这里由于源与目标的数据类型可以是不一致的，再加上有If变体，其实是一个非常好用的数据批量处理模板。",[11,1406,1407],{},"这里传入的if条件和转置条件即可以是成员的指针也可以是成员的函数，在使用的时候可以灵活的采用。",[35,1409,1410],{"id":1410},"二分搜索",[11,1412,1413],{},"内部封装的二分搜索算法，不用每次想要进行搜索的时候临时到Google或者代码库中去翻找搜索算法的代码了。",[11,1415,1416],{},"提供一下三个基本的函数",[1418,1419,1420,1423,1426],"blockquote",{},[11,1421,1422],{},"BinarySearch",[11,1424,1425],{},"LowerBound",[11,1427,1428],{},"UpperBound",[11,1430,1431],{},"功能就如同函数名称一样，是搜索，最小值和最大值的查找。",[35,1433,1434],{"id":1434},"排序",[11,1436,1437],{},"封装了几个排序算法，不过由于排序算法早就还给课本了，在这里就不比较性能了。",[11,1439,1440],{},"毕竟一直使用std的sort也没怎么关心过其内部实现，虽然TArray也是有封装sort操作的，这里也就不去看其实现了。",[11,1442,1443],{},"整体的排序上，有一个Algo::IsSorted()函数可以用于检查数据是否已经经过了排序。",[11,1445,1446],{},"同时排序算法都有提供SortBy的变体以方便进行更加精致的排序。",[43,1448,1450],{"id":1449},"heap","Heap",[11,1452,1453],{},"严格的来说也提供了不是排序的部分。这里引用一下Wiki",[1418,1455,1456],{},[11,1457,1458],{},"a heap is a specialized tree-based data structure",[11,1460,1461],{},"给和我一样一下子想不起来Heap到底是什么的筒子。",[11,1463,1464],{},"可以Algo::Heapify(TestArray)来将一个数组转换为Heap，同时提供了IsHeap函数来检查一个数组是否是以Heap的形式保存的。",[11,1466,1467],{},"然后Algo::HeapSort()才是进行堆排序的，其结果满足Algo::IsHeap以及Algo::IsSorted。",[11,1469,1470,1471,1476],{},"另外，根据资料显示，[",[18,1472,1475],{"href":1473,"rel":1474},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F19336881\u002Fwhy-isnt-heapsort-stable",[22],"HeapSort并不是一个stable的排序算法","]，记得TArray是有提供StableSort的，果然其内部应该是有别的排序实现的。",[43,1478,1480],{"id":1479},"introsort","IntroSort",[11,1482,1483],{},"这个可以明确的记得当初并没有学过呢，再次引用Wiki",[1418,1485,1486],{},[11,1487,1488],{},"Introsort or introspective sort is a hybrid sorting algorithm that provides both fast average performance and (asymptotically) optimal worst-case performance.",[11,1490,1491],{},"似乎是叫内省排序总之是相当优秀的排序算法，只是不是Stable的。",[43,1493,1495],{"id":1494},"sort","Sort",[11,1497,1498],{},"直接调用Sort就可以了，Unstable，并没有解释到底用的是什么算法。也没有去关注它的实现……",{"title":342,"searchDepth":343,"depth":344,"links":1500},[1501,1505,1506],{"id":1360,"depth":343,"text":1360,"children":1502},[1503,1504],{"id":1366,"depth":344,"text":1367},{"id":1388,"depth":344,"text":1389},{"id":1410,"depth":343,"text":1410},{"id":1434,"depth":343,"text":1434,"children":1507},[1508,1509,1510],{"id":1449,"depth":344,"text":1450},{"id":1479,"depth":344,"text":1480},{"id":1494,"depth":344,"text":1495},"2017-12-16",{"layout":371,"status":372,"published":373,"author":1513,"author_login":376,"author_email":377,"wordpress_id":1514,"wordpress_url":1515,"date_gmt":1516,"excerpt":1517},{"display_name":375,"login":376,"email":377,"url":342},2158,"\u002F\u002F?p=2158","2017-12-16 08:35:14 +0000",{"type":8,"value":1518},[1519],[11,1520,1348],{},"\u002F2017-12-16-ue4-algo-note",{"title":1343,"description":1348},"_legacy\u002F2017\u002F2017-12-16-ue4-algo-note",[389,1525,1495],"Algo","zFRtkSrzpzFpfSpjsqSMzc-9RQQLrc9SyeofTK2pIHw",85,1788763178248]