-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
580 lines (297 loc) · 190 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Luhao's Blog</title>
<subtitle>luhao wiki</subtitle>
<link href="http://luhao.wiki/atom.xml" rel="self"/>
<link href="http://luhao.wiki/"/>
<updated>2024-03-01T05:33:17.404Z</updated>
<id>http://luhao.wiki/</id>
<author>
<name>Luhao</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>【Graphics-2022】Texture 纹理</title>
<link href="http://luhao.wiki/posts/2022-texture/"/>
<id>http://luhao.wiki/posts/2022-texture/</id>
<published>2023-12-10T13:54:10.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<h2 id="定义">定义</h2><p>图形学中有两个与图片相关的概念,分别是 Image(贴图)和 Texture(纹理),可以这样区分它们:</p><ul><li>Image:内存意义上的图片,例如 <code>tga, png, jpg</code></li><li>Texture:GPU意义上的图片,例如 <code>RGBA8</code> 格式的一对像素组合</li></ul><p>因此 Texture 一定是以 GPU像素格式来申明的,并且需要规定 <code>Sampler</code> 的规则。</p><h2 id="Color-Space">Color Space</h2><p>参考阅读:</p><ul><li><a href="https://luhao.wiki/posts/rtr-5/#%E6%98%BE%E7%A4%BA%E7%BC%96%E7%A0%81">【RealtimeRendering】5. Shading Basic > ColorSpace | Luhao’s Blog</a></li><li><a href="https://zhuanlan.zhihu.com/p/66558476">Gamma、Linear、sRGB 和Unity Color Space,你真懂了吗? - 知乎</a></li></ul><h2 id="Texture-Mapping">Texture Mapping</h2><p>将贴图的坐标映射到模型表面,通常采用一种 $u, v$ 坐标的形式,取值为 $[0, 1]$ 的浮点数。<br><img src="../../images/Graphics2022-Texture_18326.png" alt=""><br>贴图一般都是规规矩矩的方形,但模型的表面却差异很大,这套映射关系是如何选择的?下面展示三种基本的 texture mapping:</p><ul><li>Planar:只考虑两个维度的<strong>平面映射</strong></li><li>Cubic:考虑三个维度的<strong>立方体映射</strong></li><li>Cylindrical:<strong>柱状映射</strong><br><img src="../../images/Graphics2022-Texture_45479.png" alt=""></li></ul><p>此时,一个<strong>从贴图到模型</strong>的完整映射流程是:<br><img src="../../images/Graphics2022-Texture_26581.png" alt=""></p><h3 id="Texture-Tiling">Texture Tiling</h3><p>上面讨论的是 uv 映射 最完美的情况(即 texture Sampler 每个采样点与 Image <strong>一一对应</strong>),然而实际应用中,这两者很难对上。</p><p><strong>Q:当采样点落在多个 Image 像素之间怎么办?</strong></p><ol><li><p>如果粗暴地选取最近的一个像素,那么会导致严重的走样,不可取!称为 <a href="https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation"><strong>Nearest-neighbor Filtering</strong></a></p></li><li><p>传统的方案是增加到四次采样,并在这四个像素之间作插值,称为 <a href="https://en.wikipedia.org/wiki/Bilinear_interpolation"><strong>Bilinear interpolation</strong></a><br><img src="../../images/Graphics2022-Texture_48684.png" alt=""></p></li><li><p>考虑到当图片与屏幕呈倾斜角度时,我们需要<strong>更多的采样点</strong>来铺满该区域!这是硬件层面实现的 <a href="https://en.wikipedia.org/wiki/Anisotropic_filtering"><strong>Anisotropic Filtering</strong></a>。其中 <code>4X AF</code> 表示需要多采样4倍的pixel;<code>16X AF</code> 同理<br><img src="../../images/anisotropic.png" alt=""></p></li></ol><hr><p>参考阅读:<a href="https://en.wikipedia.org/wiki/Texture_filtering">Texture filtering - Wikipedia</a><br>越多的采样数往往意味着性能开销更大,下面是各种方案的采样数消耗:</p><table><thead><tr><th style="text-align:center">Filering</th><th style="text-align:center">Samples</th></tr></thead><tbody><tr><td style="text-align:center">nearest-neighbor</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">bilinear</td><td style="text-align:center">4</td></tr><tr><td style="text-align:center">trilinear</td><td style="text-align:center">8</td></tr><tr><td style="text-align:center">AF 4X</td><td style="text-align:center">32</td></tr><tr><td style="text-align:center">AF 16X</td><td style="text-align:center">128</td></tr></tbody></table><h3 id="Texture-Tiling-2">Texture Tiling</h3><p>当你翻出一张陈年老照片(分辨率很低),想将它作为 4k 显示屏的壁纸时,问题就出现了:<strong>待采样的贴图不足以铺满整个屏幕</strong>。<br>此时就需要 <code>Texture Tiling</code>,即考虑怎么将贴图放大、堆叠,传统的做法有如下几种:</p><ul><li>下图忽略了 <code>Repeat</code> 模式<br><img src="../../images/Graphics2022-Texture_31282.png" alt=""></li></ul><h3 id="Interpolation">Interpolation</h3><p>考虑下面这种情形,我们需要将半张图片渲染到 (下左图)经过透视变换的区域内。而 Vertex Shading 阶段<strong>只会接受三个顶点(及其UV属性)</strong>,那么实际采样中应该如何确定区域内每个像素的 uv 取值呢?<br><img src="../../images/Graphics2022-Texture_47237.png" alt=""></p><p>显然这需要选取合适的插值方法!我们可以将这个问题规纳为一般情形(上右图):</p><ul><li>已知三个顶点 $A, B, C$ 及其 $uv$ 属性</li><li>求三角形内任意顶点的 $uv$ 取值</li></ul><p>考虑最简单的线性插值方案:<br>$$<br>P(x, y) = \alpha A + \beta B + \gamma C \\<br>\alpha + \beta + \gamma = 1<br>$$<br>通常会获得如下的效果(下图中),显然是错误的。<br><img src="../../images/Graphics2022-Texture_12104.png" alt=""></p><p>上图的原因用一句话概括为:<strong>ViewSpace 的线性变换不等价于 ScreenSpace的线性变换。</strong><br>解决方案可阅读如下链接,暂时没理解公式:</p><ul><li><a href="https://lxjk.github.io/2017/06/10/Conversion-between-View-Space-Linear-and-Screen-Space-Linear.html">Conversion between View Space Linear and Screen Space Linear - Eric’s Blog</a></li><li><a href="https://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf">comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf</a></li></ul>]]></content>
<summary type="html"><h2 id="定义">定义</h2>
<p>图形学中有两个与图片相关的概念,分别是 Image(贴图)和 Texture(纹理),可以这样区分它们:</p>
<ul>
<li>Image:内存意义上的图片,例如 <code>tga, png, jpg</code></li>
<</summary>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/"/>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/Graphics/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
</entry>
<entry>
<title>【RealtimeRendering】5. Shading Basic</title>
<link href="http://luhao.wiki/posts/rtr-5/"/>
<id>http://luhao.wiki/posts/rtr-5/</id>
<published>2023-12-07T16:02:09.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<blockquote><p><code>Pixel Shading</code> 阶段会决定每个像素最终的颜色和透明度,而决定这些颜色的公式,就是所谓的 <code>Shading Model</code></p></blockquote><h3 id="前言">前言</h3><p>这一部分其实更多是面对 TA 向的内容,掌握 Shading 基础,是各种 真实感渲染、卡通渲染… 的技术基础。虽然说大多商业引擎已经实现了非常少成熟的 PBR、NBR … 渲染框架,网上各种解决方案、优化技术也非常普遍,但是作为引擎(图形)开发程序,掌握这些基础的 Shading 知识仍然是非常必要的。</p><p>23年底时,曾面试 <code>Garena</code> 的图形引擎岗位,其中一个基础的问题没答完整:“请介绍 Phong Shading 的渲染公式”。这个经历提醒自己,一定要夯实基础,万丈高楼平地起,积沙成塔,只有底部足够扎实,才能爬得更高。</p><h2 id="Shading-Models">Shading Models</h2><p><code>RealTimeRendering</code> 作者选择将这个章节放在 <code>Textures</code> 纹理之前,我觉得是有失偏颇的。应该是先有纹理贴图,继而发现朴素的贴图缺少光照,表现非常虚假、平淡,继而引入一系列逐渐复杂的 Shading Models,其目的是 <strong>优化纹理在不同光照、视角、及法线下的视觉表现</strong>,只不过通过一些 <strong>计算调参</strong> 的手段罢了。</p><p>我们从 <strong>第一性原理</strong> 出发,先考虑最简单的原则:</p><h4 id="1">1.</h4><p>将采样的 <code>Texture</code> 简单粗暴的渲染出来,即 Shading Model 是:<br>$$C_{shading}=C_{tex}$$</p><p>参考 <a href="https://learnopengl.com/Getting-started/Camera">LearnOpenGL</a>的示例,得到的效果是<strong>明显不真实</strong>的:<br><img src="../../images/RTR-5-Shading_45121.png" alt=""></p><h4 id="2">2.</h4><p>为什么这个最简单的 Shading Model 渲染出的结果具有强烈的不真实感?想象真实世界的物体,最主要的特征是(太阳)光线带来的强烈视觉感,而这个视觉感与远近没有明显的关系,影响最大的因素是<strong>与光线的夹角</strong>。</p><p>如果难以理解,想象一束光照射在镜子上:</p><ul><li>当垂直入射时,是刺眼的白色(假设白光)</li><li>当(接近)水平入射时,是接近物体本身的颜色</li></ul><p>根据这个从物理世界观察到的规律,我们进一步优化 Shading Model:<br>$$<br>C_{Shading} =<br>\begin{cases}<br>C_{Light} & if \ angle \geq N^\circ \\<br>C_{Tex} & else\\<br>\end{cases}<br>$$<br>实际计算可以作一些插值,以太阳光照射水面为例,效果大概如下所示:<br><img src="../../images/RTR-5-Shading_19391.png" alt=""></p><h4 id="3-Gooch-Shading">3. Gooch Shading</h4><p>上面都是自己的瞎扯,这里尝试进入正题。<code>Gooch Shading</code> 是一个足够简单但经典的着色模型,它将光照颜色分为两部分区域:</p><ul><li>法线越接近光照:使用暖色调</li><li>法线越远离光照:使用冷色调</li></ul><p>具体公式如下,其中 <code>2(n*l)n - l</code> 是用来计算 <code>l</code> 相对于法线的反射向量,在 shader 可以使用 <code>reflect</code> 函数代替:<br><img src="../../images/RTR-5-Shading_53237.png" alt=""><br><img src="../../images/RTR-5-Shading_12110.png" alt=""><br><img src="../../images/RTR-5-Shading_52010.png" alt=""></p><div class="admonition note"><p class="admonition-title">如何区分 冷色调 和 暖色调?</p><ul><li><code>Cool</code>:偏蓝色的</li><li><code>Warm</code>:偏红色、橘色的</li></ul></div><hr><p>鉴于这个特征,<code>Gooch Shading</code> 又被称为 <code>Cool to Warm Shading</code>,实际效果如图所示:<br><img src="../../images/RTR-5-Shading_57107.png" alt=""></p><h4 id="4-Lambertian-Shading">4. Lambertian Shading</h4><p><a href="https://luhao.wiki/posts/shading/#font-color-750000-Lambertian-Reflection-%E2%AD%90-font">【GAMES101】Shading - Lambertian Shading | Luhao’s Blog</a></p><h4 id="5-Phong-Shading">5. Phong Shading</h4><p><a href="https://luhao.wiki/posts/shading/#font-color-750000-Blinn-Phong-Reflection-%E2%AD%90-font">【GAMES101】Shading - Phong Shading | Luhao’s Blog</a></p><h2 id="Light-Sources">Light Sources</h2><p>想要更好地描述光源对于物体表面渲染的影响,我们需要对光照这个行为进行定量地分析,这里列出 RTR 书中的分析思路:</p><h4 id="1-光-射线">1. 光 == 射线</h4><p>先将 光照对表面的影响,可视化为 <strong>一组平行的射线</strong>,同时射线的密度代表光照的强度。</p><ul><li>对于一个固定光源,不同射线的间距是固定的 $d$</li><li>垂直入射时,到达表面的长度是 $d$</li><li>倾斜入射时,到达表面的长度是 $d / cos\theta$</li><li>背面入射时($\theta \geq 90$),到达表面的长度是 $0$</li></ul><p>因为 $n \cdot l = cos\theta$,所以(单位)光照的影响长度为 $d / (n \cdot l)$<br><img src="../../images/RTR-5-Shading_20955.png" alt=""></p><p>考虑到光照随面积的分布是均匀的,当单位光照影响的长度越大,其所受光的影响也就越弱。<br>假设 $d$ 是一个单位为1的值,那么(单位)光照的影响强度为 $max(0, n \cdot l)$</p><h4 id="2-有光-无光">2. 有光 & 无光</h4><p>PS:这里有一点困惑,假设自然界完全无光的情况下,那么物体表现应该也是纯黑色?<br>本章将物体表面区分为两种状态:无光、有光的环境,而最终的呈现是这两种结果的组合。</p><ul><li>无光:$f_{unlit}$,阴影中死黑的部分</li><li>有光:$f_{lit}$,取决于光照公式的选取,如 <code>Lambert、Phong...</code></li><li>光源颜色:$c_{lit}$,通过缩放还可以表示光照的强度</li></ul><p>此时,对于一个光源的光照公式可以表示为:<br>$$C_{shading} = f_{unlit} + c_{light} f_{lit}$$<br>如果扩展到多个光源,那么有:<br>$$C_{shading} = f_{unlit} + (\sum_{i=1}^{n}c_{light} f_{lit})$$<br>结合前面 <code>Shading Model</code> 介绍的 <code>Gooch Shading</code> 模型,我们可以为上面公式套上:</p><ul><li>$f_{unlit} = (0, 0, 0)$</li><li>$f_{lit} = f_{Gooch\ Shading}$</li></ul><p>此时就得到一个完整的光照模型啦~ 但是考虑到自然界存在着无时无刻不发挥作用的间接光,这里将 $f_{unlit}$ 取为全死黑,显然是不科学的,后续可以继续改进。</p><h4 id="2-1-方向光">2.1 方向光</h4><p><code>Directional Light</code> 是一个最简单的光源模型,象征自然界的太阳。它具有如下特征:</p><ul><li>方向 $l$ 恒定,因此又称为 平行光</li><li>光源颜色(强度)$c_{light}$ 固定,不考虑任何衰减(伟大的太阳!)</li><li>没有位置的概念<br><img src="../../images/RTR-5-Shading_14188.png" alt=""></li></ul><p>因此在 <code>shading</code> 中可以考虑如下定义:</p><pre><code class="language-c++">struct DirectionalLight { vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular;};void main(){ vec3 lightDir = normalize(-light.direction); [...]}</code></pre><h4 id="2-2-点光">2.2 点光</h4><p><code>Point Light</code> 象征自然界的电灯泡,它的特征如下:</p><ul><li>方向 $l$ 向所有方向均匀发射光线</li><li>强度 $c_{light}$ 随距离衰减</li><li>有明确的位置概念</li></ul><p>RTR 书中使用下图解释 <strong>强度随距离衰减</strong>。考虑到单位光线影响的范围,随距离 $r$ 的增大而平方增长,因此:光线强度与 $1 / r^{2}$ 成正比。<br>注意,这个衰减并不是因为 <strong>能量随传播的衰减</strong>。<br><img src="../../images/RTR-5-Shading_44438.png" alt=""></p><p>因此,对于<strong>距离为 $r$ 处的光源强度</strong>可以表示为:(这里选取了一个参考值 $c_{light_{0}}$,表示光源在距离为 $r_{0}$时的光照参数,你可以通过度量等方式定义它)<br>$$c_{light_{r}} = c_{light_{0}} (\frac{r_{0}}{r})^{2}$$<br>这个公式称为 <a href="https://en.wikipedia.org/wiki/Inverse-square_law">Inverse-square law - Wikipedia</a>,即平方反比定律,物理学中存在非常多类似的例子,例如:万有引力定律、库仑定律…<br>在实际的工程使用中,它存在一个非常明显的问题,即当 <strong>$r$ 无穷趋近于0时</strong>(或者干脆取值为0),那么光源强度会是一个趋向无穷大的取值,这显然是无法接受的。对此,商业引擎有几种优化手段:</p><ul><li><strong>Unreal:距离叠加一个极小的数,实际取值是 $1 cm$</strong><br>$$c_{light_r}=C_{light_0}{\frac{r_{0}^{2}}{r^{2}+\epsilon}}.$$</li><li><strong>CryEngine:限定 $r$ 的最小值</strong><br>$$c_{light_{r}}=c_{light_{0}} (\frac{r_{0}} {max(r,r_{m i n})})^{2}$$</li></ul><div class="admonition note"><p class="admonition-title">物理学解释</p><ul><li>从物理学角度解释,<code>CryEngine</code> 的做法更科学。因为$r_{min}$在物理中表示发光物体的物理半径,比它还小的距离,对应着光源内部的着色表面,这在现实中是不可能发生的。</li></ul></div><p>实际开发中,为了性能考虑,我们希望 <strong>光照强度在某个有限的距离处,能够乖乖地衰减到0</strong>,因此会引入一些距离衰减函数来实现这一目的,其中甚至包括指数衰减,这里不详细介绍。</p><p>另一方面,为了<strong>让点光的效果更贴近现实</strong>,<code>OGRE Engine</code> 引入一些复杂的衰减函数来实现点光,参考阅读:</p><ul><li><a href="https://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation">-Point Light Attenuation | Ogre Wiki</a></li></ul><h4 id="2-3-聚光灯">2.3 聚光灯</h4><p>TODO: <code>SpotLight</code></p><div class="admonition note"><p class="admonition-title">多种光源对比</p><ul><li>从左到右依次为:平行光、点光(无衰减)、聚光灯(有衰减)</li><li><img src="../../images/RTR-5-Shading_47228.png" alt=""></li></ul></div><h2 id="Anti-Aliasing">Anti-Aliasing</h2><p>抗锯齿的部分,看 GAMES101 时有作总结,这里不详细展开。不过有机会还是希望实践落地 SMAA、TAA …</p><ul><li><a href="https://luhao.wiki/posts/games101-aa/">【GAMES101】Anti-Aliasing | Luhao’s Blog</a></li><li><a href="https://www.highperformancegraphics.org/wp-content/uploads/2017/Retrospective/HPG2017_Reshetov_MLAARetrospective.pdf">[HPG 2017] MLAA from 2009 to 2017.pdf</a></li></ul><h2 id="半透明">半透明</h2><p>TODO:这部分讨论的是,光线穿过半透明物体的效果。</p><h2 id="显示编码">显示编码</h2><p>推荐阅读:</p><ul><li><a href="https://zhuanlan.zhihu.com/p/66558476">Gamma、Linear、sRGB 和Unity Color Space,你真懂了吗? - 知乎</a></li><li><a href="https://gwb.tencent.com/community/detail/120396">伽马空间与线性空间详解-腾讯游戏学堂</a></li><li><a href="https://kinematicsoup.com/news/2016/6/15/gamma-and-linear-space-what-they-are-how-they-differ#:~:text=non%2Dlinear%20space-,Gamma%20Space,shades%20better%20than%20lighter%20shades.">Gamma and Linear Space - What They Are and How They Differ</a></li></ul><h4 id="Gamma-Space">Gamma Space</h4><p><code>Gamma Space</code> 将颜色输出为 2.2 次幂,所谓伽马矫正是指如下公式:<br>$$C_{gamma} = (C_{linear})^{2.2}$$<br>为什么会引入伽马矫正?一般有两个原因:</p><ul><li>传统 CRT 显示器的设计原因</li><li>人眼对暗部辨识度高于明部</li></ul><p>以第2点为例,我们可以理解为:如果在 0% 和 50% 明暗处分别增加 10% 的亮度,那么<strong>人眼对前者的感知更加明显</strong>,这也意味着人眼对暗处更敏感。换言之,我们应该给暗处更大的存储、展示细节。<br><img src="../../images/RTR-5-Shading_50654.png" alt=""></p><hr><p>如下图,中间是标准的线性空间,右侧是 <code>Gamma-2.2</code> 空间,可以这么理解:</p><ul><li>左一:给亮部更大精度</li><li>右一:给暗部更大精度</li></ul><p>显然<strong>右一更符合人眼的观感</strong>,这正解释了为什么要将图像转化到 Gamma 空间!另外,我们购买的显示器一般也是 Gamma2.2 空间(又称为 sRGB)。<br><img src="../../images/RTR-5-Shading_57381.png" alt=""></p><hr><p>虽然 Gamma Space 更符合人眼的观感,但是它不利于 Shading 的计算,因此一个正常的渲染流程是:</p><ul><li>png/jpg:sRGB</li><li>shading:linear</li><li>display:sRGB(取决于硬件,一般是 sRGB 空间)<br><img src="../../images/RTR-5-Shading_54553.png" alt=""></li></ul><div class="admonition note"><p class="admonition-title">sRGB</p><ul><li><code>sRGB</code> 又称为 Gamma-0.45空间,即会给亮部更多的精度和细节。</li><li>当 <code>sRGB</code> 叠加一次 Gamma矫正 之后,就会得到一个正确的 Linear空间。</li></ul></div><h2 id="参考资料">参考资料</h2><ul><li><a href="https://www.logiconsole.com/real-time-rendering-4/">Real-Time Rendering 4th Edition学习笔记(四) | Logiconsole</a></li><li><a href="https://ciel1012.github.io/2019/07/07/rtr5/">Shading Basics - 就决定是你了 | Ciel’s Blog</a></li></ul>]]></content>
<summary type="html"><blockquote>
<p><code>Pixel Shading</code> 阶段会决定每个像素最终的颜色和透明度,而决定这些颜色的公式,就是所谓的 <code>Shading Model</code></p>
</blockquote>
<h3 id="前言">前言</</summary>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/"/>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/RealtimeRendering/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
<category term="C++" scheme="http://luhao.wiki/tags/C/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>【Graphics-2022】图形API</title>
<link href="http://luhao.wiki/posts/18QJ9Y1/"/>
<id>http://luhao.wiki/posts/18QJ9Y1/</id>
<published>2023-12-05T16:46:13.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<div class="admonition note"><p class="admonition-title">推荐阅读:</p><ul><li><a href="https://v.netease.com/evideo/video_course/show?course_id=18956">2022图形引擎-内部资料</a></li></ul></div><h2 id="概要">概要</h2><p>为什么需要图形API?</p><ul><li>暴露图形硬件的功能(GPU),并抽象出高->低维度的接口</li><li>用作 realtime rendering</li></ul><p>图形API发展历史:(主要是15年左右诞生 Metal、DX12、Vulkan)</p><ul><li><img src="../../images/rtr-graphics-api.png" alt=""></li></ul><p>现代图形API的发展方向:</p><ul><li>降低 CPU 性能瓶颈</li><li>多线程</li><li>优越的开发能力</li></ul><p>从 High-Level -> Low-Level 角度来看:</p><ul><li>High-Level:意味着封装层次更高,性能较差</li><li>Low-Level:以为这封装较低,学习成本陡峭<br><img src="../../images/2022-graphics-API_24124.png" alt=""></li></ul><p>整个完整的图形引擎调用栈:</p><h2 id=""><img src="../../images/2022-graphics-API_09984.png" alt=""></h2><h3 id="图形API-vs-GPU">图形API vs GPU</h3><ul><li>图形API = Resource Manager + Commnad Producer</li><li>GPU = Commnad Consumer + Execute async</li></ul><p>从 <strong>生产者消费者模型</strong> 理解:</p><ul><li>图形API:从CPU端 创建资源 + 产生一系列 DrawCalls</li><li>GPU:指令的消耗 和 异步执行。</li></ul><blockquote><p><em><strong>GPU = Async Execute Engine</strong></em> 一个异步执行的引擎<br>CPU 永远领先 GPU 1~3 帧<br><img src="../../images/2022-graphics-API_24172.png" alt=""></p></blockquote><hr><h3 id="Single-Commnad">Single Commnad</h3><p>一个基本的 CPU 渲染指令,应该包含如下要素:</p><ul><li>Command ID</li><li>操作数(注意有大小限制,后面会说)</li><li>CPU地址(GPU可访问的)</li><li>GPU地址</li></ul><p><img src="../../images/2022-graphics-API_09197.png" alt=""></p><hr><div class="admonition warning"><p class="admonition-title">问题</p><ul><li><strong>如何向 Commnad 传递一波数据?</strong></li></ul></div><ul><li>Method 1:直接传递操作数 <code>(<64 Bytes)</code>,一般只有特定的API支持这么做,例如 <a href="https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushConstants.html"><code>vkCmdPushConstants</code></a><ul><li><img src="../../images/2022-graphics-API_45028.png" alt=""></li></ul></li><li>Method 2:拷贝到显存(GPU可访问的),然后传地址进去。注意避免 CPU写 + GPU读 的情形发生<ul><li><img src="../../images/2022-graphics-API_13901.png" alt=""></li></ul></li><li>Method 3:在Method 2的基础上,通过 <code>Blit</code> 将数据拷到 GPU内存,然后传GPU地址<ul><li>如果 <code>Blit</code> 一次,但是 <code>Read</code> 多次,那么收益比较高</li><li><img src="../../images/2022-graphics-API_20940.png" alt=""></li></ul></li></ul><h2 id="API结构">API结构</h2><p>前面说过,图形API分为两大类:Resource Manager + Command Producer:<br><img src="../../images/2022-graphics-API_50546.png" alt=""></p><p>以具体的API为例:<br><img src="../../images/2022-graphics-API_20155.png" alt=""></p><h2 id="API-一帧的调用">API 一帧的调用</h2><ol><li>Create Resource<ul><li>Texture / VertexBuffer / IndexBuffer …</li></ul></li><li>Set RenderPass</li><li>Set PipelineState<ul><li>Shader / BlendState / DepthState</li></ul></li><li>Bind Shader Resources<ul><li>Uniform / Buffer …</li></ul></li><li>DrawCall</li><li>Present</li></ol><h2 id="1-Resources">1. Resources</h2><p>第一步是资源管理,对应 <code>Create Resources</code> 的部分。根据资源的类型还可以细分如下:<br><img src="../../images/2022-graphics-API_13785.png" alt=""></p><h3 id="Resource-Memory">Resource Memory</h3><p>上图框出来的部分,需要注意**内存的开销<em>j</em>( Buffer、Image)。对于图形API中的内存分配方式,一共分为两种:</p><ul><li>自动分配:DX11/OpenGL/Metal</li><li>手动分配:DX12/Vulkan/Metal</li></ul><p>对于手动分配的方式,有一个好处是 <code>resouce aliasing</code>,多个资源可以共用一块内存(真节省呀!),参考阅读:</p><ul><li><a href="https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/resource_aliasing.html">Vulkan Memory Allocator: Resource aliasing (overlap)</a></li><li><a href="https://gpuopen-librariesandsdks.github.io/D3D12MemoryAllocator/html/resource_aliasing.html">Direct3D 12 Memory Allocator: Resource aliasing (overlap)</a></li></ul><hr><h3 id="内存架构">内存架构</h3><p><strong>对于PC端</strong>,CPU 和 GPU 都有独立的内存,有如下特点:</p><ul><li>GPU 内存传输快于 CPU (主要是带宽高,数据bus设计原因)</li><li>GPU/CPU 之间传输很慢</li></ul><p><img src="../../images/2022-graphics-API_29146.png" alt=""><br><br></p><p><strong>对于移动端</strong>,CPU 和 GPU 共用一张内存,有如下特点:</p><ul><li>考虑到低功耗(带宽变小),内存传输非常慢</li><li>GPU 部分有 <code>Tiled Memory</code> 的架构优化</li></ul><p><img src="../../images/2022-graphics-API_38013.png" alt=""></p><hr><h3 id="Memory-Types">Memory Types</h3><p>图形API中有不同的内存类型,区分如下:</p><ul><li><code>Default</code>:默认是 GPU 内存,不支持 CPU访问<ul><li>大多数资源的选择:buffers、textures、rt</li></ul></li><li><code>Dynamic</code>:指 CPU只写、GPU只读 的内存<ul><li>需要CPU每帧更新的资源( todo:举个栗子)</li></ul></li><li><code>Readback</code>:指 CPU只读、GPU只写 的内存(使用情况比较少)</li><li><code>Memoryless</code>:适用于 TBR 架构</li></ul><p><img src="../../images/2022-graphics-API_05483.png" alt=""></p><h2 id="2-Render-Pass">2. Render Pass</h2><p>对于 <code>Render Pass</code> 的定义:不切换 FrameBuffer 的连续 Drawcalls。<br><code>Render Pass</code> 的性能需要关注两个操作:</p><ul><li><code>Load Action</code>:注意 DontCare/Clear 是没有带宽开销的,只有 Load 需要注意</li><li><code>Store Action</code>:将 FrameBuffer 写回到 主存,开销大头</li></ul><h2 id="3-Pipeline-State">3. Pipeline State</h2><p>通俗说 <code>Pipeline State</code> 作用是控制渲染状态,现代API通常将所有状态打包为一个大的 <code>PSO</code> (Pipeline State Object)。<br>如下是 <code>DX11</code> 的示例,需要手动设置 Shaders、Blend、DS、Raster 等所有状态。<br><img src="../../images/2022-graphics-API_25420.png" alt=""></p><h2 id="Shader-Compilation">Shader Compilation</h2><ul><li><ol><li>将 shader source 编译成 跨平台的 bytecode/glsl</li></ol></li><li><ol start="2"><li>将 bytecode/glsl 编译成 machine code(ISA)</li></ol></li><li><ol start="3"><li>Patch the Shader (Todo):例如为 Binning Pass 产生仅包含 pos 的 vertex buffer</li></ol></li></ul><p><img src="../../images/2022-graphics-API_46759.png" alt=""></p><hr><h3 id="重要点">重要点</h3><ul><li>Pipeline State 创建非常慢</li><li>Shader 直到 Pipeline State 创建完,才能明确所有属性</li><li>尽可能早地创建 <code>PSO</code></li></ul><h2 id="4-Shader-Resource-Binding">4. Shader Resource Binding</h2><p>这阶段是为了给 shader 设置参数,以 <code>DX11</code> 为例:</p><ul><li>每帧重复设置</li><li>逐个参数设置</li></ul><p><img src="../../images/2022-graphics-API_17849.png" alt=""></p><p>反思一下,<code>DX11</code> 的设置方式太落后了,作为现代API,可以分配一块GPU内存专门用于 shader 参数传递(核心思想是 cache):</p><p><img src="../../images/2022-graphics-API_43538.png" alt=""></p><h2 id="5-Draw-Calls">5. Draw Calls</h2><p>核心是如下三个参数:<br><img src="../../images/2022-graphics-API_40089.png" alt=""></p><ul><li>Indexed:需要绘制的 vertex、index 的 下标</li><li>Instanced:一个 drawcall 绘制多个物体,CPU端 需要 buffer 存储不同的信息(如 vertex,pos)</li><li>Indirect:<code>GPU-driven rendering</code>,同样是一个 drawcall 绘制多个物体,区别于前者地方在于,是 GPU端 填充 buffer 信息</li></ul><h2 id="6-Swapchain">6. Swapchain</h2><p><img src="../../images/2022-graphics-API_36591.png" alt=""></p><p>Swapchain 会持有如下资源,呈现最终的画面(Presentation)需要如下步骤:</p><ol><li>从 Swanchain 取一张 image</li><li>rendering 整个渲染流程</li><li>设置到 composition(因为有多个窗口)</li></ol><p><img src="../../images/2022-graphics-API_06244.png" alt=""></p><div class="admonition warning"><p class="admonition-title">TODO</p><ul><li>SwapChain、Presentation 这几个部分没听懂</li></ul></div><h2 id="优化">优化</h2><p>应该同时对 GPU、CPU 进行 Profile,如果不是性能瓶颈,请不要 <strong>过度优化</strong>。</p><p>基本的优化手段:</p><ul><li>降低 DrawCalls:Instanced、Indirect</li><li>规划绘制顺序:eg. 通过 state、distance 等排序</li><li>降低 带宽:合图、mipmap …</li></ul>]]></content>
<summary type="html"><div class="admonition note"><p class="admonition-title">推荐阅读:
</p><ul>
<li><a href="https://v.netease.com/evideo/video_course/show?course_i</summary>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/"/>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/Graphics/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>【Graphics-2022】渲染管线</title>
<link href="http://luhao.wiki/posts/2GFAABV/"/>
<id>http://luhao.wiki/posts/2GFAABV/</id>
<published>2023-12-04T17:31:13.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<p>IMR、TBR、Pipeline…<span id="more"></span></p><div class="admonition note"><p class="admonition-title">推荐阅读:</p><ul><li><a href="https://v.netease.com/evideo/video_course/show?course_id=18829">2022图形引擎-内部资料</a></li><li><a href="https://www.cnblogs.com/timlly/p/15546797.html#1246-hidden-surface-removal">剖析虚幻渲染体系(12)- 移动端专题Part 2(GPU架构和机制) - 0向往0 - 博客园</a></li><li>Todo: 与 <a href="https://luhao.wiki/posts/gpu/">【硬件】GPU架构 | Luhao's Blog</a> 这篇文章合并</li></ul></div><div class="markmap-container" style="height:250px"> <svg data="{"t":"root","d":0,"v":"","c":[{"t":"heading","d":1,"p":{"lines":[0,1]},"v":"硬件架构","c":[{"t":"heading","d":2,"p":{"lines":[1,2]},"v":"<code>Discrete</code>"},{"t":"heading","d":2,"p":{"lines":[2,3]},"v":"<code>Coupled</code>"}]},{"t":"heading","d":1,"p":{"lines":[3,4]},"v":"IMR","c":[{"t":"heading","d":2,"p":{"lines":[4,5]},"v":"Warp"}]},{"t":"heading","d":1,"p":{"lines":[5,6]},"v":"TBR","c":[{"t":"heading","d":2,"p":{"lines":[6,7]},"v":"Mali: Index-Driven Vertex Shading"},{"t":"heading","d":2,"p":{"lines":[7,8]},"v":"Adreno: Binning Pass"},{"t":"heading","d":2,"p":{"lines":[8,9]},"v":"PowerVR: Hidden Sureface Removal"}]},{"t":"heading","d":1,"p":{"lines":[9,10]},"v":"Pipeline","c":[{"t":"heading","d":2,"p":{"lines":[10,11]},"v":"Render Pass"}]},{"t":"heading","d":1,"p":{"lines":[11,12]},"v":"Frame Graph"},{"t":"heading","d":1,"p":{"lines":[12,13]},"v":"Compute Pipeline"}],"p":{}}"></svg></div><h2 id="硬件架构">硬件架构</h2><p>如果仅从 CPU、GPU、Memory 三者的角度考虑硬件架构,那么可以分为如下两类:</p><ul><li>(左)<strong>分离式架构</strong>,CPU和GPU有各自独立的内存和 Cache,通过 <code>PCI-e</code> 总线通讯。其特点是:高带宽、高延迟,性能瓶颈是数据传输。主要应用于 PC 和 手机。</li><li>(右)<strong>耦合式架构</strong>,CPU和GPU共享内存和 Cache。主要应用于 PS4 等游戏主机。</li></ul><p><img src="../../images/2022-rendering-pipeline_35740.png" alt=""></p><h2 id="IMR">IMR</h2><p><code>Immediate Mode Rendering</code>,通常指 PC 端的GPU渲染架构,其特点是:所有渲染管线中的读写操作,都直接由 GPU(紫色) 和 显存(深灰色) 之间完成,如图所示:<br><img src="../../images/2022-rendering-pipeline_23750.png" alt=""></p><p>其中 ↑ 表示读取显存,如 Vertex 阶段需要读取 几何(顶点)信息,而 ↓ 表示写回显存,如 Visibility 测试阶段需要写 Depth-Buffer。这些会带来巨大的带宽开销,IMR结构通过引入 L1、L2 Cache 之类的结构来尝试优化这部分带宽。</p><div class="admonition warning"><p class="admonition-title">移动端</p><ul><li>由于 高带宽 导致的 高功耗,IMR架构对 移动端 的 <strong>性能</strong> 是致命打击!</li><li>因此 移动端 大都选择 性能友好 的TBR架构</li></ul></div><hr><p>如何理解 GPU 的 <strong>高度并行化</strong>?</p><ul><li>参考阅读: <a href="/posts/gpu/#GPU-%E6%9E%B6%E6%9E%84%E5%8F%91%E5%B1%95%E5%8E%86%E5%8F%B2">##GPU 架构发展历史</a></li><li>假设 GPU 每个 core 一次只处理一个 vertex,(NVIDIA架构)每个 SM 中包含 32 个cores,那么就可以同时处理 32 个 vertex,这些统一称为一个 <code>Warp</code></li><li>因此 <strong>Warp</strong> 的数量直接决定 GPU 的性能<br><img src="../../images/2022-rendering-pipeline_25797.png" alt=""></li></ul><hr><ul><li><strong>问题</strong>:<code>Warp</code> 之间是可以并行执行的吗?为什么图示是并发执行呢?<br><img src="../../images/2022-rendering-pipeline_27939.png" alt=""></li></ul><hr><ul><li>光栅化阶段,最小单位是一个 <code>Quard</code>(即包含4个像素)。以下图为例,其中绿色表示通过光栅化,黄色表示因为 <code>Quard</code> 而被保留的像素。</li><li>之所以使用 <code>Quard</code> 的形式,是因为便于计算 <code>ddx, ddy</code>。这个在计算 mipmap 的时候有奇效。<br><img src="../../images/2022-rendering-pipeline_35771.png" alt=""></li></ul><h2 id="TBR">TBR</h2><p><code>Tiled Based Rendering</code>,一般用于移动端GPU(例如 Mali),通过引入 <code>Tiled Memory</code> 降低带宽的读写功耗。</p><ul><li>优化点:先写 <code>Tiled Meomry</code>,再实现 <code>Blend</code> …,最后再写入 <code>DDR</code></li><li>参考阅读:<a href="https://litmin.github.io/2019/08/31/%E8%AF%91-The-Mali-GPU-An-Abstract-Machine-Part-2-Tile-based-Rendering/">[译]The Mali GPU: An Abstract Machine, Part 2 - Tile-based Rendering | Litmin的笔记</a></li><li>参考阅读:<a href="https://www.cnblogs.com/timlly/p/15546797.html">剖析虚幻渲染体系(12)- 移动端专题Part 2(GPU架构和机制) - 0向往0 - 博客园</a></li></ul><p><img src="../../images/2022-rendering-pipeline_53347.png" alt=""></p><hr><p>很多及计算机技术,都是通过引入一个中间件,<code>TBR</code>架构就是典型的例子。</p><ul><li>面对一个巨大的汉堡,一口吞不下去(IMR带宽高)</li><li>尝试将汉堡咬成多个小口,慢慢吃下去(TBR低带宽)</li></ul><hr><h3 id="Index-Driven-Vertex-Shading-Mali">Index-Driven Vertex Shading (Mali)</h3><p><a href="https://www.cnblogs.com/timlly/p/15546797.html#12411-index-driven-vertex-shading"><strong>IDVS</strong></a> 是 Mali GPU 的优化技术,考虑到传统的 vertex shading,即使经过 50% backface culling(也可能是 frontface culling),顶点的写入数量也剩 50%:</p><p><img src="../../images/2022-rendering-pipeline_08888.png" alt=""></p><p><strong>优化关键点在于</strong>,既然有 50% 顶点是注定要被 Culling 掉的,那么为什么要写入内存呢?<br>因此 Mali GPU 将 Vertex Shading 细分为两个阶段,分别是:</p><ul><li><code>Position Shading</code>:位置着色,发生在 Culling 之前,只转换顶点位置,因此输入只有 pos</li><li><code>Varying Shading</code>:可变着色,发生在 Culling 之后,只处理通过 Culling 的顶点的其他信息、操作</li></ul><p>假设 顶点 Vertex 的分布是,按照 pos、npos 的比例为 <code>1:1</code>:(通常情况更为复杂,eg. position、uv、normal …)</p><ul><li>缺陷是:需要CPU将这些信息分开存储<br><img src="../../images/2022-rendering-pipeline_34173.png" alt=""></li></ul><p>那么 IDVS 技术起码节省 <strong>50% 比例的 npos</strong>:<br><img src="../../images/2022-rendering-pipeline_16452.png" alt=""></p><p>进一步分析,通常 <code>Varing shading</code> 开销一定是大于 <code>Position Shading</code>,所以 拆分两阶段,能够让 <code>Varing shading</code> 充分享受到 Culling 的收益。</p><hr><h3 id="Binning-Pass-Adreno">Binning Pass (Adreno)</h3><p>参照前文,既然某些 Vertex 压根不会参与最终的渲染,那么有没有可能,这些都不用写到内存呢?(节省掉 0.5 Vertex Write)。<br>Adreno 引入一个 Binning Pass 的技术,在 Vertex Shading 阶段(同样先是只处理 pos),将所有的 visibilty 写到内存中,避免了顶点信息的写入。</p><p><img src="../../images/2022-rendering-pipeline_37944.png" alt=""></p><hr><p>Adreno 基于 Binning Pass 的渲染架构如下图,继续悟一悟:</p><p><img src="../../images/2022-rendering-pipeline_13639.png" alt=""></p><hr><h3 id="Hidden-Sureface-Removal-PowerVR">Hidden Sureface Removal (PowerVR)</h3><p><a href="https://www.cnblogs.com/timlly/p/15546797.html#1246-hidden-surface-removal"><strong>HSR</strong></a> 可以直接理解为 隐藏表面的剔除,传统的 OverDraw 是通过 Early-Z 避免,而 HSR 可以无视绘制顺序避免 OverDraw。</p><ul><li>如下由远及近绘制时,不会对遮挡的像素进行任何剔除,但是 PowerVR 做到了!</li></ul><p><img src="../../images/2022-rendering-pipeline_45218.png" alt=""></p><hr><p>HSR 实际做法是,在光栅化之后,<strong>写入一个 Depth-Buffer(降分辨率) 到 Tiled Memory</strong>,在后续的深度测试中也会依据它进行一些 Culling。</p><p><img src="../../images/2022-rendering-pipeline_46152.png" alt=""></p><h2 id="RenderPass">RenderPass</h2><p>对于 RenderPass 的定义:对于渲染管线的一次完整执行(连续地往 FrameBuffer 绘制对象的一组行为),如下:</p><p><img src="../../images/2022-rendering-pipeline_10671.png" alt=""></p><hr><p>RenderPass 的组织形式对于性能影响非常大,如下绘制一个Scene(电视机中是另一个Scene的画面):<br><img src="../../images/2022-rendering-pipeline_55447.png" alt=""></p><p>下图展示了两种绘制方式:</p><ul><li>左侧:3 Pass,先绘制音响,后从 FrameBuffer 切到另一个 RenderTarget,来绘制另一个Scene,接着再切回 FrameBuffer 绘制整个Scene</li><li>右侧:2 Pass,先绘制另一个Scene到某个RenderTarget上,然后切回 FrameBuffer,绘制音响和电视,并将之前的 RenderTarget 当做 Texture Load 过来即可</li></ul><p><img src="../../images/2022-rendering-pipeline_41030.png" alt=""></p><p>分析以上两者的绘制开销:</p><ul><li>左侧:3 x store,2 x load</li><li>右侧:2 x store,1 x load</li></ul><p>由于 <code>Load/Store</code> 操作依赖带宽的开销非常高,因此 2 Pass 的方案显然性能更加友好。</p><h2 id="Frame-Graph">Frame Graph</h2><p>为了优化 Render Pass 的绘制顺序,更好的配置 <code>Load/Store</code> 的关系,可以引入 <code>Frame Graph</code> 一种有向无环图:</p><ul><li>有利于渲染并行、排序</li><li>有利于性能优化,降低 Load/Store 开销</li></ul><p><img src="../../images/2022-rendering-pipeline_06607.png" alt=""></p><h2 id="Compute-Pipeline">Compute Pipeline</h2><p>Compute Pipeline 的意义:</p><ul><li>在多线程组(thread group)之间共享内存。eg. 在一个 <code>Quad</code> 内计算 <code>sum, agg ...</code></li><li>读写 Buffers(例如 FrameBuffer、顶点数组 …)</li></ul><p>如下是一个 DX12 的渲染管线示例:<br><img src="../../images/2022-rendering-pipeline_37432.png" alt=""><br><br><br><br><br><br><br><br></p>]]></content>
<summary type="html"><p>IMR、TBR、Pipeline…</p></summary>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/"/>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/Graphics/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
</entry>
<entry>
<title>日志:2023年12月</title>
<link href="http://luhao.wiki/posts/2023-12/"/>
<id>http://luhao.wiki/posts/2023-12/</id>
<published>2023-12-04T17:24:09.000Z</published>
<updated>2024-03-01T05:33:17.408Z</updated>
<content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <script id="hbeData" type="hbeData" data-hmacdigest="bdb51053d99e2f462ae67d1ce2d789fc6419bfc2931878fdd9d55d607e487035">b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475def1af0b6bc61f7912497bd2791ef1396a9bcf0f6940df039a86f945fe75789ec31640a75d317a8abe2cb024b5dba86bfd34e4bd9e09040b251abc3b0833849a256141b075696212f5ac257b95a02cb70c1fb6c1dfa0046c0c4e50ab7d1f131976105380b96af668fb462122068b4e163024b111dfb4e1f0aa6d1310bb266a940e5f9b2c00815b2e6c6d5c94f661147bf2459a3e427b075205c997af090859dcc0f6a2bc00983015e3974124a37d5bb1246404d8c761a8886603c25b1334d4f7ea42576e8e59e547705dba29592e2cc07c9622468e190523cc899663124a6fee22d9435faa3710d6d951e354c26503744daa3dc9b0a026e22639fbc17e6a82f8312dc213906656a56642d95a9fdfc114c85c8540fab525816f8958fd5b6f53fc3ef8119c6b4d6d470437fd6854f6edf1e1fd2c0667d781a5ca4b2eaa5b59d08cf6f503e6c1c1716df8cddbaa7077355b9c35f46447967fa3afcb8c5e35b4e8be3e75565d1a79a4648dfb95e597a6438ca9be1f00c1d2cb89bf9e31e6636d0b2cbd3b93991a6287036cdd3191edd7d8c0a4a0ad5645174358886f2809699484f27ef51d5750abddb669777bc9c2551ae9e1f8f702d5c768c891ef0f863fee9f04a8bfa74057c9c4726dd0dc296eeac2ee9abb448bde504c647aa4a6aa58adb988e016da96fcb237dafb64428b08d8e555e3ffcaa58d922ade78131e10c0268c50acf1b088cf0fbbb1244cf55393cb140bc82719b916667da06ea72f4acc93d1158429630cfd3f55eeab3bcdfdc1f04ad2a3582fd8caf885efc77037ffc2881207a72f5ce14a84a89420d71f2faac7a4d5c5951eea7857660e838551e953814f74ba86c1af6a32f10faf1f664cb9e71bc8cc70ab2f1f27b225ab4b49c3239e553b52eab6cb83c52af2e426e3bef0cb3e40f889303d05b7395082ef77df9914fa9488908bf9c53de603ff1bf4fade763ffac122df68870941919379609bdbc9496fef29ccf6be4a4ce63bd0f5f3776155c10bbbcbcb2912fabe4fb11a0114033af102435c18d2c90e2ae7c1900332e2e1febea5073592e62393b6838dda6f1259a55e4b96d38f3177c567fd15fe49dc6101deabc8902f620ed3934afc4101ba3938cd2fac1d50ad93bb14e59c7291fea9d4093e0b53eee165a0e95805046078351f84adc55f717d3c45cb5f411a8530ad2aa3fac0e7e585810114c4026c7f80cc0ea864ff027fe2680e0c395613d41e37723981d361ff6ee94b5b557bc27694b8bdd685d4ac7da7c10d9313e4fe0b527225ac4dfaa297c60eca2d9cc219803e87a778e67d34ef1babff9b90f64b1a2f83a0dae204e054dc09fe8c315e0d99c83f381c852e232bc133afd86f4405b475ffd98161bd72d86142779ae8ce14b7c62cb4b3c7b630b641752173e5264ac8881eaf6454aaf711a10151842f176a7c78bafe7459aeb3bb3a4c4eebca83c2f4f89f72c174edc64c6d203422546275db1892f94c8127f800f4c3703aaab1870f9b15ddae3d991242919c22650f7a5db341194fcede17fdcb103e474bfd85adaca0c500848d478807cd8d2dd2c03a52d955bb1178aa5cfc0ce09f850e8490e5e3557866d33e2fa0d9adcf8cca0d34330cbb825c8f80b82b0c3367e0aa2b0a70ff43d0a36070819d0ebf25ee0923df066f97616de4914f9c553fa7deb7a5749a9fecb8d38c3875f1a02ce323637d8c52d0efa2648a4afdc329c597d0606b5ecb668b451f7af43d75ea1301cc41347ac427683e6e741daffab57a269c6f955e551a02910b6f01f12b0198bdace5564b14bd93666a3a990410bd6b915552dc60f214bbc76fa227ec168fd67b291c884e2f91c4ccc7f57d2985ce42b45acfda732ac8b545760dd69743c833ea224484d87eee20d032b8125917289cc3aa06941fd4e8a0d1c51ae69d26c2ab94b9f832eb282ba49f14d2407d5d9e9ec73683bd45af6447e521d5f37ab0e0311c972a9f55bb4f0f9f136691edaadbde4364628cdeb85484b79c64d7b78f2739e20bfd6fca0d85ef8c6a53fdc1e9f431e40a932d7d5bd69aec16615845273ea5e32b841e334ef57d425bb34c8c419b04c8c8f72e84a3a27d7bd43d2d5af942daef994d655b7e65b7332fa77abc971ee34db4b0bf180c26c64ac576808300712580c105966b456805c45262ae40f12566c448bf154af8401710033cb9b63c26f1a017cdeaf8658bd9def6e1ebc7ca65b1b7a2cbb60faafd4dfc195983ab2eeab90cd9c3fd8fa2f3a506fda37682bb846543df3dea39200e8ae30bda3c2834571dcd7b2fbd1e7d3791f0aad045c052235a5df58ee4c151412e5b97ec760f77f907dbf203842f6b421e5a737f396ecfc85624bf58cc70aab2aee0acc18d35c46a8577d4b3efd239b2a868b413262d75b9d6aeffaf18c3fb02d1f97aa433e8f422e4ea77f3b3faba6b09e747f009520f39a44336b7c61e2b214b20931bfac09dd7034a281bd0a6f3a7694256cc4bef53343dd59785c3afff662a8f6bfd776f8d8ec265559d2cdfc1a1574ddd229d7a67001b87013d42db8b1ba73e4d641bfe8f3b37068716e172559cc5e4b98532a5bd2315e81c66a07b68a9a4ff0a2728c45d423dd33b3a03e2fddaa30edbfa838f0670e2ee9d181e3d086fd3afd4849523f71d8e4d188ed0f4bd2296dee768ed789c7728b0b5a0003c6e57363e9408c8c1d0ce64bf818cfaab5d9e42b855fc6083f018b0b7fb341b4c2f4f8c71dc2b9df04554703395b1a82e0dbf96cb415b9e617e2e23a12153f23b9797377c892c3b0104565ed2ebca1835fb7ffe5d9f69d3093b54b76255468f552b4fa247287efca8394604ae4c3e0e74e1d6932380d7d0dd17a82f3dae2c3bedba8cc3e88c50b8d32abcacdc2e64189b75715ee6ad50f856f8ca9d255137bfc61a396a3363702348656ed725020b758c5fbe07ea0898521e7d1fcd0073837cc960fd27122614a804638f0b0a384beaf270277e4a2416de67f6ca68aaa9996725b88e4b61e540c2a39f4cdfdf917002567c8133b08c1572c46245dcd86316192d298d4892f81d3d9fb11e69aa42eb68c5a1a37e0457c093885917f8f69fc438a754cce35ba84811c1721e0bce633ba9eb4007e7e0651dd8f925c5fdbbe13a21f44bcbb9c40089288a2d12c48480a1c8e21bfe3cf3699e1f1d2ebb39a90e22927f6c1dc87eab34fd85fde5c5cc2bcee76b2d70b870431c187c1ecb0cd4917ab291ae2bf0b890c676e52637065ef2cb2721f68223c8f350</script> <div class="hbe hbe-content"> <div class="hbe hbe-input hbe-input-default"> <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass"> <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass"> <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span> </label> </div> </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html"><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOP</summary>
<category term="个人日志" scheme="http://luhao.wiki/categories/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/"/>
<category term="个人日志" scheme="http://luhao.wiki/categories/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/"/>
</entry>
<entry>
<title>【工具】Obsidian笔记</title>
<link href="http://luhao.wiki/posts/obsidian/"/>
<id>http://luhao.wiki/posts/obsidian/</id>
<published>2023-12-02T09:36:00.000Z</published>
<updated>2024-03-01T05:33:17.408Z</updated>
<content type="html"><![CDATA[<p>构建 <code>hexo + obsidian</code> 工具链<span id="more"></span></p><div class="admonition note"><p class="admonition-title">诉求</p><ul><li>提高笔记效率:<a href="https://www.zhihu.com/question/384309878/answer/2713962647">什么是 Zettelkasten 卡片盒笔记法? - 知乎</a></li><li>提高 hexo 开发效率:搭建 hexo + obsidian 环境</li><li>todo:还未掌握 obsidian 的核心功能</li></ul></div><h2 id="功能">功能</h2><p>汇总一些 obsidian 值得使用的功能和特性</p><table><thead><tr><th style="text-align:center">Features</th><th style="text-align:center">Hexo</th><th style="text-align:center">Obsidian</th><th style="text-align:left">备注</th></tr></thead><tbody><tr><td style="text-align:center">Adomination</td><td style="text-align:center">√</td><td style="text-align:center">√</td><td style="text-align:left">不兼容</td></tr><tr><td style="text-align:center">Todo Task</td><td style="text-align:center">×</td><td style="text-align:center">√</td><td style="text-align:left">有空实现下</td></tr></tbody></table><h3 id="示例">示例</h3><p><strong>Todo Task</strong></p><ul><li>[ ] 测试 todo 功能 <code>- [] xxx</code></li><li>[x] 测试 todo 功能 <code>- [] xxx</code> ✅ 2023-12-02</li></ul><h2 id="插件推荐">插件推荐</h2><p>核心介绍如下插件,对 hexo 有所帮助:</p><h3 id="1-Templater"><a href="https://silentvoid13.github.io/Templater/introduction.html">1. Templater</a></h3><p>通过自定义 <code>post.md</code> 模板,可以快速创建 hexo 文章。目前需要定义如下信息:</p><pre><code class="language-md">---title: xxxdate: <% tp.file.creation_date() %>abbrlink:toc: truethumbnail: /images/default.png---这是摘要<!-- more --></code></pre><p>当通过 <code>Ctrl + N</code> 使用该模板新建文章时,就会生成默认的 <code>*.md</code></p><h2 id="插件开发">插件开发</h2><blockquote><p>官方的插件开发文档,但是很残缺:<br><a href="https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin">Build a plugin - Developer Documentation</a></p></blockquote><p><strong>具体步骤</strong></p><ul><li>先clone官方示例的插件, 在此基础上做开发</li></ul><pre><code class="language-shell">cd .obsidian/pluginsgit clone [email protected]:obsidianmd/obsidian-sample-plugin.git* 进入目录```shellcd obsidian-sample-pluginnpm installnpm run dev</code></pre><ul><li><p>此时重新打开 obsidian 工作区,新的插件会出现在设置中</p></li><li><p>如何发布? 通过 <code>npm run build</code> 生成必须文件,然后拷贝到插件目录即可:</p><ul><li><code>main.js</code></li><li><code>manifest.json</code></li><li><code>style.css</code></li></ul></li><li><p>如何调试?</p><ul><li><code>new Notice()</code>:弹窗信息</li><li><code>console.log()</code>:输入 <code>ctrl + shift + I</code> 打开debug窗口</li></ul></li><li><p>Reload?</p><ul><li><code>reload app without saving</code>:通过 <code>ctrl + p</code> 呼出</li></ul></li></ul><h2 id="自定义插件-Luhao’s-Attachment">自定义插件: Luhao’s Attachment</h2><p><strong>背景</strong></p><p>由于采用 <code>hexo + github.io</code> 的技术架构,文件结构为:</p><pre><code>* /posts/obsidian/index.html:名为 obsidian.md 的文章* /images/*.png:存图片</code></pre><p>因此在任意 <code>*.md</code> 中插入图片,需要采用如下格式:<br><code></code></p><p>但尝试一圈 obsidian 插件后,发现不支持带 <code>../</code> 特殊字符的替换,因此决定自己动手改造。<br>为了避免重复造轮子,挑选了一个插件魔改:</p><ul><li><a href="https://github.com/593413198/obsidian-attachment-management">593413198/obsidian-attachment-management</a> (fork from <a href="https://github.com/trganda/obsidian-attachment-management">this</a>)</li></ul><p>主要修改如下:</p><ol><li>修改默认 setting,如将 attachPath(插入路j径) 写死为 <code>themes/pure/source/images</code>,将 attachFormat(插入名称) 写死为 <code>../../images/${notename}_${date}</code></li></ol><p>已经打包发布 <code>release</code> 版本,链接为:</p><ul><li><a href="https://github.com/593413198/obsidian-attachment-management/releases/tag/0.1">Release 0.1 · 593413198/obsidian-attachment-management · GitHub</a></li></ul><p>使用效果为(图为 <code>obsidian</code> 中截图):</p><p><img src="../../images/Obsidian_46986.png" alt=""></p><br><br><br><br><br>]]></content>
<summary type="html"><p>构建 <code>hexo + obsidian</code> 工具链</p></summary>
<category term="工具" scheme="http://luhao.wiki/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="工具" scheme="http://luhao.wiki/categories/%E5%B7%A5%E5%85%B7/%E5%B7%A5%E5%85%B7/"/>
</entry>
<entry>
<title>【RealtimeRendering】3. GPU</title>
<link href="http://luhao.wiki/posts/rtr-3/"/>
<id>http://luhao.wiki/posts/rtr-3/</id>
<published>2023-12-01T15:50:56.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<blockquote><p>本章主要介绍 GPU 的硬件架构和管线,重点理解 <code>SIMT</code> 和 <code>Warp</code> 的概念,并结合实践理解 <code>VS & PS</code></p></blockquote><p><strong>GPU硬件</strong></p><p>关于 GPU 的硬件架构,参考 <a href="/posts/gpu/">这篇文章</a>。</p><ul><li>Warp:GPU并行处理任务的硬件形式,一个warp通常包含 32 个线程。</li><li>SIMT:<code>Single-Instrucion Multiple-Threads</code>,多个线程执行相同的指令,高度并行化。</li></ul><p><strong>GPU管线</strong></p><p>下图是GPU的整条渲染管线,其中绿色表示可编程部分,蓝色表示固定部分,黄色是可配置、但无法编程。<br><img src="../../images/rtr-gpu-pipeline.png" alt=""></p><p>可以结合 renderdoc 的 pipeline 试图理解这一整个管线:<br><img src="../../images/rtr-renderdoc.png" alt=""></p><p>对于可编程的 shader 管线,虽然最终GPU硬件执行的是机器代码,但是多家厂商退出了适合编写的高级语言,例如:</p><ul><li>HLSL:DirectX High-level Shading Language</li><li>GLSL:OpenGL Shading Language</li></ul><p>通常 shader 会允许被离线编译和存储,这些被叫做中间语言:</p><ul><li>IL:Intermediate Language</li></ul><p><strong>图形API</strong></p><p>每次图形API调用时,shader阶段都会包含两种类型的输入:</p><ol><li>uniform input:指在一次 draw-call 中不会改变的常量(如投影矩阵、texture)</li><li>varying input:指在一次 draw-call 中会变化的变量(如顶点位置)</li></ol><p>图形API的发展也十分迅速,如下图所示:</p><ul><li>OpenGL 和 Vulkan 显著特点都是跨平台</li><li>Metal 是苹果与 2014 年为自家产品研发的低开销的图形API</li><li>OpenGL ES 是指转为移动设备研发的<br><img src="../../images/rtr-graphics-api.png" alt=""></li></ul><h3 id="Vertex-Shading">Vertex Shading</h3><p>VS 通常是可编程管线中的第一个阶段,前面有说过,它的输入就是一堆 mesh 的二维坐标(附加 color、normal 等信息),一般有如下用途:</p><ul><li>顶点动画:草、角色</li><li>地形高度:通过一张 height-map 模拟地形的高低起伏</li><li>程序化变形:模拟布料、水面的运动</li><li>粒子特效:创建一个粒子发射器</li><li>…</li></ul><h3 id="Pixel-Shading">Pixel Shading</h3><p>这阶段是基于像素颜色作一系列计算,其性能也与分辨率和Shader复杂度直接相关。</p><p><strong>MRT</strong><br>早期的GPU,仅支持将PS的结果输出到一张RT上面,这样的绘制效率是很低下的。<br>MRT(Multiple Render Target)技术的推出,<strong>支持在一个pass绘制对个对象(GBuffer)</strong>,这催生了 Deferred Shading 的诞生,这里不展开描述。</p><p><strong>ddx ddy</strong><br>硬件提供了像素求偏导数的接口,由于GPU并行处理像素点时,会将 2x2 个像素放到同一个Quard中,因此<a href="https://www.zhihu.com/question/329521044">计算偏导数</a>十分方便。<br>换言之,这三个像素的 <code>ddx ddy</code> 取值也是相同的。</p><ul><li>ddx(v) = 该像素点右边的v值 - 该像素点的v值</li><li>ddy(v) = 该像素点下面的v值 - 该像素点的v值</li></ul><p>推荐阅读 <a href="https://blog.csdn.net/u013746357/article/details/107975128">mipmap_level 的计算原理</a></p><h3 id="Compute-Shading">Compute Shading</h3><div class="admonition error"><p class="admonition-title">Compute Shaing 部分缺乏工层经验,后面来补空缺吧</p></div><p>DX11 引入了 CS技术,它充分利用GPU并行计算的特点,使其扩展到 深度学习、神经网络、量化计算 等复杂的计算领域,而不仅仅局限于图形学。</p><p>它的优势之一在于,可以访问任意GPU上的数据,具体有如下应用:</p><ul><li>粒子系统</li><li>Culling</li><li>景深等</li></ul>]]></content>
<summary type="html"><blockquote>
<p>本章主要介绍 GPU 的硬件架构和管线,重点理解 <code>SIMT</code> 和 <code>Warp</code> 的概念,并结合实践理解 <code>VS &amp; PS</code></p>
</blockquote>
<p><st</summary>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/"/>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/RealtimeRendering/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>游戏引擎岗要求汇总</title>
<link href="http://luhao.wiki/posts/3K6XB0W/"/>
<id>http://luhao.wiki/posts/3K6XB0W/</id>
<published>2023-11-30T12:43:01.000Z</published>
<updated>2024-03-01T05:33:17.400Z</updated>
<content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <script id="hbeData" type="hbeData" data-hmacdigest="a57dda4b7b82ae17071efb24e494716da9c4a50fd3572560fb9a883a3cf48a5b">b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475debb79baf718116b8398ebb7a36965f5712db04a990a04196bf7d89b9188d2f59696c274d67c5d338176316d0990e58740237b16c70080fbc3d186f48f65ead60c9f3efb14c16981a609c6df80b790f379e6b80f20ef476aa62233182f1be3173b510d09a093b5d25c7164e684586e9473ef084c4f40e2dbda8db3191087876f0c531d728a5d1f4d23091f9671910335bdd2dc460cf799674f06ca53e241a6a722de1fd81291bb9a501fcb5a6c362b80ee2ff346f53bed741db17b867184dc18722db17c8cdb37a99a8973386368fab795f91e4a480544b15380df3928c3ac7402e2ad0413032d30ed13b2b4080cbe28adc7a9539cbdf3ec9351fbd236fd587fbde15dbcb4010ca92202814315437c081c4c06a533032d0160baea7e0ee8fe85bf7e6c31851b65834dfe4b05ac88de74a61af7c67c4d3e749e09db00f6397cbccd52e6b2bcb04e043643b6996a587a228936b537377f69214573b844fc9c0ce0ac37729a985faab847d80733cb1538a56907f96b215a1db2de50ef00c7d093b717e3c3e3fca1befc064c0dd0777403eda61a876757d07d9a4fcd4219ab115fdadf41483cf5cc776162b659b42edbe066104f8f83c7a8a3bba84aa2c9a0b5be0e75e6b8c6bcbd4fca788c536bfc0783504d5a5cbc5110b0d2799cb6366cab37538acb97cc29005709c1083cc134519c08a9027d97d8c3fad8556e516435723465e677b68d6f1c1845160bc92f94fe8918a209f5c741e4525f16e877714d16e42f143a1668f46787ba09645c4309424cae18bdc04952a058e8c426677c54143f95c548671fe6d3bb1e4a7b5288a52ee4ef6b09ce8a2bf41922cdb08af1c78edb0dfc3e0d07e7a0dcb282156645c3a498946ad854b6c4d1ff25abf9edb9a0d77de321437b76b92eda816c0315484daeb4385812b7d7f44b1b19a98d5cab10e89399e50cbe83685931f879617afca50cb67725623bff904ca86f9eb022ba29d39458c30953be5ad06278c28d1b30cae0493be7004b649f742c79eaf2e39f8fdcec066f5ecf3bb4d1847d080cec3cf2a7f02f65e9be3653542e80b392788c3bf2e1c71c3048ba8b02604d6f84ae52c22fc414ed73a5f117a50e053d547ac6fab8dd12a35cb3853d679a5184c6d207486f6f1e922e10f7dc47538bfed34e52ed540f65a9b1bf7fcfa6544b7f546c30238152b62b8c3865c50661f365c792c624690b8f304945edd7f9aaa917c8d2ca46bc1ece0fcaa191fff959b902d92fde78ce97a8f560ddc49524b070259f067d2ac966bc1aa345dbb831e5243db9eca324f4947bf4187df5f60201dc5efa81ba85474939e5ea86851ce248ed2253a3cbc69028789576b5fb51ed4c2bad29cc637ce36c341aac3bd2a86d2383154e25b803b1004facbc505b52710a67f1bb9cdce38104133136316cfc9ecf937e3ea40ec87cb1501e6bdfdceb15b979e26b89133cc26f39a73e17ed206a81dc353da0fd49e6e4739ac40ee145890a5d96cb2ffd1e7a0faaa6cd03fc852802fb097e261d58396c0a987368480aa8daa89426b90b48a43c7fae6d65dfa976ff0712281cd0bab23333f90fb1fa708da5e28386c39009faad83daa2ba9de1f1d81252f70712142cb6a686175603c032442bcca0fe95041d39e728df69d417a1b22eb7d013bf257c65f1eb5d415924423231c84cc33760c19379f8731c851f677963494dbdc9a4f1b74b672ffd9f42cac9729ca2f43569a61f507ef8f364ce9ba22f71a343c9d86bf69d3d349d28867e0cc2af9222e5a9afa0a0c42d2a3198d318e5b0ca804b2a9b4d9ec6881623f4e7f26a633cf096e3f5676bcb15416d3f904937bdea2bce2b5ecd6d45d6c84e8e66f828e8d532eab2e7e784a853ace44cb35b26cc1add287838b2439320bae546673c3319109cdf7f0da5d1ace15e69aad5218699460fff3dda1b7b032a8088957495fce20cbf8b8080c84e0a3ed3216c7693789a4c08c3b4eeb623869ec051fc9d022abdadcdf44637b849b117cad419b40df12345f9c918dc71a5448e800fa6bc76f6f8858f5819dd1a83874c28b71a80abcfb9801979971f31bb038fccb0b576657e84edc6939095c095aac95f2eb4d828e4d45b83f5e728ca4c78f9455a2856ccd4cc41afa34d5d6f273dc2d995d98112474bdb67c478ba1738f9a429e3320985d19b9480f4dc19ea4b5de91f6c6d680af49dc8692fd15d69e62153f76db26d4681e14f4e30cdf20b427eb643d498af62b5bf66201b7f1b02302f6699aa1639200726fc153f955cfc5ea69186c3a414602764aec0071298ed540fad5f459c26fae893705cb83d2049458b19c6afd389206847ca503cf11e08228c7b858ec02219840725abf66d0d203b61ccb0bc15c16cbef01bfd8eb0dc0bdf63ed8b8b64be1bb1add8c1bb0bb3d1d099f9fb5bcabf05ef3f1b41b4142747bc25889ec03715a701dcd6b4995bbf1fad6aaf9f43891a797eb4d22e1fcedecf000921678fa63fc711479bd2bf193b0abbfdfb654c5d0eada2146e2d78f5f6bfb29b554dedbda140371ed26acfbd39f865655b5ccab336d942a7c9b352202e53a8e01e446b0340389ee7195b4b5fab00c46d1c3895ad8495dabcd32fb4fcc5fa36d879a419e65a2d59e58ab10085f223a80935fb3490a46ded7f7dbb1702be610c16ed8a560113ce4a0eaf7899bf08209106d10105f976ed591694ac165dc2c960e771cbd06cc1b1fd3cb457261c216a8948c8146eb9cf86cadf24f06e7daaaffd9478cf5be817f809a93d4797b4d20fd880d88923b348967d01d665446e151a914817267611a51fc73d4bd690f478dedb7552fe4faa4668d64a1b7b56aaeff10a0b9a4e9e7d17e30fc0eca048448c8189d4fbc92de70203f3d0548220541ca2a63d52632b95e78ccc9fce208721131f935aaa484c06fb99b2f6c8f2063d4ead37514da0faaf07950744a38fd32a1d680f03fb509b073a5362697cfaace58b78e58c9da46e71ec5dbeae73b26b1a996a03b6821db5f1b668308d5de6b123087cc081da7ad46777148e2c0138451757ebbbae3777112395d1735ec273fa6da7c0024b13431d655e98194662a3356cb03a31ab4529b769b018d1747c12137629c5de0b813f7e274e6c51cf640e355f0961eddef8d009b24f1470e00d40d37c0b2379326f8c2f16ba5330b6c7776bf05e8731f6403c6f821ce19ded55084c7a2e31d5625ae8b2d277204db3a540d5d7b56accbd4bd5b56ef72ffbfc6af148d2210796cff7087299f712648145b0ffa1548d90d215a4b05646aa819c8deeb93bb4e8f6dc7610c55f2376c260a8ba2198f4b663e7f9cbc5b6d45b08d9a29b5750982a1dc2c33a26e47257d085efc3cc4ccb1ab31ce0af8aa642aa50129b91ce4fb5736daa613e5e2ca5a680d631bb80805d207612ea581ed325e70138704871b4b70d67fb9909fbad2798f593b7997b5dfc9ede7ab710a4d4c194e6f2dcbd89d1946b24e19f4d4960d7da95ed0fa60071ea9b0f283256eb396486a1b7f10ff2d33c0760aef10ec679e7d0d2ed8ea34325b3e0450bf03d267c9c280720c3ca60a9f81918212f9e73c3f48afabe8110cac633d2ef8b1253e539c87592d560613a4a54b08932113e0ef1390a894e19fb3afa3bcca76813bf1903fcbf8fa0436f4ae1f513881abe025a43ac50cb61084414b03f460b97612a01b6c6ee89b3d454f6e4b886beb16852892d3aa4e42e761962bc004810c91e02ed88f995a5bcb395495b8b87f02cf2ff5d61a05b87a74e3943a4f0eda2d5e8c6a6bc4996d43e03cdbf7745a9de853af84a4618da1047374498e6b3625dc24af4a868b92515eed3c18b5bb8acdd51833829037c0f506a5c091107d5dd8ddb975cc183153ebbee48cbcf1ff487709c5ad2d69c4d90bc8e77caa34a43dda6525d9725e4785367c23385da93bd335589d4777942c0c32d9473c9de5bfde2749e5948c56eee57904819b85ded14953db42eae7dc4ab72dfa8c680b7a1ab0170a5c4a15df24f939f2078a4d7dc184466d33621024bd9c87741119524e24c1e4e738b84faa71b34f9cbe18b066db8aa4dd5f44cc3ec240ed50adf0baf49a2fe80539b272d45a3abe176bfff93521bfdaed776d82ee5a05aa75624ff55de3521136cf3571fafeae09ff1e763dbaac45fb68abdb066ce77f6b95e200c7d2669505fbd1457edc634993fdf86396f4caaa9ef00c9ab7182fbfed9916607c232af7d4db0f934bf393bc507c7836faffd5a3514e99fb5ac50a88e1526c32143c288e1dca6a1b1ca1f2fa6bccded1781568a7b95602856656c3626c2c04e92294f7a9f3abd1021733cd30845b2fd9b5e105780d510dfe682d37a717f7d4943fbab78ca7d20ed7ffb6bb0cd84f98464337b827a34e5b593d94050a88978a024f9753faf12d047c05dcea854e0a55c22d8fd852140a3535243b8d4ad974b40974b4c9e0cbd20e45e8b8a3e2862d0f6176b9959b4f539f50cefdf24351260e4cf975c6eca1893e0aaf1f19c8aa21f5e5f4b5c3ba8681ed99cf635ea99aeecffd2395ca92df079171d609e31ece572d21ff85e1070b3ef355530df65ce869af5aa0466f795a8225db6eedbf19d9680c6d83a4fa800a2ae1ad28aa21c7cc4a0342f11025ed159f64f6aa6d3c21bc02ca86879f71e23c738087f149b5c50786b13eac7473eeff40c12837ac4cfa8ff0005eefc53c740d968a5ce8c16a03f7af3a39b8a823688f54541b6abe3e28290a3751a71312912238e0b75f8e7fe83f3f0eec7fb6f9159c319efa0052112364cd132c15e6a80f30cf7b03ad7fae29f503628012ee002993c030b93202b4301c8d7d305c0bb1335a3dd4ea1f82bef485c784e4d0baec1d5c76bdda24480db4ad9e86e9776e876bb066aee75561e4781b65b2e49bcd392b50dd14714475aba37e24471b30d4e5d385607953440de9ce97d73d260488961f88ad46329a1f72e68950bd222e26289bcd287d047678af090beab87fd2ff1eb7e5c6eaa8d8fc7de2a5f04406acef76415caa0eedd262409d068ba5873e58a6889d34028318c39677751f7839b0480edfe8fa5dfc71e653545d107067e6792be3c7dd84e41a422196ef31c92d54998e564266780e7799945d50089b770648d8dc7d13845ae114628bc7fa1b5c628f62eab23a4aec8c22a38d90867d14f081098f12c17c96983a5a388d1eadfa3b57a868f4bad87d8a53a838687426463154480589417cf8cd392f0d29b7f559506e48d2aa8e4ddcb67bac1db537f9223c14e50b64a62b5148d33e8ec6e6640ce017c14afbb5958b835a2ea9e371001aac248026ff4ea8bcbdc69ad08b09fcc68368783a74cdf7dd0392ee414eec5b4a40aec6bc7d24caf48f90bdf9f6b1483ca6d493b0b4e01062aec3d950eac3eb0194f1b14922ef3a0c4083b1d66b07b400752204a464f2a1f7c364357be8530ed890cb8e5918511607e07b3665e73cd2b31312f575cfa28a2119672bc85df732a5a33b91eb18bf5fb3e30b1ab1a0ddc80b6f4a5a8e7910123769f169b229110a6ff2e8fba10fbf2b1bde72600e110c459a2472544fcee4a1b1b83de29b10059f659fcdd122c7b10ad2f74656022a93e205e837950d2e578da257c4fece34d742a720df0c1f478d5f63e82dce41397405a7691c1b23bad5baf5668c0cbde588f1b08218a037d434f6cffe3ca670ee19d39bff1813e61acacc79d355f8adab6f1463cc6d87a948d5faf197f5ccadf6c60066eb48526e16257bbd5eda2d4763a542c7edd9530596a197a3a0f412116dd7a643dcb78f963d1444dfe288f6be49ec77ea4c6adc227ea63592121476325b02f549273834f53b94a4effdb5f71afa01b1559dc706b8ea641fcf42421860720cfd42d6d9e0b5bc5ae29f681fb11fab60685ee170c5c13f582221cd4a58ca2de1239a4eecede737930174db32a8d54ef1ecd3994d0812a083fccc3f5d602c1aeed84573930cd3d00e1c69ab81a5533ec1307829eb457e6c3b6b9ed35ce0fc232012d5cd6bec3956cbc19cf6f2a3474894a77deb377252be51309e62b38952bfb2c8848636522014e551bda54987cdda04375a625c21887484c14176087a5c3378ef7131bfc913be909ddda65d48796d3ba18429b203fd531ac90ddf1d80c912cc10fef148eea96a3159bd19d17f131a563fa500d8e4ff8aabf637f2a2086c7e902b75ea124bffbfc71cf13d99e57439c7fcff864fe33068eae4f019f6dfb227a54d646b14c5a131d87f000e0599f6d01030742844574014f8ca942e00bfb7ae983fcc691316fedaecd8fbab9e1cf4ebad96272110075266927cb208b6bebb3ccd6e3f24088c6ae023382f49df1a0f45609786bc2b192e0058deb017ae17de93630868e3ae519fb59543b8bac090fdf3c6fa80b18784162e728d231192693845369ed4875721c3f97f53f155e7b8b498a03925b87eaa132086000d6012f9f190d1f15688ab4e13206ad288e1960fd7004666c76965c3495c20c309671bffdb30ebe9ecbcd08455ae74b2f0be97fc677b3ca314d11783b44d7462d92df010993284ab054b1b0f2d408b7ea81cca8f861f0a8511d266d5e5f82d8d406dc96870c8ab885898b8fae70385e05ef1a310f9842961a10c24fc705860146d117034a194a333e7faedff11cf4f418cee260b7eca85b7bab0cc0afd25f473ac6b018f6ec7b11d99baa70dd583a68b509eb51e184ed4c55b82e8ea022ba7032bda77826ce16280ec673f576319c4edca1af9c548ca024a1a07dc4eaa84ccfdd9162f6651a38a02a91f78f7b2c5a4ecddc39601e5c1426d4bbfd7a386188c8e802c7ae43eeec89202ca45504df5a87434ea97067402897c551f8afa0f605c8a521acb2200671f06b08df4cd330598bcae48cf4ff605ee78896583966740e1d2f22ff9768c5557f4a4c659fb6beb5b5bdfa763afa401eec20ed92a20c08bbbcc35e8b7677d118570f20214745d75ed760c36de6dbb8f54cb8c95c097d328162e18f393cc1ce7d7d103446c4919e366b128163e720b30d9c71100c3a6c0b6de686b4be3389cb71a008c1b6f9e35cc9edceaada6322fe7644eb9990053134d195aab4edf8c8d7d5d7d4922c677937b706a5a412d7117cc04c9290651b4fdc04befea9d5ed94a65a228d594b06f7a25f55ce730bf886a131387f0f7cecd4c09fdba98da97895ffafd0b5e382e6edc4bece618c7be60b67ca80b593a7d99c0306d21d6f817737603873cdb13ac852c4668a67b9200096ebe4af2dea257cb2c577db037a26788d194745faeac00e35b92e73b24e851725fd4f39bb02abf74dea7594cd199fe708f678922e1289bedca381311880e14ec9299a11f6c6c91279f09fe4d4d89e9cce746510f68992312b7a6527f6b4d3a6e1a8ea55b1ae196d204e2b318156078f558e7225c7e47a6ee3806410a746fd95f16fb22b1fdf08665c9aacf00868705f388a487cb2c9c198ca2756273b818e51a5bbe95fb9a6007c249456ed0f7b07b6d4d7053c82b0209ec72d6de278c54b3003f6a316bd13475207b9ffd0f810e5cbab9e2b70aeaeef83583ee83e41b24a5a0a1cb4096bda2b4c3910e85440a160da32ea16639b5dad4f838f224cfcade35576e17df266f797a1ad26228ee7b991674e1d60344ac52d7127066e496d5952f0bf48ad9e1dc1f8b1dbee40e6507e4c1e71a781de825b7453d507e2bd9a0ae69558c5496f99221d14180f39253aed556e203732ed7fa12214b6041a2d42e808a5c6c627b4839bad1888cbce2498d37fbc9493c2e3cfedc6afa3c2fc0c2e78b602967628a12c32064ade5a382e2d7a07f19bc3fe3d2771686158f495aa1b9aeacf36bcbdbb3b52e87cef4888f733d1534ab11f8551ad0221768a39d92fb9935d124a2ea0b1317e28fe7a02de1855796150092a25d64ffa6b97b615d9b73194b6c0c5fc8086ebac2b8cf90ff330aacf4d1567fb2589a5e07f5a7285b1f792e734d1eae8b73994c23a5c04b44bb96e470983d66e46f5aca3e9c4f261addb760e97acce34365a1ad800d1a09990948181bd6dccb1081dfda3c7b55a00fae239b82e0d8cb3f3fcbab449f9ecc26562ab9b513cf69a95ec4c285b53eda2c03455895fecd5ca0fc686f61970572cfa7fd0fa92ba175a0e8cc692e1669efec359f57ca198c9be22ee2e894b68ee2c6a0eca97b5115ada9acc7a87af67b97bab1c84e434fd48bd28ea9c5201d605fcb31dc7b0ed56cd9328885503b90c495c61105a7394ac67334a42980973fe89633f604001b3fb45c214b733b8cc07b50aed57898dd59084e4213fe2523f077981fc942eda9c884643176238fa0ebfdcdb3f8a3d6c6223160cd70b788da4ccd7958cc0e5d445d5f74f75bc5c9af7d1b2928f655a972003df88b82bfb7822f2891f5aa4826fd4f161e25413af5e9517a6e37c5a69c34fc429844bc87e1e5a8d9e1e747f74e014017a87083e90baf5e7c30f5e0282bba74f2c4be954dca4d897f9386e35b359c28bf7124a9ecda0f2b20ebfa044437bdc694687caeaeb116ccbf1dc45255e67584771ffcd426968d4aa717d7f3894d4a7c70ca216cdff84768b1760388d3a9905531e36ae480a1f228586194cc564092b91cfd939b86a7beeb548b4dca51e4faec2d5aab87469eabb4417b6feb99c62d450c4fd294dc96526287cf61d5971c04680d324441591ec8331fe20769db5cfbdb75c63a2ce73179bffcbd52c9ad5d24e7aed50d5b572134c9cefc96992600c5a858f0af8572906ed6281ebf59637698bed36eca4917f4ea169826d6a32afceece1e1c27da7388bd9b520b878360bf10f39eacf35fe59f6d5766c6d7612735fe3054ef0dd4e8be953820475b0d499a449e2d6103a37384294734d36948aed6b1906701f87d17fe248b227559bffc7698034d94236e9adf338417038f9c9ff75f5b99c82bba887677370190aadc34aac5e13d6234d2f8cbdbf53f86729bdb06c860f39e938ec2fca708b83dcb7f867ff131119308f7b434ab86c9ad35ce15cb0c760754f4bb59a72bbd67b4014793024e096d9a4a66159705367d0709d75f81204d1660976071ee48f7973b4e18d7fa86029751b783136d9ebee635654a6561e84427c207513c9f548330b39372540ca565c698fdf7ec0c02b4190ffb734f62149546662cc8787d54c6e60222332726828ef4a9da0e66f8fbb47148f9104ad8da6db6f2e57bfc395b6f48ccb159af890951461612e52033c5c474563b0a9a9a67400bc2274e92ceedc271e7d21e8051d16c54ae690daf966de50a0a0f8e64ad45d8d21744abd14d51cd771c6430ad083242537f983f1af5937c549e5cad5083700416f15b9aeede143a319b469c8ef32f99067127369bc0a99a74f634d7e703e680059bfa50d9aeb1996ae0835927720c5a6e9512d239428da94f4ee6333ef8453d4bb3ac664d9d9aa3d930b388645da6e5cb94b44a729e066d12798f7f2635a0972efa834c9554da62bdd35b4626fbe5167b5f3ee685a4e0746861031e2232b9211bd1b9b2bdbfca58250b167f4182b8db4e37f0c674b860fb29e57c46bffbe9ac8847a2d0849c3a000bdfefbe2ade4bfb476e5b168d84446d897f796b6477fabbdeb75cb8f95d4f0479622dc70e28af5e7acdd7d970e48ee504b076946f23985bf89cef9449f3d95497ad4f7be48f745e5e6b920755de158acf4f916baff44b4dc3c72b885052f3bcf2452ef315bd5d76284faad1e1d662f9dbbd282bb81d1634b748b5851d90634b70fb505e2a2e692b7f86ee0fb02389a3d75ce0e65bc4efe287acdb8ad60fe83cd7348c5e3e24f92b23b417aedc4749af5acae09b856db58782b75e965b6d76adb467dbabeb8cfde7e20938bd5976daaff959b9f91996d3cf10df6b1d5602e0b8a8380621dac8ba906e57871c73a943eccfbb05567d48a791a583c081616df24d3d464967b15921a9ef3f46bb1de189939e7523374be2c2fca7194162d41ac9b33070d33f3b5ea66718dccc3d93dbda6abea5c1098b3702689951ccdee421c0a4c741ae3feee318dd4a96f94bca9033e055474d41a1d0bbb89f3668203330fee3657c3db45483132162073e6f6b59899fc815fb3627e4244a999df15ddb11aca4c253e6c0442e722d4360d5535ad98a94e3997650f7eb257ec56eda68965b545fea8eaaad77631c20b1cc8962a0f51c89cb0339c69ae7350648508807002114cd603fb75f8695c09baa1484a1b1f21c81cd05139863040342cfa618119a8e7ac1e59394363aaf6c181386cbbf46bca103c198095cef38586c5a592fb6f677cfe2eb6d43410a50a93935622ebba35b61a1001778a805d6d9b88be7e1d44eb117ec0c55765ef24adad9e5a2e37a361f4712ee131864ff8abddfc731c0532aa13bfa5c5cd89c6a009194389a7542a6e568123825d099e726e1a189a36d313e8cc010edf43817b0562ddd48c28d3db1923e6e39c431082a30c8278b2bd178bdf5aff72ec34b94efa2e3eaa5ca1d7b47f56a1b22cfbaeaf320dfda9913ccb54505b41eeb6b1cdc75bdb402c1ba357c378571bf1a2346526db585f6f59ebd3117ef435ae7504ef97ab6a494d1ce5fa789279f6cde07367afea75ac6b6cd710c02e751b39d0612f570ce1e591d25b24c0cfc003b35eccffd2af2bbbfce50199309a799df98079888ef6ae5f48de7ba41207a8d35cb04dc8ddf6ab6db7d29c59b878142a77a4ab0f2ff8a6cb124baf60392c8b8b123da6c0178dd12e1513af881b05357f224276f314a388fb20b7801e7bf7f7835d57439b2ad72e7b7e39257aa68018598da4bc57bdc05f44fe1f753aa49e66c51e2d6f803cb36725af835cc26a1dbd24695dcc47aabe5919f72feb2d380e86595610a82fb7ceba4fff6fa2041111454afe666e7a27316ca5aeb21f5791b876c95ff078a8c65a8d9870d857f510f08cb5ca2fba4b6a381606b681003e4d862b6f86a4ad4b9f1dfaa9cf85225557159d798fb0104ac8b4e95c63ad9512b940b56762914348716bc0e9b13bdcd06a1260b64b4d7104313eece8066b5b0b29071822e7dfb914cb47f3a07b9eb8b698adac5a5a1a32f90e2fdfc49dc3746641316149a18e512b39f744bef52d50ba093e055949bd4068732891d154542cafffd9ccfc3318f2eb04ea4e999501f6829379ded1788ca19ad2f0b6bd83f5fb2f99e0cb4fbc6ead8c8436869f0ebed2d5bc9415e5ae94d810538f25d708fb870fb0d32c0df1db07cf9ec7575d6ddb01ad81416dd0928a91aea7d6a7647865fffc5492fd1186135889e4b81c4caf9e2ef24c436dbb07b35f9033e421134c3859066333169736ac58d32770baec6f35be38d429de4fb25569bb5cc33716457e34208a111bb74a3f23e7b3cf3fb16b1c010a3bf24cbe3cad618afb0bd9feb878cc484a39c6e0c139714e2906ac9abaed062445114e6a8883e3a3dbc421acfe865866c3aa0f7bd4a55449a2d9c3345b9aaa945a74da8ab046ff7d76bb3623b2d7590a02f941bbc021b463502cff23511d66a309d57f7256d4edea9acb739d5a3c3f7a3a88ad7c625149d2034d80cd037cae167727d4330ea3915bc490370204a387d7351285f7dcf4699531b524ef00d354c5503dc07acaec4e67be0240437a09b716498b98364ee6207059f1c673c8575724a189896a969235f5d2f36a734f241467e3aec6bfcda5e463170542da564f31dec9db76757155d95f4a1cfef199296952d3514b5ec7b5f3c89ba7b594c79182f34bc29e4b7da88b28ca8cd58b80ab62fea0e90396422c0b3d35314b7a8bf1b2ae1a781d56cd4deea89623c6a99daa5539538015d99f9278608da4c6d9736cf5f71706329e2bfebbf7ba2d096c4bf04c7249317904a2824d814359dc81294874c344ac5e6f239fda2c3584d59340d49903f32b751e1c75dc7eb89b069053806050b5c52d3599d5036cd06c23a356904d2ac8bf21d20bed634820f281f0a24c3bc2a6f187ed8c257691daab3f35e61d04b715831b49860ac9988b2c4c98d92fc0065909a1948b8c97fe89a00904e32f4a62236664f8111be74e07004f4db26ba4b4efbefb9a347d2f73ebfa9cd4f6311730537c3db2c56d8d9e81fdb64b2586566904846a72fff3a8eb9df86d3e8e9bb098729ea28a13890510fb60860f17df76b46eef6ab20b4bf1be65f2dfe433b3e68ac63d9e7cb99d525fa4b272c2cd0b24439726185011f4c60852132fadfcae784631cd510aa56a9107afdd399f2514abfeacffa9284a93f5d93ab0b339d730cde541f56e5dcd83118dba19d4d1f867bdb98a2ba6c70effddc6567bbfe455402abc82d84b08b60ce4cc5224e4cc825b29fd127b505de5c4dea7f831455fa3a9e7b3531959d7e4f056136efe1281a0cce34fdb9bee542b205eeb91824ce6bbe5543781e94c8c16bfc4c35434a58647e531e942e3cea266ba7d5c0a989f715097707c50bf85c12bfa31951041f0ed8277145117b5093b22e862773d232aec3ac3ccca8343ee08dcf7c356328c69aa03db3dbc7f4d5779b152aa0551d05843f92007c2554bcdc48aa07559207d65e83a0fb044c4b4111f3bc2e07230f916cb62b284d0781cf7d96ea8579a38cdc97d3acdf4eb9372e498029b5e894320ecec1979e22a974dfefa43ea48e4061949963a03c2a446d35b83f615b47a7e8eff03af174bbe23d0000e539b30f217a2c732a4a83709763cdb43bc2f824d14a24f64905c1c7d15c181831273d84d838a85016d3c69285cb28ca5641399ad8f617751fa1883996b98fa15b19b3c9d3d4f1bcdb3047180edbc9998c1f528cfb418e907b1cbbaafe96da6a7537c823f38347476182ddf2f8d274507ee3e8ab783de1a21ad87cb7b4d3edbdae89ae9bc138da16cae8c8346fd4ac738b77a952ebbb96c316a4a7005b1a4d5bfc8c2ac1094612b1ae40fd94e635791898484c35907e57f84cc9e65fe0454a077de39a9fcdf953e2d9c8b0670935cec0bb1ebf989090d5f2fb5dad01d6660183f83e3832bdedcc0f932ab1a78dab4b1702ce25352f1ddbd98bf83ed400e0a1cca9bda285135b3f6f42c58395d323ee98bd6053f485c7f290073790d8faf9f25d74186c1364fbe32589277662a8992b0dd1d0857698dfe38ea3a4e2f23912991814dff303c1d34d95c9f3728d76387bfc8f6ad692aa4cd13de3c37a1c6c75310c1cd5a64c9575aab575e7e88811e9789cd2690a373130549cb69837c929802c4f625761065528456cc0f2caaff207993a8470bcc80a1b5850dc5fc13b21613fb6c747a4f26095f6c4131b34cea93ce2f94959098a47ef251c953013c410b793375f6d0d9739f934db417d39efe28a512a793e15a6e47d50a4a2b038da1d55ffccb0fd1cf626bb761f6b272036cff0c138ed00e7bf498e93ea69332157f148f658cbd3a603dcd</script> <div class="hbe hbe-content"> <div class="hbe hbe-input hbe-input-default"> <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass"> <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass"> <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span> </label> </div> </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html"><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOP</summary>
<category term="3D Engine" scheme="http://luhao.wiki/categories/3D-Engine/"/>
<category term="3D Engine" scheme="http://luhao.wiki/categories/3D-Engine/3D-Engine/"/>
<category term="C++" scheme="http://luhao.wiki/tags/C/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>Tracy Profiler</title>
<link href="http://luhao.wiki/posts/tracy/"/>
<id>http://luhao.wiki/posts/tracy/</id>
<published>2023-11-28T07:53:41.000Z</published>
<updated>2024-03-01T05:33:17.408Z</updated>
<content type="html"><![CDATA[<p>性能<code>Profile</code>工具的使用、接入<span id="more"></span></p><div class="admonition note"><p class="admonition-title">导读</p><ul><li><a href="https://github.com/wolfpld/tracy">Tracy Profiler</a></li><li><a href="https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf">官方 pdf 手册</a></li></ul></div><h3 id="Tracy">Tracy</h3><p><img src="https://raw.githubusercontent.com/wolfpld/tracy/master/doc/profiler.png" alt=""></p><hr><p>Tracy 是一款<strong>开源性能 Profiler 工具</strong>,它具有如下特征:</p><ul><li>支持纳秒级别精度</li><li>支持 CPU、GPU、内存、锁、线程切换 的采样</li><li>支持三方集成使用</li></ul><p>Tray 虽然作为插桩式的工具(手动插入标记段),但其对于原生程序的性能影响却可忽略不计 <strong>(约 2.25ns)</strong>,<br>因为它是基于汇编统计的时间戳,官方手册中 <code>1.7.1 Assembly analysis</code> 节展示了 x64 的汇编代码。</p><hr><h3 id="如何集成">如何集成</h3><p>官方推荐使用 <code>git submodule</code> 的方式集成 <code>tracy</code> 源码,但是又强制要求 <code>client & server</code> 的版本一致,不然连接的时候会报错:<u class="error"><code>Incampatable Protocol</code></u>,因此还是手动下载了 <a href="https://github.com/wolfpld/tracy/releases"><em><strong>0.10 版本</strong></em></a> 的源码集成。</p><p><strong>Step 1. 配置CMake</strong></p><p>集成源码后,需要配置新的 <code>CMakeLists.txt</code>,可参照如下改动:</p><pre><code class="language-make"># 打开 TRAY_ENABLE 宏option(TRACY_ENABLE "" ON)# 添加子目录,注意 tracy/ 下需要包含一个 CMakeLists.txtadd_subdirectory(../misc/tracy ../tracy)# 将 tracy 库链接到引擎target_link_libraries(${PROJECT_NAME} Tracy::TracyClient)# 添加 include 头文件的目录target_include_directories(${PROJECT_NAME} PUBLIC ../misc/tracy/public)</code></pre><p><strong>Step 2. 插入代码</strong></p><p>因为引擎是以帧为单位,所以需要使用 <code>FrameMark</code> 告诉 tracy 一帧的范围是什么。官方文档推荐将 其 调用处放在每帧渲染的结束,即紧随 <code>glSwapBuffers</code> 调用。</p><ul><li><code>FrameMark</code></li></ul><p>最常用的 Profile 函数是这俩,区别是后者可以定义 tracy 条目的颜色:</p><ul><li><code>ZoneScopedN(name)</code></li><li><code>ZoneScopedNC(name, color)</code></li></ul><p>可以通过 <code>ZoneText</code> 传递一些参数,例如 <code>shader uniform</code> 的名称,以观察哪一次调用开销大:</p><ul><li><code>ZoneText(name, size)</code></li></ul><p><strong>Step 3. 开始 Profile</strong></p><p>运行标的程序后,注意打开对应版本的 <code>tracy.exe</code>(<a href="https://github.com/wolfpld/tracy/releases">下载地址</a>),点击 <code>connect</code> 即可。<br>对于高版本的 tracy,下方(红框内)还罗列出可以 attach 的进程,非常贴心。</p><p><img src="../../images/tracy-start.png" alt=""></p><hr><p><strong>Profile 示例</strong></p><p>笔者实现一个 OpenGL Demo,发现 <code>Render</code> 部分的开销比较大,于是集成Tracy后增加了一些插装,最后定位到是一次 <code>glUniformMatrix4fv</code> 传递时的开销,如下图:</p><p><img src="../../images/tracy-demo.png" alt=""></p><p>最终定位到 <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGetUniformLocation.xhtml"><code>glGetUniformLocation</code></a>,它会在每帧查询 uniform 变量的位置,开销非常大,改成 缓存 或者 全局固定ubo 可以优化掉。</p><hr><h3 id="其他">其他</h3><ul><li>应用层可以对 <code>tracy.hpp</code> 再作一层封装,便于使用,参考 <a href="https://github.com/urho3d/urho3d/blob/e0ce107356b255bf2e24d94a41d00b512b9ce633/Source/Urho3D/Core/Profiler.h">urho3d profiler</a></li><li>tracy 对于 内存、图形API 的支持,网上文档和使用不多,有时间研究下</li></ul>]]></content>
<summary type="html"><p>性能<code>Profile</code>工具的使用、接入</p></summary>
<category term="工具" scheme="http://luhao.wiki/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="工具" scheme="http://luhao.wiki/categories/%E5%B7%A5%E5%85%B7/%E5%B7%A5%E5%85%B7/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>【RealtimeRendering】2. Graphics Rendering Pipeline</title>
<link href="http://luhao.wiki/posts/rtr-2/"/>
<id>http://luhao.wiki/posts/rtr-2/</id>
<published>2023-11-25T15:21:06.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<blockquote><p>本章介绍实时渲染中的一个核心观念:<strong><code>graphics rendering pipeline</code></strong>,它将图形渲染的整个过程,抽象为一条流水线。</p></blockquote><p>渲染管线的开发,有两条重要原则:</p><ul><li>各个阶段可以<strong>并行化</strong>,但有些会依赖上一个阶段的输出</li><li><strong>短板效应</strong>,性能总是受制于最慢的一个阶段</li></ul><p>本章还将渲染管线细分为四个阶段,后面会依次介绍:</p><ul><li>Application</li><li>Geometry Processing</li><li>Rasterization</li><li>Pixel Processing</li></ul><p><img src="../../images/rtr-pipeline.png" alt=""></p><p><img src="../../images/RealTimeRendering_51788.png" alt=""></p><hr><h3 id="1-Application">1. Application</h3><p>所有与渲染相关的CPU部分,都被统称为 <code>Application</code> 阶段,它的核心诉求是:<strong>计算出所有需要渲染的 <code>render primitive</code>(点、线、三角形)</strong>,并输入到GPU中,给下一个阶段执行。</p><p>重点学习如下领域:</p><ul><li>(CPU) Culling</li><li>Collision Detection</li><li>Multi-RenderThread</li></ul><hr><h3 id="2-Geometry-Processing">2. Geometry Processing</h3><p>这部分完全运行在GPU上,<strong>输入是CPU传入的 <code>Primitives</code>,输出是屏幕上的 <code>Pixel</code></strong>,它的功能较为繁重,因此被细分多个小环节。</p><p><img src="../../images/rtr-geometry-processing.png" alt=""></p><p><strong>2.1 Vertex Shading</strong></p><p>先明确CPU阶段最后传入的数据格式(参考 OpenGL VBO, VAO),它是渲染图形的所有顶点信息的列表。考虑到它们是基于 模型空间的,因此要<strong>通过 <code>MVP</code> 变换</strong>转化到统一的世界投影空间。</p><p>同时,顶点上还包含<strong>法线、颜色、UV</strong>等信息,<code>VS</code>中可以由这些信息进行一些廉价高效的着色计算(当然效果比较挫)。</p><p><strong>2.2 Clipping</strong></p><p>在经历过投影变换后,我们得到一个 (-1, -1, -1) ~ (1, 1, 1) 范围的标准立方体,出于性能和可见性考虑,所以超出这个范围的顶点都不应该渲染,即被裁剪掉。</p><p><strong>2.3 Screen Mapping</strong></p><p>这阶段的输入是经历 Clipping 之后的三维坐标,输出应该是二维坐标。<br><font color="#FF1E10"><strong>todo: 比较粗略,没搞懂具体做了什么</strong></font></p><p><img src="../../images/rtr-screen-mapping.png" alt=""></p><hr><h3 id="3-Rasterization-⭐">3. Rasterization ⭐</h3><p><a href="/posts/M5TXVE/#Pixels">【GAMES101】Rasterization</a></p><p>这阶段的核心,是将二维坐标,映射到屏幕坐标。</p><blockquote><p>假设有一台分辨率为 <code>1024 x 720</code> 的显示器,那么光栅化的作用就是,计算 1024x720 个数组的 rgba 取值。<br>这些 rgba 取值再传递给显示硬件,就是最终呈现的画面。</p></blockquote><p><img src="../../images/rtr-rasterization.png" alt=""></p><ul><li>(上图)左二阶段:<br>通过确定每个像素在对应的三角形内(<a href="/posts/M5TXVE/#%E9%97%AE%E9%A2%98%E5%BB%BA%E6%A8%A1">参考</a>),以决定其着色。<br>其中 <code>Triangle Traversal</code> 就是遍历所有的三角形,并对像素进行插值,其中学问很深不细究。</li></ul><hr><h3 id="4-Pixel-Processing">4. Pixel Processing</h3><ul><li>(上图)右二阶段:<br>即经典的 <code>Pixel Shading</code>,逐像素的着色计算,这里不详细展开</li></ul><p>这阶段所有的颜色信息,都存储在 <code>ColorBuffer</code> 的GPU内存上,通过 RenderDoc 抓帧可以查看其具体内容。</p><p><strong>visibility</strong><br>另外,<strong>更新可见性</strong> 也是这一阶段的重要任务。通过每次绘制时与 <code>Z-Buffer</code> 比较,即可以判断深度遮挡关系,这里是硬件支持的算法。<br>值得注意的是,<code>Z-Buffer</code>机制对于半透明的绘制很不友好,因此需要严格遵守 <strong>“先Opaque、后Transparent”</strong> 的绘制顺序。<br>而 <code>ColorBuffer</code> 的 Alpha通道,通常还支持用作 透明度测试,即 <code>AlphaTest</code>,也不详细展开。</p><p><strong>Stencil-Test</strong><br>通常还会将 <code>Z-Buffer</code>的其中8位用来实现 <code>Stencil-Buffer</code>,即所谓的 <strong>“模板测试”</strong></p>]]></content>
<summary type="html"><blockquote>
<p>本章介绍实时渲染中的一个核心观念:<strong><code>graphics rendering pipeline</code></strong>,它将图形渲染的整个过程,抽象为一条流水线。</p>
</blockquote>
<p>渲染管线的开</summary>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/"/>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/RealtimeRendering/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>RealtimeRendering 阅读计划</title>
<link href="http://luhao.wiki/posts/rtr/"/>
<id>http://luhao.wiki/posts/rtr/</id>
<published>2023-11-25T15:15:40.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<h3 id="方法论">方法论</h3><ul><li>建议阅读英文原版,遇到困难时借助:翻译软件、Chatgpt、毛星云的中译版</li><li>建议结合引擎源码 + 实践落地理解,不要纸上谈兵</li></ul><hr><table><thead><tr><th style="text-align:center">章节</th><th style="text-align:left">概要</th></tr></thead><tbody><tr><td style="text-align:center"><a href="/posts/rtr-2">Ch.2</a></td><td style="text-align:left">渲染管线概览:四阶段</td></tr><tr><td style="text-align:center"><a href="/posts/rtr-3">Ch.3</a></td><td style="text-align:left">GPU 硬件架构</td></tr><tr><td style="text-align:center">Ch.4</td><td style="text-align:left">Transform</td></tr><tr><td style="text-align:center"><a href="/posts/rtr-5">Ch.5</a></td><td style="text-align:left">Shading Model</td></tr><tr><td style="text-align:center">Ch.6</td><td style="text-align:left">Textures 纹理,<a href="/posts/2022-texture/">【Graphics-2022】Texture 纹理</a></td></tr></tbody></table><h3 id="资料汇总">资料汇总</h3><ul><li><a href="https://www.realtimerendering.com/">realtimerendering.com</a></li><li><a href="http://110.42.228.178/pdf/RTR4-EN.pdf">RTR4-EN</a>,<a href="http://110.42.228.178/pdf/RTR4-CN.pdf">RTR4-CN</a></li></ul>]]></content>
<summary type="html"><h3 id="方法论">方法论</h3>
<ul>
<li>建议阅读英文原版,遇到困难时借助:翻译软件、Chatgpt、毛星云的中译版</li>
<li>建议结合引擎源码 + 实践落地理解,不要纸上谈兵</li>
</ul>
<hr>
<table>
<thead>
<tr>
</summary>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/"/>
<category term="RealtimeRendering" scheme="http://luhao.wiki/categories/RealtimeRendering/RealtimeRendering/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
</entry>
<entry>
<title>【源码】开源游戏引擎系列</title>
<link href="http://luhao.wiki/posts/2ZE2VGG/"/>
<id>http://luhao.wiki/posts/2ZE2VGG/</id>
<published>2023-11-23T14:57:42.000Z</published>
<updated>2024-03-01T05:33:17.400Z</updated>
<content type="html"><![CDATA[<h2 id="引擎汇总">引擎汇总</h2><ul><li>一些老引擎(如 KlayGE、AtomicEngine)不支持 VS2022 编译运行,需要额外修改</li><li>一些引擎(如 ogre)只能采用组件的形式嵌入,没法直接当成编辑器打开*</li></ul><table><thead><tr><th style="text-align:center">Engine</th><th style="text-align:left">特点</th><th style="text-align:center">Build&Run</th><th style="text-align:center">编辑器</th><th style="text-align:center">Profiler</th></tr></thead><tbody><tr><td style="text-align:center"><a href="https://github.com/gongminmin/KlayGE">KlayGE</a></td><td style="text-align:left">编译失败了…</td><td style="text-align:center">×</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"><a href="https://github.com/urho3d/urho3d">urho3d</a></td><td style="text-align:left">代码风格,容易上手</td><td style="text-align:center">√</td><td style="text-align:center"></td><td style="text-align:center">Tracy</td></tr><tr><td style="text-align:center"><a href="https://github.com/AtomicGameEngine/AtomicGameEngine">Atomic</a></td><td style="text-align:left">继承自urho3d,<code>C#</code>脚本</td><td style="text-align:center">√</td><td style="text-align:center">√</td><td style="text-align:center"></td></tr><tr><td style="text-align:center"><a href="https://github.com/OGRECave/ogre">ogre</a></td><td style="text-align:left">功能庞大</td><td style="text-align:center">√</td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center"><a href="https://github.com/bkaradzic/bgfx">bgfx</a></td><td style="text-align:left">图形API丰富,简洁</td><td style="text-align:center">√</td><td style="text-align:center"></td><td style="text-align:center">Renderdoc</td></tr><tr><td style="text-align:center">o3de</td><td style="text-align:left">todo</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">godot</td><td style="text-align:left">todo</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">cocos-2dx</td><td style="text-align:left">todo</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr></tbody></table><h3 id="注意">注意</h3><ul><li>编译前请阅读 github 上的文档</li><li>如果 cmake.exe 不清楚传递哪些参数,请使用 <strong>cmake-gui</strong>(推荐)</li><li>因为某些引擎版本古老,编译脚本写死了如 <code>VS2015、VS2017</code> 等参数,这种情况比较坑爹得手动改</li></ul><h3 id="编译相关">编译相关</h3><ul><li><code>error c2001: newline in constant</code><br>这个报错一般是因为文本格式错误,可以用记事本重新保存为 <code>UTF-8 BOM</code></li></ul><h2 id="KlayGE">KlayGE</h2><p>截止23年还在更新维护,但是提交频率不高。<br>cmake遇到错误,手动解决后还是无法生成,已经放弃。</p><h2 id="urho3d">urho3d</h2><div class="admonition note"><p class="admonition-title">NOTE</p><ul><li>重点学习 urho3d 代码风格、项目结构</li></ul></div><p>截止23年1月已经放弃维护,且已改成俄语项目(?)。<br>抛开别的因素,代码风格和结构非常 nice,同时又不显得复杂,值得阅读学习。</p><p>支持两种图形API:</p><ul><li><code>OpenGL</code>: urho3d\Source\Urho3D\GraphicsAPI\OpenGL</li><li><code>DX11</code>: urho3d\Source\Urho3D\GraphicsAPI\Direct3D11</li></ul><p>项目结构分为 samples示例 和 源码部分,非常容易上手:</p><p><img src="../../images/urho3d-demo.png" alt=""></p><hr><p>代码、注释的风格也十分清爽,这里给俄罗斯人竖起大拇指o( ̄▽ ̄)d:</p><p>推荐一些模块的阅读:</p><ul><li>Graphics 渲染模块的API组织</li><li>xml、json 的模块化管理</li><li>IK、2D/3D 的物理模块</li></ul><h2 id="Atomic">Atomic</h2><div class="admonition note"><p class="admonition-title">NOTE</p><ul><li>这部分源码和 urho3d 重合度较高,建议一起对比阅读</li></ul></div><p>Atomic核心源码是继承的urho3d。<br>编译脚本只有 vs2015、vs2017,因此手动用 cmake-gui build 比较稳妥。</p><p>严格意义上说,Atomic 算完整的游戏引擎,拥有 runtime、编辑器、创建项目 等一整套流程,通过启动 <code>AtomicEditor</code> 可以打开一个项目管理器的窗口后,参考 Unity、UE 的实现。</p><hr><p><img src="../../images/atomic-project.png" alt=""></p><h2 id="ogre">ogre</h2><div class="admonition note"><p class="admonition-title">NOTE</p><ul><li>ogre 功能庞大,但是 GUI 真的丑</li></ul></div><ul><li>全称为: <code>Object-Oriented Graphics Rendering Engine</code>,即 面向对象的图形渲染引擎(不禁疑问,这年头还有不是OO的引擎?)</li><li>支持几乎所有图形API:<code>Direct3D 9 & 11, Metal, Vulkan, OpenGL (incl. ES2, ES3 and OGL3+) and WebGL (Emscripten)</code></li><li>cmake 时遇到 <code>imgui-1.90</code> 缺失的报错,建议手动clone一份</li></ul><p>可以通过官方demo (<code>SampleBrower</code>) 进一步了解引擎实现:</p><hr><p><img src="../../images/ogre-demo.png" alt=""></p><h2 id="bgfx">bgfx</h2><p><code>bgfx</code> 是一个 <strong>跨平台的渲染框架</strong>,支持图形API如下:</p><ul><li>DX11,DX12</li><li>Metal</li><li>Vulkan</li><li>OpenGL 2.1,3.1+</li><li>OpenGL ES 2,3.1</li><li>WebGL 1.0,2.0</li></ul><p>值得注意的是,<code>bgfx</code> 实现一套<strong>跨平台的 Shader方案</strong>,后缀是 <code>.sc</code>,有空可以研究下。</p><hr><p><img src="../../images/bgfx-demo.png" alt=""></p><hr><p><code>README</code> 首页提供了很多基于 <code>bgfx</code> 实现的渲染 demo,可以结合工程代码理解:</p><ul><li>Cluster Lighting: <a href="https://github.com/pezcode/Cluster#cluster">https://github.com/pezcode/Cluster#cluster</a></li><li>Cubemap Tools: <a href="https://github.com/dariomanesku/cmftStudio">https://github.com/dariomanesku/cmftStudio</a></li></ul><p>推荐几篇 渲染分析 的文章:</p><ul><li><a href="https://www.cnblogs.com/crazylights/p/13555816.html">https://www.cnblogs.com/crazylights/p/13555816.html</a></li><li><a href="https://hinageshi01.github.io/2022/05/30/bgfx/">https://hinageshi01.github.io/2022/05/30/bgfx/</a></li></ul>]]></content>
<summary type="html"><h2 id="引擎汇总">引擎汇总</h2>
<ul>
<li>一些老引擎(如 KlayGE、AtomicEngine)不支持 VS2022 编译运行,需要额外修改</li>
<li>一些引擎(如 ogre)只能采用组件的形式嵌入,没法直接当成编辑器打开*</li>
</ul></summary>
<category term="3D Engine" scheme="http://luhao.wiki/categories/3D-Engine/"/>
<category term="3D Engine" scheme="http://luhao.wiki/categories/3D-Engine/3D-Engine/"/>
<category term="Graphics" scheme="http://luhao.wiki/tags/Graphics/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>【量化】OrderFlow 订单流策略研究</title>
<link href="http://luhao.wiki/posts/orderflow/"/>
<id>http://luhao.wiki/posts/orderflow/</id>
<published>2023-11-04T17:59:51.000Z</published>
<updated>2024-03-01T05:33:17.408Z</updated>
<content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <script id="hbeData" type="hbeData" data-hmacdigest="ccac50fc08d0dd2d4fc4a571702034a4c2d2400428c3fc8d0e47cb23b2617d98">b3dc07a81f6459d120ce338ccca5504603780459e0306826441972681af56d624582f341449102626e559c75af687736c2dbe4e6baf6a934a24c0f78af13a83ced8b74c5e59aa1777c6d8a7c473d8eeb65368e9c8d836d47c12efb54e65da712f7ce30f43d95ed5d1faedfb647a5ab9ead8e3ea35bd6ffb81f3b46f64dfb989dad5998fbd968d8322f354aea44b5ba730decb5ac815450b1672014ccad9da2c73da7476fe270af5d2cff0cff70aeac6ccfd1fe160cace9d4e1a0ddc43cc0c379326660785b5fa0934f65e2fbaba535db0dbf085a0c6ca61625a8846c4855b0bb0f52ea598f22a9c9a53c1c2151f04eecf16c58c96444c417e73d4037ef69a461f975df7e624370078210fb5f983c29ee8e3f0ef2c6cc50e939615ee37e33c92992883c6c9ff8828c4bee8ac6287103ab26f6c658c0aa9dd6990058d3f440d0cc673a6d3db6cc7c6b646feeeee67bb10eef676edeb707e4d7b9f10b7243868021dab0b094fd3638281b2bb15d96acb1be6b5daec423d2e6ad1c133e3eade3055787e356da765caa8c93f298959510f5e4ce2c50265106bfb4408cfff89f290d2e96955727eeb52a627ffa26da381cad9a0aeaf46a1cf4cb7fd56a2090c3762662e44863a550a9bfffa3a98c86ed1db8194b6a9d66129cfcf34367f3c86c7904d8d915251f1e5dcd81ff6a9936d7f52369388053ba60113acf175086dd5b31521dbd052dd0a7e59248f5dbad97ba6aa059a6ab76036ca2dc21e607d79c68c041e1ee288628f460fa00c5a79ee62d0f625d2c767a7bc8e5fa99ca83b009a20d1db280d774f026ba2a8f343156db04b7cab70872e48ef48d7db1ddb4e81711d788a4ebc7741615ae93a92cdb89b685fa8f0096ec789dbd769ad90ec5c2f56de877398efdd0f56a840891a627e008d0c402015ee2225d3a1f837d5eef62bce525e0ebbd5a432116e54d8099f7623137ba9ea7c1431bce4c902435fc690d589958813c54c09118355a8ebdbec39ddbec6966144b43f689a0e1b267a1f97a4b9048f352544f920109c7ef2ad0994092ab990ff3e84441639b2914f23c312a608acd23e3551722bb515062b39835df49e0507719aab9b52a65ecc00603c18d51c3fd613aa4732536ed84399e7246f81a40c36d5b42dbd59dec4217f1ff9d054dd4bcae4d8c3406ce5ed05cbde2b0faf6cb7410312d768f1ecf50af72d695fe9b8df12c4ab45d6ea1398a998d8c221e92134a79d1e0c7f7d342ad164d51a331501f97917b1fdb9badb901a5007a5ade86f2551d5a002273a622a5bffea692dba9c677d1b8efce53dd4d9443ec2fc638c5434dd7fc5d3d1b7cc49e92a2b5b623cc6377a42cd83382f2c47751cf8da811c35a9388494b086f74486b57127c61e1d96cec892d4af68ed55fb7c7e2d27cf1dcf90e28578b680936476d65f1a6a26eec30a0c1ce4a3829af2edbbbf9c935b6e91537f7e2ea5c46dfa534f3bba03e3c0b5e4d2ea2382acd99c108f9d740f5fc27780cfb92c308d72310e13d6b360d700e4afee1d33b6fc8e8c722a2d8a8b499843ec2cba4ef4e707bee1a6c5b8cc87b509af29ee50f69302df04f61b3ee2cf240496fe12271d89e601ab5d9587453b985cc3a08aa45a51db840066ff380afecd2440b7fac168eb97337d4192908def4a519b3f7545a190d01467ae2202e1cbbaecc16eb1139c9744c839747f60c7e8d9bc60368612b0cf8dbec3fc1631d070a8dcb15c11bebede7977de65a9e3c77331dc68ec20bfd528fbcf1973bc11afd1aee7919edb34e0ba1e93b121d6898bdc893a3c7a9742951d30d4c066aaefd4d03a88435b4619a4061c9cd1926a70805849ae72caca09972271f205c51be5a43e1dcd10703b06e63f7c1ed8d3e4c5bc82e52612271b1a9c22ca1d4684c7841bd4148a584f6ca6a7b809e72593f435800f946213709b7c81ed7a6f8d761de4caa4e5e86d6da13f6ac8921c77ae62d7444ab9f5729ac4df2e434cd159a5faa1e87fcf58b552ab594ffc917e8d4ce78cb1dff1ac758aae1a2f00622df3e18dedd2901c4279321bbd29cef2c6e5b3ee23e91982ac5d5fa2046def80e9fa6f92d58ffbdbfe1a6b3a30943b46e671f2cff6201ed6b123cfd8967986aa0b1865ffcba603ff4ac1222391a8c1ea881033b77bcfa2b3d5667267d787ff997b0a547ed2e19b9b12248291d1bf90b7db8975413cf50152d6399474bedfcbbb1b53b703545972071895cbd40678f9384c59cca794c1517670a64d392bbfbbaca3a471e509107d11f18bb33db61fdaa63b3a36f109148a7b6d586f33266282222de2b8bbb4c81fbe32a59f6307562810eddbb5a163bc613ea6b7267f1f7e3c978d5071ce1caf590ed32246a7cf964eb21211327d0f85fe2ddb5aa0ca5d34f578e9ec9b343ba4b7b46fae4c6b484c76b56d0ff42fab92d68db4ec25805b27caf8b56e8c86bdb28ce85e8d29e12ae9dab79c758df57678da22d568a332a049b7216ed61fc1b3ad25bea9dc6cac945d179e16ea474e731ae155d96bfcf7b3e5aa67cc843fc7d35168296fcb81454833ba31d0eff11af7ef78a87ad96ff99f08ab20cf0d0df37e291987a012d0ae647f0f9d2d94386d26f1e5931d0f703e1fdf1bea15882564ff4ab46f7885463c7a56722cbd339d32ef5474e3fa544db9c5913658d9c724fa190f03a5369feeaeb612c83a65f96125deb436098588c9eb674a0d4bec78cea3fb11006194495cca518d92da97e65b8847a084c9b890be7867686d5de5a9a85feb332bdd8d165e3a5d7c2849d068aed5dc19eb0c6a93c2d8a06e090226fa29a097a709464315b123706245d85eac0c8dc48115caa926077a9fc04a12c3bb23855f7879f8cb61a28982c6c38fc5585e3ee5c37975f7ee96756cfb770141b7e29a8a132a4f21e50564e68d082c52d6513ed08f878ef3d4b27f57715d0f1545c66ce3c12c5a6db5d3a15db707487db6bae609219a293e5ad8e6eceebc7507809ad490f6b9b5476761668d5e9e14b68668eb3e137784acb245546c196f073bcf7c42330dd938d55eddeb2d14d370fa0320a1d1c2224b1fac5fcb156f35c0b55942bff19ef9e47a6340e90e9fa763b71f3e88f98b5b9c6c52199b0da43c685e8a47480f6d34aeb88411c612f90d45ad0a086548fa391285a827fbc12becd67ca3505ffb9fb50e679db618fd388e45918e92c54dadf8cfddf08f7a7626d152a31c327cc9a8a6abcbf02d7d91d301c2e166db7bca6a6ab2cd46d31fb0d655bb6beaebd2fe16b55b7e39e53eb74faae64c796a6d45226b106180c153cb708a47f6a69ebb4e5726b6e3a511fe440bc46710f0edaf058ca35ec0f15c21dea3b4827be686d8a87c437684c168f6b7a4d2ae3f01e4a376fa660bdb8dfe9f5106017be3298f5e2fb423435d84213a1dc17dd2bdd5b4474d640c46e591d4f87520af85b62c524b8e9bf5aa83ab8e1b98e89390daf542449a48b02aa02c691dbe354292310677796525596ad33232c1c48742e4867e3c0a77cc9b58dc7b264aaf126997d934e85b18515c0e680bbed9dd8918d5308d43108ee26f0b3d62e007f455133288deaec3ac798817b9458c9bd88373ea5eaa823850aaa32bd899774d0d4d87693194e319d93004a850618a9767e02a512eff348d8afc9d03cb6b0dec2f3d10dec3970e324476184b389d5d0603251d0a90d2e3e758a32ad20e6bfdfc8993391bff054fc0785462bf63df64b17f993ffd3c81260c012f41f66d3b4b6c5047e2bcebf2c9510558e2dde850542246275f5dc9aa5ecb350ed03f03fe84a896e52f74afa4ebc987bf267f466aff06552233ac2f22262d62d77653ecac72d7f5bec72da6b608f07dcc63642c91219057fdba0a625e4d8beba0466f78386b9e5595b5c97620373ab0edc63a8c9ea0e9d54ac017c74a87a91252f46b5733f5b8ea4da1d6b3b0a8d2cceb3837f3586d0c5f59296e61702e085f3450eaa73ad4c8335fe5bea2c101294ec4e6a3a44c726463b91897b39cd710f785daa67260d8c96bd02447362701ff85057db5b5dc6e0886f55ae77af47213da90dbfb5c377a8e8fa334c15e4c0ef40cbfd0113046efb76b985e4bc8cffed63933db58180a671332ecbf14c4e78bb4bf096562a8967a312eaaa0e7c508e637c8013ba5e753a14fb17b0296b91e81a6ee88f280211b6b2a944e5d19f3dcbec103c6e69f232096541ddbfd64a3199502ba6605d2925b1c913a3515875127ce36cdbf0622774a12b3e001002ce08d37f3ccb7e4aaf1efeb97c7465ba538df0ffa8a600d8d32961c32dffa06f484190f8b1db14e9d99a897d3bdaa62afab5a5bda81b19f476a04a7f5653dba71de1a516fb81d14809ee628e9faf78782772571f9c528e038c1ff8408e1c654a9b363b77bbcc903f4cfcab0d500b789e33994368ccf8530fdc0b299930a14881477e87f2fdcdffa56b2f9780315ce06e299a1a0b0c85f8f2b090906d2e9556ee37e83e8e331b880cc87f1bad37e0ed79a02708d4844f0136b949dbd3c074985a380b662d48a934f4ee1711097ef9736a71cf7534ea8c85565428a7408c324de2066c716bf6576b03bb3b78b983f33661490cc0976a7150e17f835ba09590ab9670e220e7bcc3ef253cbafd2bd3cd92b8ab432fd6db13d11476a38ee69642965324e1cd411bc032547706b91001886d3c44bd211cbe15d136912942596e167d48f832489f1c9031bd64ff212ee6b4e89c4429480a4b9370d25fcf2d889e2ad7afb6f80444e755a7f30ce42d4366193eaea3b6708690b15847ac317b6a75da7ab1c8363440addc9f162e68612b3e4515e969e71f049e2ec173554fb10a5ffa5a999f54fbd564c9ddf595ec5244ff0211a7ef8ec7812922870086a213d01038d0f76d6371fa979303b5e3653e27978b565b8b2569b9095956f5df76c65bfa64b12291646237a0d022c099c5191d213ca318acad044192c307dfea73c104724afcc2e6cc29505a3366b8f3fb73a732221f09c4db95ee59d7aea7d185a492fc668e36f1ef9395018c4eecac0230181133387525c6a8c6f9448f0324ca88f61df08a8421688249d9a01938acba3c42f4e97e96dbaf290a96135ed97b7b7af67cff5a6648d9d3a0ae2730e5693465070683d912dd9631f81bc692c7c8232ce73d87459a232344bb06fdc09e2f70f96bb1c92f0bbfe541ded74a5508a21a7f3f92bd447ca30e1f35f383d3962008289d34c40d6d49eb74774bc31af3bcf3bdabc05972126a98e696c5e7e8e923be2e5b30486fa7c4ade5d2979df62cc094d1dc4f2919483deec9e745637c8f561563e11d7475d2cff990e7db05a30d2e29fc6d48ef3259f477472704e16aff4ccc3765d9a250e8d1766d8baea68a3b91093e833f5326dacc5c55b2dedcd0b1d23cceed1a64bb1003f4fd5a7cfe4af6ae229117010cf9b07514f2f1feadb0356d4c0ea4b0c26d5b2b647241e035d65cb476a9f1a55e131708e7b4ad03918916b52342a6361f35716a260b39bcf6876490f0d213d0a9264a7277191098c20743d7f754676bc22bff0556c63dc955e741aa4c4d58d5fbd8720a503cdc37ec5b67fa0b13a6a80f06821f5ca52f63d9489a1e00b208218c7135d42e5078d143173944f188c6e1518d1567bc3768c486aa656b3e071f97131385b7c11a613679cd581a4dc69fe3fb7c24b49a4778d1a90e13024b522e1d44a70997d436d2f484e6be1a57c65fb292e94dd3ff810215b312d752a13ae10af71ab554c30245224060bb6747b3004378d4906059bc154efb5bfd94c3b82305089912ba62586ab9c9fa79fbc15e72fcfcb943739584dbcc8ce5c1ffdec4d0391561d5e3f52817dec927b56b0c938c85d69260ee30819b0dfd688dbcf4fc09a46dea592ca6fc9c498218f0d81c1981af450204f07d0a52afaa4bb9c4591786f8bb444b9d4b6402509579be36900a2fbca4b0c2f42255a8777687bde8e9af2e14f872190ea8e69d6b876217c7fac0cabcae3b5def97924576b981db5d1068a6071395e916c84a6e7e28c62688a3b2f489b392bb48ce550580fee2aac74343b9254fa749a723b44543e7c89e259c8685e017987ada3431d1198946c06cd75122cadfc2725e8085e3d380877bc5e9f123a85744974198592310b2da5226b9275250b9fbcb7c1fa1f853dfad337aa9b6bae94e350a01719e2205145b1386e11bdba77dce442e218692a2ea4269b9ff4b4a62050616ab38001a7d290782255e925b72b4a3b9440615f2f3c1ac63b2db7a1d6a89a544876439925ea75b167a9b82401cd1bfe8c5ae2489edc84b210f5e550d99027fbc06fd3bc8cbef9f805096cc7d38c8ff026473213bcc4d03e67430f241fda1d598ee75c844d4db4ebcff8d70323fbf8b9041fb18f01b2d6c1b0a4941fb182af6e2c56370b53398ac2ec92e8d1e5b9d8723b5ccb6fedde1b7ab09bfb5818cc1ba22ad098f5da1886ad4ed432a5ed5edb0565074ff1b9ca559d304a72a9e6cb45b2dad252b565c8e353c85eae8aa04e2b321f2c26cf923d93035a01acd6c97021e1fa8951fca534ed031a39c485c6a9a857986a183a732ed97908e849d814a50c7c498fd2127715198f9284f0a78c8923f0883c1757bf3f0f61e1937631f953f3f5cdd7e697345342c76d3baa07b5f153cb5a73f8ee6470d1342f433305ba02a15ee443bfd1fcdda5a45aa788ad49bbe57c96cdfca8cdb5af288405cff7530a0d93f6e589fcc68af561efdc9356280f2731d51e059a535007a4ef824057b78c5eceb7bc1247b79a76631b12804d6657ef6589d3f2d937de329eab6d7edd2704ddfcd6fcabbef71ccf75cdeeaf58374cc014f6d77fc35a257b4b5d0fe4c35c140a5efea382ba00b872cf097d23c32a38a5afbf2c566a47b6a293118de9768ab783dc07d664a6789ffd7460df8286221375c2d0d77119991246eda63c9988ecbfb81815906b35a4417ff005bc299773705d9f4bbc5152230013d245df90f2ca0548b0a4c33f9b7c6d0d0b251b72cb790cf106e7eee4a2e1944952bdbcd0903bc883083c8fc4f7f3b755707143858f07eca95dcc4a2df12c32e9460dcb38e19b0c38bd06de0ee21c37bf157be5592dd67f36859c288057a2aec0fb6d915160b1efa1fffc4190f13d32e6c82bb4d51f75d60aeb22dda25711d74aef02a5d7fd5590eac292b29303c5fc2306a48cdb0947bf6a2bcab792aaf4917ae2762a024610312ca617b80b77b2b37447dbb789e7db60612ef1ec19369afaf233677a75ed4861e75d5d14ec87820be74aac9a4ea11962d13d4f7b00d3b0a4acfd3ff63fda2f253daf867dca1d35254b4580ad1d9e0244f3062e550d935bdad19c0811bd3a8c781deb838819624300ab55f00d74c5db68114b1f8acad12cf01854116a3df1ad92fdfcd3c3af23cf76438029a91b849b57902348425f6b61372f738b18dee55976182e269f321386bd0232fef9924a40cd1f1ad998d3867393bfdfd0fe86200803d4f4ca6472a05fbda01bf00c2fa35d4765f21c1aea3dbb18e03a978d6ee980bbdd3d679454e11d2f1e6e9aa80fa2c19931231dc1e93c74e12daadd315c318686f3f6faa1c97c00922acf20166613702f32c6536a06d0594022c4f7014d545ba59238ed60e6882a5d6542100ac0f677a5102b741b50fae374556e2857dc4226954fdc2ecf524fdc4df2b13f1eb0375e57ccb00fd95eb9c880d1d0435057f8186be77c71785b1dfe83982c7a725f13097b598e9bd688b41175455bccd0a6a97c08e424a77aabf2f904b27d72c8d9f86e40c9ff8c11a69c2635d7b214ecf26142083c7927534e79a782765686615662c2cb07107f4762e2972a459eef3a7975647b22d530813837c84c11e2dd6d1d9899e5da1e4bd1dab0661129a6f6417a768f0bd1807ecdf621d121285e476e1aa69e2e3cb1b7989fc6a2eaeb049a460d7a62d5db494a1849c0c875c5399de8b1df17602089e9925e1be7b3e9cecd17bc7d8896f9c045fada73a4b7d837f9544e47449990766c51f732973b049401f01e5c1a3ca06ca93175cd7f986268a04128afeecb3c42e159f1f28d68233df3b3ec5ff69f5bfbd459df477d1dce269b7d28fe06b09a5fce8be321a45e75f50a7837fbc723e01bc37df0516018d85050c8fa13c1cb09a0d19526f7af889e7fe30a56cf58e0ebc277608d5ad98a3d4cd35e4bee894179389c6c40ffa6cd7d155fd01b14494cbc4a6c66041fee34423bbc03307c0801376bf05340ed09297dfbc375a7d03e4b0d5cc4409cf252579ff7530c6335265d1ded492761468c501e05950c01a615150c3c793e3f10ea99bc10324eb24484aaa4bce6d00d8c5f01521335cf550bd057ac2426efdc2927e4ed17c19cae3764561bb17eae941db1d4dc4dc8bb2acabbb27e760e777e75806fe7304d3871fbee9ad38364438b11133687c34c7b6466eb0ea263f6ed643bd443bfa49e61c56495d9c2c898211e4e890741e4aa230e359089f6c29f08047fbb845332b577c096ee27ddf414ce04973e32c06a69d5743d4e6260ca56d0d0257bc1cd8a05a96ecccb0e8b045612335058f12020d54ea1a929c0a687b00ed4a7a63000e4ff916f71e1429bcbd1be2fe7d3d96758f8b83fee196159b41e1a50a268fcf948abd1f21d200358b8df9f953229d6fdec867ceb54e3f33407cb2997f85e775595f0cbf10e273004c29057512b2df6f0d7387f84e54a420f6b55903e17b83e61eabbadbbddf723b3ad85dd4b6e58edd90b94e1f9c3ce3caac5bc0a1b7c37040f006d8812da4b59f4bac997a74d5ae52f0eae1b3d0db48b171a39190b54df27f9cf1da5645ae1589cf2d7b372821a24af1a633a7827376ba47b5906f25bb207048f228e0992e21f00a6ecae2fdfad5487d84d1c240d72f570498bed27161f016f2c05e39c4f67b42388b35dc4a1fcf31b371c6a459bec5f131de632e912d34e78ebc1f681b5a1a7526ee47b6c12632899636817a37782ab81e53c756f89b406310e152eca94e5790de55206f8ed40ba6a7e9d15cf30c7d7430309d3cf87a2bf09b98efcacf8079912402f017b45d71c32010a5617a8fade3d675db1bf7ef462c0ad1f32aee4b2a8191ee76c5705f3e4e7f43d6614875e421be0744a6feb178474ee26ef459cbbdd40d4198d36664fa1b9b390396de50143b0c531be1cf8287844a8d703105cfa1b3268ca0fbb9c9e4d16c77af21eaf1f702186b23f744d3d325b6717e9e65a6ff2b0dbf0320ed1a0feaa33e04b57eded3957b0dadd5bb9d3194f6d6d95c45c30b6390a44c545a363ee1df9de55b2226ad944672560849eaf6f7fa28f2538a6305b5b35d8b0a68ef36d82955b40897bd5df78c5bf2469e7725b587c14328c14c0277acd954f8524802a1b86cbc5073fd47c02b9754053db5239216bfa10985cee5b515a33633d8f95e87b20e0b488bec5cd8c1e36852ef752d024446ff78788504c61aa2c93c1229dab715dbcefe4c494d28901f84610cf0d7c0fcc2a5ba17e79cd70c05a059e4cfbd2a27031589b825a6159b227ed006b75d5bce7f90cc168d3eef33c63cef64cbd58ebf9e6a0aeb008b826fc574ddfadc5994850d6804220485f8cb26d861a52aca7375981bc3ca64d87d9bbdc2354784b415ea3472b4cf43cb9f29104c5240a2b57f3c9f826c523a6b5ddb2735e422d76f884b20254751ee5a0a802e52fb93171760a43cea797222f400582d0df72743ca9a86e29b9f134cb359ce8faa63dab5a16e14fa4a2621dc096facfb6af89cbe4672add4f2730e78f976a00e2ab454bd4fdbbe3947d730743136cd4cef90902919c582b85e548b42d4d853ccbb085ef2eb4d64d2148d06c60de9eb97841f4d1f6e65592b340a1512efaa8d5429fe3282012ea22aae28b423aea2cd683325f367dc6066ae7b85721d533566c5b5aaeeb3e94e01262b966c57a86abdf64403446be85c79f7ed63b3e24c5853d89d157b1cfc959dd99559e02babf7ef33f42f8bb6af9f4baf4976d2876f32cd591837f172ed3cc77960542115ff15338aa01f77253df5ee2e2e98e4cdb2c16f0c1fcdcf2242312d87c5a7d6b2f1867105850975d57656fc5ffc701f3aa820cdc5695098f240c2eba9102852688606d3eccbcbab66a42f9f5d9ce5f59b428633ded4e6bed88c3ba699796d0cf8b6f16c514f65a8ed8f105f3b99a8a258c5bdaa8bc7c82ab234228f48c802004df2bff74942be5320c4380e90c59d55dcf6398c0110789934a163fb64793b1134cb51aa4343a3ec107ddf561e800eca85ae7db7557456f3b2fff5a912ad785a21a4440f534997c2218261db4ec51893d4569d73a40628c9fc092132420a55145838e0a3797b4989dbf7aace3bc226ac39e5f1f8a9f3583ffd63992b002c5e4fcc9a557c9550971c0d3ac8b2f4de67c995e58fae41d6963733aa4522057ee4c2b2a3a921510f7295f2c6ce1af579c41834dd419e8f7163c1a7e578ba99055f21bbff567a7f09ac190b598acfcd244d5bcf3848166eddae4c578d59888b46e2503d26d3a80dc785b5ca295ea1c461ca9267172a829f1e86b83328da757afc14a016fc87328e68fd0ee5882121c2c90d5f53a5ee625377434307fce6300a7e68cdef2c1f621e47733ddedf5c8924473e9a14f05c5ed8d25e4f78bc2f6dbecfef02c48af87b8ef3b6d16f400999ba994d9b076fd347ec70c8ee8d493708f13cd853ee577dbae20f177c728659e257256f45e3517e1cae32b9a970fae0b76d3c158317a2740a5f6b0bbe16654bdccf0951da074e08fbf73d82862a5cc6fe560b130d9697dcf18c53f602e6f7af97468a817d1c133a49e6e15461a55a8d60a67e36e064a26cf7df270af389acdac2aaca563d12ada0a16005c7b46a1dd49ebb3035ad35de27cac62fe0125611e4f25057290f13469c3e7c04f7f60966d87b8f5123272cf31a6716666472f45422fc4c2d814602467441480073fa9910c04170211fc97fd34cf49bb7cfc5025760b9e02fc3f4403d07d422c48505ce5a1af9a3628316c4fd015d2cb3a57fc7e10c490e58d599e2751c153326831169e73a7b4c472f8bc0faa86b208337fe5b31f7ef3f512d9b0bf96f95ccdcbbef1ddc5ebee6cc444fd4ba19fc3339a55cff946dd10502c119b27a7c701c92c8d8a9a9fd257f6a98e358617743ae30ba246231798cf12545a18f55be9dca720345b328553dc5e08b2e823a1b1c6862cac96a8dcd0d344ec6af5b9c12c8dcf27fba2d0fa12b6a2dab62e85ad0281c5ae8322dbbecdce7f7185dae7d46322c505f461d7bbbb62dbdd111c05d303fd78c52e16aee0e4e94d3ff6c46fd70cfc31999c0827afad2aba2314ac7802ad81222a55beef179f1607f82aebcbea7df8a1e269ee8b472ad8391a7e6cb49e41db84adc7991db0e68e41db5d9f7e02cae2b8b075717ab465f0ab42d8c73ca31ff0bb1195c29b68c83b93cf2620eea1a46f55259c7a8166cdf81e7cfa1823b72a0720da9dc9e0609f053b625a4fc181c0eb42505ac52b0e2da5c29d9a10396d56a71f67550f86bf7f320d7ae04d4b7d333b3baa1e5748a8d0df4cc164a0d0e238e2db0a488c6b1f8062af689850b8fcbcfa8fa11d01fd1c4b859af1c02b2fdf8f98f196f2c19849633915fc40fe6cc49deefaeb775a1facd5acf379f8ae03d38814daeae0c9c3dd8bc0532ff8a841b8094dbe0dade848f6a3b9facc3a184d69e569c8f2744add0386abf1211657ede1089dbbc8472412d5867b9cc15f78053dbd6154542a9a115063e5e325173a5dd14762d76349f1d84ed48c54ce19641c6231aa3eb631fdd73447d0e5c666bef3ffde1bfde750a3e51fa2f3707cbfbc71a373d4d5aa97eac167a0eaabd79e81e4d9c8c163ac845f9f3218b8c0ec8f945db736f58ef6af0e50186b9bc9ce037298c7f888a87f15e4d7c844b74d2224b589c16aea40a6438c6f58a2b26805a8ed17236aa72231e91476fa43161703942cdb7e473268e1c944ece7203e15ec124fb4015b728f718b193ba9cdc33d53ab812cb699040850cb30f2092a1b3687d3dd6833af489a540a1f5af29affcdab7f4f64ca0441c106d6b5dab055a4576e20dcacbfdd9e4bce1134d704ed94cacd95be69d9f0a4d378d29f2ea5833e46d9177e02d1273e34890c57ecdd5b543fcd6ab6bbbd5499b2539ef79855fdbd04cc31a84908192d6a4916eedb5fb6181f9dfc9783f92d278fc3a8a046069d6dcdf04e820ad9f4752b09bd853ad4c41bbe45a2da712b2ca19fdeaf353e81bde4d09a400fd5b293e046b8dbb6f2ac0302e3fac1fcbbc06c663e842c0ef96020c541e093c9ce1e2ecd8be9f8f21f546556c442db02016ace0bbca86a8040626ba544357bd6a716db3f03471c5630ae7250ef4f326b6914cb74ce5654cc6f065c40af7812590b8e220f137b8db6f15f065d4a867fa3f4dff46df70e7580b0500d09007a653cbd6f5c77096ec306c802516a1bac9fa5296cd95de9380655b200cb8d35929bd9c6e25651a21f676d254c24149b01427301fba1c033ef7da16ce20a6a2d74e654e6a8d2b006026050249b0e235bb2131d6700c947ee9ab93379d9637040d64d9fe9ac2affa6cdba2fc983be722f88f7e8dc2</script> <div class="hbe hbe-content"> <div class="hbe hbe-input hbe-input-default"> <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass"> <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass"> <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span> </label> </div> </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html"><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOP</summary>
<category term="量化交易" scheme="http://luhao.wiki/categories/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
<category term="量化交易" scheme="http://luhao.wiki/categories/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
</entry>
<entry>
<title>日志:2023年11月</title>
<link href="http://luhao.wiki/posts/2023-11/"/>
<id>http://luhao.wiki/posts/2023-11/</id>
<published>2023-11-01T18:05:53.000Z</published>
<updated>2024-03-01T05:33:17.408Z</updated>
<content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <script id="hbeData" type="hbeData" data-hmacdigest="7cbf118ad0e6f9587ccc4672f478e6ceee77711202396680cd894c3e41e6629b">b3dc07a81f6459d120ce338ccca55046116de9af7135eccfa9c61a82756948fc80f892c477e2c8b4f49f1e28d8dc47ae33730d80f767fde161c078e95f5e43f85de758fd4c096799f9d13ad814c1721eccc7799ffc0b0ed8f3323bac374edb4542d4b5ecc52709d59318a85c2a0f698dfa8ed10a11c0c274da6eae32926a611b121fce93d73f6b752644fc68763d978715773fd1b7e6237849b16198cd5c0c5b15bdd5cd8e7e45e8eca0808e869012976a2883cfe76dbff330bb4ff1ebdd04513d1236babe8023bbe1694b45decdb6c8db6cea8dec71e2c7a6a6cd817e6e73052da845f197fdb8f843dc73242b78366ec81ab9ba3e9bf95a12bff3b57eb410246f16ed1cd6d8bfcbba40e7e4682e3528993029a8644e9b67abe062aa8ffe4306fc4f921bd6a091bf4a5bf6659013a45484c289b524d7903d9d5d0ec5d959c29a63a9c08018d7f0ae135aeadf4acdd2b9afc60f40a58862a6fea6c0626ee4a280f88eaacab2d26b69861df931ba6ef033e2fb668eb9400ae60c0942e2f96f3a385f3efd7bb72f186deec0e2589643897cea705f8fc08db94bc42e514e78af72e8b31d09e55ee65414088485eced1fbd231ac522f01371264b3117c5fedb9880f701f9a86a1bb6e937612cea1b0a452f5a1f53e25920da26f59aafca162cc240e111b607181e030cc15d71c672acbaf0d79c8f3a3ff126eec8474dc627e0fd0878320fd7694394be3c982f0a5da5d3b7360480c0e56e4a7ec05e623703dfc939afd5462ba28880385ef8ed0c25cb3eab4b474ca83cc99f29fc401e6208f35586f0d4d60e109f3d9a50b019506b4a7060b7ffef86a59ba815899fa3630ddca779f4f93dcf105aa747254d494a84d4b2262366bea5fae7e0c04e3f77fccb7ff1ea4418911eefe2d2eeca9774c17a4cec9dd55ba9b54e83890fc10a1072d598f6876742170db759c622b43cc111dcc27daa5afece0ba63c0b067ccb4742a2cec7f5a607f17265f0c41ecc926366be95dbd865870584193d9e549e251d42eb0578ad0fe9fa13ccae1b5e8289a8e060ad611f0eb1882a225a108ff2afc8cbe826158431b40de642de32408cb7dcc6448452ea45371d2e4f05e8b2a68cebaea2bd470aef7e655603642c7970198fb5c0eb9fe526a310749a26b810671133919b2b8574e7f83b2a94aa266d5669680c946479b79369494fb1c7519cb6954120adbe40eaeb1f038edea0c28506edffaab1303c9569d8f0b814b4777ebdbe5a37d63ca59d94985f7aab11e90527cfe43074af4c903e98561a6f39e8930dca543dd9b3df0ca9e95de5f35a095cc9d21fed4732a10365e75fd7f308984ddba97123d47dafa7bb779c59439a4e7b3e8ea9b622187e0d56c4378d0b94ef5bfdfa82c93de92780be970a0bedc4e62e5a40fbba68c3da3f4cc5a93fe14dcf0318f96152a7c900caa590623443382efd44dcd2657b15207c066e9af0ada28ae072a7c91b74b63b5b1c0e7c3206050fafc992d66984ce34d87fabf8d89892f006623779b8dce14de9d0f26c779c517b53913630963c2eb718f4edf058159f1d3e3e6073390e1a69e68fcbf93e687de96a44735597e788ef0a5f7ba6abba0d8e1ad2c21d544dd1c249980201b02c098369857310767b4624aaffd1d74925991a7d8be859429ec311374da864f37135144ede9a3da8af642202b33f1cc4f57300849f15624a5a849912b3c9930c4217af34159374166841393e368f2a2c3d551d41e02f52c5c11e21aa8e6cfc5b1cf976c451c832cc78a0d357b16173660127f9bf2ad1a6932249bdb9d5bb44b0a6517f5b62d888e7031cb4c66676b337397b444edd334d6fd53d938adb64d95856c2df12d62433ca777a573dbdca3f10a5a8643d9425194e388a85002b95683aa94b7648a6a2770534b9c6ea47ae802d0ad45c448acef579c52fc62d760e3f07da3ee8705b8a9091c583a14686abb13efb32a388703b9619efa190534e4da50e85db3035e8f870dcda18eae9515cfae2fc1fc30c8e10bdc758581aa6927075793111d6d287ef5616bd516d9b0438ce646c04fec9ed2f3da2566f8819e4c3949f7299b40d3b0bad810fe5b638bbba55832396ff8877f327d22c9bb8004df56c4f7a71eb2fb45b6342120c11902cd4509ac4700ba646ef2b3b1bbda852686bfff90c520cd693e5bff4bf1871fed4f75068144e9a7073d5aaff143879de5e5eb5ad41d6cb450450cf72374ef316b45fbc3fec50a0669f3590c38822d5f587fb11360ba76b9a53177cec356a9b8ae8454663da72442e9d091faf69661e5e5a3434059ed35c211646a559ff96f070299a40fb607c9620eef53dc6c387aad7ab9fff4b1effe2c5ba0f19f33703ff3bb737a5f0297a591aed80112aa925fa7e3da8880a46eee943b22277c45fa39ee72f4b11c0e12a932a210a203603abe97de62f07dfb23a32d5ff24315b2bf7c0fa2d295d818a88b64670ed5113deab7248dccbc19c6c80da13bd8a740a5ff06dd195ae6a5bcc2cab97ddd9eaea84d4740feeffd7513d85d37fb5600e78d298f05c22d4c62147a7d5e4bed8d07dcf5ccca91844b515fb0ecc763b37e029648aa4a1c2481e81c50a3dfbb23da18536cd9ab0b52fcb02462edecd94617dd0e35da4829e277a93724e8065782057e444acf67d393ceafb4a018b23e9491215aa611926f644cd3e0b1686e3cec067391195cd6cf0a7137f9daca16dfe63b8aeb933ee9b85788f92a300b6286409486d6abe37786e1dcca6a0972ad42b852db2de6de059305bbe83c9676fa1b8696cb19e5e07f67a9c7ae50867939da8a2b7a7e6cba12179227f4d77be33afa861b1d9fdcd2f946f8148cc617b52e8fdfb48dba05b695639528e4d84ba41863ae01052ffc2dda4d4b38a3ff91d0fac0a7b338550c2b1df6e2d9cc510231045c57e93bbddd6b1af06a713cf8dcc6092ee77ff11aefd582a68c003bf8a2befbe6ff5a23a5aefbc24d35ffd93ff02057eee22cab81806911845ee7ee3026986f0c9321ccf3d310bb96f744293a342507efaa914af591d57906bd13f560799d1ee7dcb63f49e54f7287609420d47108ddd28db331d786accbc0d88131becd4f45fb139f5c59a88baa5cd39922d3aecb03acec7df2bb2a23b76469f53e7321a4b57f793991c1241a3dfdd7f62c35edb025e184d40b242eb157d6f2a1499df04525824f2703913de359322314e442d111b7943e185c67d15279d470ea1de81a87af6e582853c4148ca7d3d0f445b99e6df72e41aa5ded909868ec4c19581e57f41e3914b6cdf8ceea74ecd9fbaae4250489669e15daeff789c6bcd3283958421623a395eefdb6861f050bc2c038a67d095f8dc194f67ec798002e415789b596af2cb818d4a9228796f908b0a9cbc5989da894aa890e58b33b8d90985faf7fa90fd37327abde5ae619b8a530373fa13b2b96b96c9118ba38799fa6858fcebbf1bbbf67d95ce3c21cf31f2c9d7c6afd0a65d8870a74b1404c5051b4f7ba7797346e711b4d0ec8952eb66cbd857954173279c1a4597ff35f1f572bab6b78834905399656d40a7c46d0f522643a0a7e9f9ceda637b026d161b78e39e2325efa25202ac214039708ee7457ff23df036259185b63e018b58122d564bcedd1f596c179a49079f7ea636a9333ad965e6908dfeed9210f920e55b094d07cb0728944ded731fe3db54c16a20d3dbcda5c4f177ffa5209905889b69607db9e8d0e7ba7ecfcd576fab9a717daf2205318646fcc56329247785c8da4c4315e6706a72c705083569787f5193cfab1c9fe0d7e16da2a1536361edd53016b66db0e99767edcdd039a44362735d3b0dc11a3b569f2b05cc1a39343ca16ff9beedf8b32f994d9b2c29f7a5f38fe98c709ded2541296b7b55336edeafaf32be246a774790065253ca3e6c06445deb5d710703c4608242817664614897ea9929948b0a8762139789b2d649206c64af2b3763017dac97afd001a2590f6feee0fa150b314ab6157088a48f819c0ca172262496b4319a6b65934bc08cd4b4ffe73783c9edbbf6493059af4d3c1eef19a2b7bfe186e813abf2e8fa66f86cc8623930117a8b4b7aae0b40477327aa84f4183c4e89796091dcbec3123629852a3cfc6401a62d54b397a58689056d5644b7879d460b7c4ec8127ede7fe606f547d798a912f2dd4f12e3cb66d67638d9b196034b5c2000b0795d6e69bbfd43d588e5fa55424e8d84f7420571987ed85d8ec7ff6ebd306d8445b6cfa915e74fb2eb45b7ec6a9696e2badf9e6c9da6d076b6f8990c8620b4eb14e306656f012afc0d023e6c712e08090d6d76ace02f0ec8e87952faebe339d85b4ce5ca557b507eaa0affd9cf847800eb53c496e8eb48ada73ad4c394757e0ccc5e58304ecc8c2c558dc8e398e1a605d10640e948f4439e171f3e90a10948010504e03690dc4b600a5c3172010ed8638c22ec9c11be45be3cf433a6beded53ad09d468bd5c165d17b868b231563e2176ada8f01a6206c43b4e4221f8e8f5e9fe8d59779f293064313032bb88512f61866088f141675837774e69e3481d93bae69d461d29b61f255345f3b6ce97113baff07fdf676308ab1cf094eed387a0dbc109d3ad39628cfc2692add385e077cae2fcdc2d72a6913fa7eac4dcd7a8d837aede621aeddf26718710eaee9857672e3ff55e82aadf1098ff553656e4fb658f28bd02946e92735d6886db90e015bda10e5d516cc132070944d16725746c45c6409ce0566f99e38007e97f079c3b51c6b9ef82ce547df437c2f9565d3a0566884add6d05f9a48495b020af6821b1469e2238dcf84af12a8aa85e0dfd6c318f28a6f0bc2f6d44b1b11918fabd9257628071cc27b7138753805b679a462af7988878ae0fcad6dbc73d7ca118b8711b5c0c4a35ffad9af382fca0e2360e53c96671130fe9df3aa2091084c1562c0383582822dc1378369c812829d13f664f712ca75eafa1b7a0d58e556b9704e075e59e04acf2991612dc327dae929989bf6f9173274aa5c04a4084304456cb87c79efb80e26c7f3168da956054ea72661577ca080436f1abd4579587b8495c3b0134675187bd92dbf1390d8c86dc3279ab6d8aa6abdace711160e49a931e72b7f93c2f652fd2df2b8cbf5f82d32b6d410005fa3b762f81f0cbe63c87fb588d7ca1ea51996ffc7ad8840f666d408bb4b42ccc2e3d8bdbebb2e22edc73ad0529b488fb0c7293b7b6c8e03a14eb43d4a3491c76a5bdad00139c31ff222df79e38acd0dc19c85fd500cca9df1839477b52b9afa813397d798f2bde2f01e9b3028f49d28eb10c65def56d5f55ee77215f2b42ffc8dbb0deaffee77f62033a4e1676e41426cf862ff7a944f86352ca2c01f09b94b4cd5d0015d8e92310391fbee1ab8090fc692b24a4f50c1da46c3e3201ac671af6652c2a041638723bc589da0f86b8c77f8f7b1e774e272a36637aba293cb566cb3b88da12b331f048a774cf6542ba626f5fa8bfcc9083caba17e617487cc02c7bf36481d9fe22181eabc9466d68ff5008500e516bc5d7cd0a121e9a871481fbc19718b7e8753fa4d970999843f4ed11b09bef81553f278cb168b1f74121be7cef677bfed112770af728d8447d2d3bfeda9139027459d532e3895b201fa683dcf39fbe26765fa0f0d01685646404e01e583e4c42b09af25b35c907363cb30c9491140d049e5c6e3c2c738728f3c60e3d92bdfce4e09c8af4919090112b3531b3ca91d1898cb0eda16962869c051b5d7d2221a5b6e8ed3a4bad7296973791a491b0fb8f215323ad3d75feb3ccba6d30d2976083d9a13f2a09b25b6f776a32df601935da832e8e72d30196c984e6159bbe2081856ab6c69ccc059b4443d71d8695b917fe4ced80776c175a33911cf26539fc7975e00861bc33fa6ff5cc9474e185ac6dc43be103dfe057c07a3198873a96243be09d5aadb9db9258e19d14b25dad4a7111fcd901f71b26395ed7aea3aec00d59644990a7f57b117080115bec02dc7b59c49c7bbe95337d7909d742133f52381bda06af811306206d28a71bfa9658b41dc619bff64ef4673818bbca1cd9d4e0ac4fb6c56683ecb9d2ae4cd0b4efb88566bd8517428e1ef6c866b7e2d35a92e0f993530d0254ecdd56847f4e301ba4466db9194cc457b6342b8e7d2163940757002893a2b158f15249c0e3b2588a2b614f9b624a6652884f644b10124087ce2c8ee1c3f255943fefa03a1b625eca4e7d253bb7dd0b1af69e833b0b5f47bd5493251687822e549f1078c07f56e9f24d4c7dcceefd9fdeec3396ed384182022543409d4c19e5e75c81b894d96e19208c61fb8543d0a17daad7e9f039fe028b929322fc835e2a42a5f11a0c527f1b7227e1b099be74a9130d2845b9efe58641ec4e2e464d3fe5e86b361f734255a544715e1be6f497147cb09ec363fb429693cf135bf53f08b2c0219e3f9b8456c570418a4ede7b75f8818be2e82b06055f1761a1ceacd69e597374e475930d8e0bfb0ddbecfc390b2179a033336262deb1f6596d9a626ef12650242db6a8c0c53918e1979f70128a3b1293b7ff0bffe833b7ea81316d81fbfe57d0991cf2ebfcd764ca0fae076313338fad6394b15dc3dadc243f7cd1353d582c85827039c75c2ce20b0e251745ea499fc949591f5f3334f0a51344e4f24289e02c9bf2dc8353a83a5f9e8bd22e23498d3b2c056b89d94a51cbad71a45cf4921f694ba6678504cc4559098894fb93dc7a7ea45f98111a9a901357a8f63e99db283a6c0b66acd86866866259ff2e92fdb1e6df5f245da8ed74d6a665974f040a698dd5dd3dbe434b9d424abff2eb91bf10d1f92fa1e3fce4e2ffa8a44e1aa65e22ec140fc88cdcfca428a65d7d033c12d44a233e6b5933a67c77b66bc1e0bda8494e441b3dd13408db37e837378b2dff08aeb464af3ba908d896f56fe2b5c81572b34966ae26b68c42bfdcba79d76a3fe567be1d40560fb2a64c158cd661dad94c81f24e3b1ce2c14ee96eb3d8a588a480e58c9d48f6a9f556420de2b78326a59dce0783fef7337c18575e8d76501dabfd0ff986cd5f24ab5379eb54b35d2ce4783f919734148b2cfa7bcefdc86a2f26f9062fb673496b8ee2f1feac7f6918f4d54a5d2d171be11ffad887efee26d9e1c6ef17e289d314e630e7df257709c6359c7fd37fd3d8ec49533e0ac236a7fd2aa69319a5f72bb21f19b1300822ab23443acc51cf0b4dbb392af842b112529cf175cfb39a404963634a20789dacf69745ea03b9b422af0a97bf40460f50187</script> <div class="hbe hbe-content"> <div class="hbe hbe-input hbe-input-default"> <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass"> <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass"> <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span> </label> </div> </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html"><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOP</summary>
<category term="个人日志" scheme="http://luhao.wiki/categories/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/"/>
<category term="个人日志" scheme="http://luhao.wiki/categories/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/"/>
</entry>
<entry>
<title>Nova Engine</title>
<link href="http://luhao.wiki/posts/nova/"/>
<id>http://luhao.wiki/posts/nova/</id>
<published>2023-10-30T15:43:32.000Z</published>
<updated>2024-03-01T05:33:17.400Z</updated>
<content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <script id="hbeData" type="hbeData" data-hmacdigest="a639c100e59a6152cf17f7143698778ed7b4c81623282fa14a3a16f7a81cd308">98b8a949c3f5e1d69458982c9c25fd5a277e65ba1cda03eb6d2ff14892b14fd18dfc2abea7d0dda28454dd441dfcfddf5256ea3ef416f3008451c8ec4c947f50581d8adf8a746cad45debbef0d23ccb525a4257da90ff423f8ca55ce004ca272b74a907fe0ff56e7528111a01a18638d4d83151023295ba2a119975b827cc19ac0bc0848395477022d70d26ac953938d1533b93399aaaa586da713023b7b2b9bcd360cd6723f66b6505843d232111e629554b5730f71cbead394b1a4a2e2ac0b4635f8c31e557bcbe3615ebd7659557debc984516eece166fa2bb07e6096b0424c8e543f1b340c98c20a46f00e12d570b47f3cb47c5a297be2e7c5972127b16d08653009db4bc3c1257c4dc59d60b0072a6fe996f410fd5c002abd1736b32fab0afc00981d40fe487ebc25e0b98c80d078603dda4d2d6bb38742a37cb8e23f706c0cfc7d432bc51409a7a0c886cc431fbdfb3656fe46d9050d4e3994854dcfe5d588f2f97ad74e90b1b7a923f3be92cd81f9903101995e557fcd1b509ef4ec6e4c1a80cf9c2107d244039328b27a46bc8b323ff66049e09bdcac86466fa26e2f86929e2553b5fa5cba6d7efdd0264945c216672e7e6b66bbf7e3e4896bfb31224514935a6a7431cfd7bb00ed7b11bc116202520d192c85cf5d842cc8916ee3b4e5945146e7138d29debddaab33ebe506905103eadc7ccdc104217abbab3a2263a8c8af83f3bdc97128c4ba30b0dae7d7dc849f4978751e8fb1fa4aea07c18f472369c7a1cd16185d09e0a8b0070b381a8cca5c4cac2f257056cd89b3a05dfd0814f8d3e9e7e33331941ce42511d6ef021837037d7ee56388771b9ac46dca33d117f41cc1f4463e9ad718c6cdd72d1ad9d9731fd0c64c888121126104f5a68ba9b9909e1e685745dd39e3989f4c4323576fa2d9ba6f041391b62fb2dda65b674193a9caddad6bb36be5f32ea2a0b295792ce6e88e71448536ca06ae6231e4003f0265bbc97fa58c7d3be1440b73d20aedf631ce798abfa145562c9419b9f5354f4dcc9a2c8313bf17a0e2055f76cd5bec1b393a14ec04db0a52a96eca4645d3d230322dc0b60818c10ea1dcbdb1de4d599fb7310a880523f773dff70669b4c252f1da1880f8a2fd18eaaea212e3c061713893d77e04abae033d6cc45c207be76cf70aafb4651ab5bf11b898cae922d71e240874d95af9b4b3fcdb4ebc71e0c388fb49c785d244065bf381b9a58cc5b7fb81e97663c20d9a233f28c9f14fb5e6d3d6e38b3f74991565dfcbd9068fc8ee8f2ea120e28c8fce730a6297c8c34fb0100ff587902adacdf9d3fff4dbac341dafa0fdcb8f8245def639f0403e1c95dd7cedfdd3d83148eed6a719bab33bb668e25a3b2c5488d62aa8d60a0ebc91698024f97d5a66a1896111f5b4eec3c87cbc3ea45c50923dc4329d567fc10a16dc5c79c3ed9b036afab408067e0dab6245f91e3c667100c6654779a5fcf13dd09944be78522ee20396bba575a8c81f2bb65d2812bddfc46b7bea2af5329f749a4f21b3bbbe182b12324104605d4c8eaa0f69fe33b3d0687bf1893a06e3bd28ba092f808ebb484a89d1fe78cbb2b335b8c41ef7db341c8ac715cab721ab37ce32b587f5385f9b930ac9e27133d5fed5c49067f9a2d6e6d1dc46994f20c26aff1d0fe82852f0555da8d972c404b9f2b0e802bf548a6e126ca7abad02eb22efb13c2d9e4a2ce26c2d8296fe3ee64165ded9fbf2aba4c35ee79beaf732532427f9ec103db9c5e8aca5e9b25066766020702c00d83f7c2a2e489f3f3f52b4191a7a7d3a91a15643774fe89bd8e6db78dfadcb405cfe51b48be0323aba941a9a49113111a63bcfa81a3aac36270e9e9ce8ad76fd8e7778fe422254fe122e9f8398dd030b9056b92f68208456021f727a0fd6c2bc885205527c71d0b06e24a594c146bdcace19c280d5f167020f0729f847ff474ec573794ffc682429cf8a8c32ed20f3e26ebc4a8545a0472eff5de61bbfd699b38facf320f079fae4445b5ba7504489d9f0f6ad272cc00f1ab1a8fc9655c6d278eb58bccf458bc66adf8867a64e22e90880f08ed400f8d7c17e6b694d027eecd081dd9a95c706006010ea64e6f702ad82f38e487c0ce6fe94a94342e712e1908fe575615f457f938ceae07d2401080d34e14a8a86e9485f94ab86920bc4586f83a2321d6ebe66f967c2fb66d5c70cd811aa2adda4ee0a78fde29c0cb62651528eb1667ff21cbb31e2aa0540fd5fcc6e2e16210e3899e776847505870652b414ba7c39120322eab9bbc813ef5b1a7fdaa5b166cd8417fe99f525dbe0226e5775fb6a7ee3924d02cbdd25976cf16fd2d6ec512b3d1266b3c415664174487a8fcd149b34ff6a301b30b59bff9e1ce9d12bc15716060bd9e0a462ab26f93419cb7e67f3f3627d6e134bb879c3eec1a567dd3a05faf991404a176f5bfc2fee949ea2e5c661dba1762d8b388272630c47f052a2049d27163ca5644c9e34c44cbd1930760f54dba94d30e40c6a324ca032788125a952b2ca3e015bbee2f1697d97f7d1a48462bcdcc51b0563b502155fc14de5d2f72e2a03bf4cccffb734f067ec14c37f5831a04cc401adef709da660b8a06d1563fbdd87f849d0784afcaa7817856cd504d6934d1d3affeee40b405ca4dd8527584fea0733ef1c7468fb4e7b12e2674e6acc31f115b47ed03fb2b80566e652507d35f2e1db77704b33821e7caa952a6d8973417113ac1cc625eb96e4310a7f512796c4d4a9a06d579a3026353dab195c98c55302714cd3d63f1a067cb8b8cf53946a1ac1de06f8310c382672c8656f95b4d91efa54ec62ccae180289f2e3071c3106447ed5e463fab2d6d767503f36e45f53401acc6b0dd9c591314d7ab2aafbeb26e3e87b7c187a27e8ef1910853c747ccebad2875a12ab831f21d6ecd9edce1b7ef6de136353ed77986df45e1234f0e0f25e61f0723259493ba08ef477c1a80fa2b00ceac9e8788021e171fcb112bc40a2dd0a9f60a1ddce482349225ecb53b46d5723332d2bc934c483c30af2b2facce2acbb55d6a868568a178ff8d855dbbe3321d7667a0525e6ec80cd56fa196cba9a17f34989bc66610c9dc94079bbcbf45b58b7fc7bc650a30e1187edabec79ff596ae459b34016470e3d9a0881e5f12e279bc02f39730cb6b00ebb14622f006efd63269d03d44c8c4c7974aa30aa3a28b2fab6d6bcc88a3ea891cdfd071aa3ca99a0d5d69b70b9016562d5378f142ed07a086dee2ab3a0dc22c48d4b3d0a3df51a5c6fd54e024a72349b7478ffb93fb4efd01a1650eef8531533b51daba00752411c2df1b1bfe874f7499fd82ada82849fa5cf21e34e333b59e6d8b2d73f45c8fc15abdcff54fd894fb6f6cb71b360b2582fdf231ec24b2f1a6d4b513f76c7ffe56b0f6adeb095946c3011382f7f26c63c00e8a0de5a57e4e9db0341aecfa64e782de8c865878b26bce19517eb4caf19c92f5bcfedbb5f0d83abcedd657c5a2eb1edd0bc00a66afaf7f742de1d10663aab64d87351d29fd6dcc9619e8c23410e72570b413a6652395795ba70684b03a7945ca2191f428b73b2a81883cad269c321ae6e7d16200748fe3a814e54ed0bcac8b900e75ec9fc4d16a37cdcd38b61bbf823964dd590da92f043e444e56eb09e4c786e77ec57c390c80d1fc0d98875b7858e5bf6075b965f720495aaf147ca97dd70987d5e7f1944388548a317ca562a57fa4836305e341a0290c791af398afe160cf1941775658ea653ef506ebaf391249c44e8d6e60eb1b7454aef378f510f62a234d97dea7bb5f23127f3aaff9d54613e4cb6f9ec228923d3226242ea096f32bb88b70b68b0387111f49db2c2647ee2bdbcc74edd8ad1e1bfb6d6e9af3e58bdaa9bfbc0840c9036d9c3228875a0e85a6bd53fa23466acc580cccfa0c7d5ceba672d231e987df6b539703ee2db348d8d9b4d31120341a5540108e125b2e4d5709770757174d08b7bf64cceda2897bc3c70cac7affba939ce1195ce3e12703bff54ae8d5ecb8e2013ca4e33d262fcb4ebe6e561257dec64408b194303d4106f3af4e5f9a50436bee606935f6a0d17449536720b625c4668e251ed18418919829d4d3f893a49c26ed00ee2ada578b399846329ea143235293ece9d94e4924d047fd3e04d23a894dfe3df9196bbb59ef4a8c8888e9ce8c08c1acce553a61dd75af230bec4b3c751cf75f304da1df356d565f092c3ebe31cf5c23eaa05c4151e316a0b85c5c5483e9f783b5b4812858a66f57b9911330876f75a585edc6615ddffed7de51a9903ff080210e538560bbbec7098138760af90166ff14eb1b47b5493c524a11b1d96c9522f14d2456a824edaffd1e5cc351ec4802b037b8a70890723e3ace88a040e6478fb9d9354052336e46ccba1dec475932816eddded95a7eb1f652667542d2996826a481424acaa26b82e5d82da63afefb4c1d71cefb3eb67e73fe9d1d67e0190eb1625eb8c01de4a0d6cb35ce10aba4a5fb0facb308d772bd34830233c307b01c4bd65a706eac26f793d4691ae58826e131b6714eb6e7dd7beabba90f13689d531e03bd5b909d745ebb133b1a22275481eb43265e7f2ad1af0837efb3ac09655159e3762d1f3b2aa47ab05624c48970c8cb068429a95970f537f20843ea29ca2fef63907ee20e2e2c40b3fb003f555802721e8cbfe5afb6a6d71aa8f5bf42a2af81d0295c48b701a85f1f1fc3313bda07cca7551a17893419527eed6db33a4ae153d5dedcce31986dc020cb8cd60f5225174bf966183de6aa0f7c39d8c33e1705ac5b8db1b976af6df34365039b38ce959fffcbfc9c5c8ef3e2a7386646610f419128188cf90707178bf1615cef1b00ecad2a5f9d6cdaf8e92e3a63c646cf69f99202dedb60cd75df52aa874ece68dc49c0668d64878a55a3de6b39a213442e6bd766876b5fad4306a682d15e51b1f5c1c366a7cb8dd48d1d5c63565b86ba79551a23a59948874b95d7c48005206c2501b4d2c4b8f88b0bbe664c03e09a128737db348bdcc5f8e5a28f21948907d3a49a9c5b7d8f69211ce2a81c8c036431cabc954940944f9609cc2de0b05c1eb5129c3564226ba81bfe9fd627e7f611ba451956f3ad05f9291e81bba015baf72708ecc1114d738a37853bdf5c3595a14bbc97258f7125aa04eb0226b7febf5c94e0fc68dc09eace80130cc77168209eec4c71f07c8f57473a1cbde29619715de8401c668f626f773160440898243e8dd63923f7b8e7af04715990164e8f1d8a5f965281677af6350cb3e843d1c022b73387e735ba3ae2618351f96161b7591248a5c97f84fad91f9467780daf07c45eecc098e7929f2ff162848fb4022a79a5d4a39db2c76164a0185aed837857257738ea8714828c6688d85632dd9837d0e1ac904da4d5d74dd2e737ebe1fb835416467ed732bdbd8cea907a0aea3a32e1001da086fb411a866b0bf2815a7d96899dbf802191fbe4be1de30babc3b4f8cce2d72d64e89ef69385c8264e35d9712dabeaeb7f40e71e79ec2a281684bc6abc9007887ea1799850426d2d6eacd01f12b0f6ebb601cf50e95e717aad0d70976bf28dd9251c0c5154beb417245983f38a2c61bf57a3e9d351879e70a6383438f151eb078cf22c71b03baf7db9cc5ffe3c34721fb9aa186d7762ffd48b9b17b3680e0aa8d482bb6b6978ca0fe183579b7b3ef73d010a529243d1decfe59c442efcfc023a8f0ef426fc011bf13e9035a24266f6da832f98adb9cecc7ba76843b048b30a39963a10a83743bc4a8e630276be5d09b7dcb3d0b2822bc37ff683b50a3f5d13d0e49063e5ff9038bb44378efd6d9f77d80ea9cfedab0489c793550de7f480acfffba9983af00f2e774a6be5f2bae994ea9f8f1ed0fe345af9e4f68e3cd70725348668047c8173659a43bdd1faacbc471f52d3959d8cd299a89ef21e2e3ab474ad442f4952e158844921c7d81b041a703d4f6d92c64b3087971a588b16e40ba15ce2fc6e7d46e91b3dd9061637250b2713ba41e36aeda7697573fd729f9698eef4f7bbc2d73374ad8da64aa27a925b58236281a1bb99e038f1621bd9f8443585d16f1cd2b035c6af8b70c650518da5906b86afd5b402d62e71b0d37f0d5e2fa9f27f8094f070b1aef79948bea47c5c88e20dd01a41b7ce76df171cc655791794948e868fd9abb7f2ce0030fb467ae810c2c345863fa0fbf6ab3167a03370d1b4c3a689fc81e9b33840281bcf8884af3cf379ba9fc3c7d8268bbe3438301aae439fb503814b801b929542b739d3312c095109d0254ea10f7e363004e736d4f8d89ab07d048b26e9b297b838ade71877fcbb90991ee3b820de8a374edb520af7e30f97a8cacd6bf1c196c32f242b504122d2ce75c78ebd265cf77cb631ea09d9b83b6922dab5549462b9481343abc8e57ed28baa7b22a3e583e280b992e52f3a40cd5040221597f33ac7ca3ab91f9064c7cea9aae3051ff005ef9ba9c92866de7d24419c1ae10de8c61bca8e80793230be7f40c972e3f1029cea465bd32c67a76f49ac6f44e7b72e4aee529bab0003a0d492abff3be61463e5cba22536558a0db01f180c787a63de28d920f0ee7de795a564b0e9c9a2d2ae9f3e5da36c7d9f0bfa884b1dd024cce9e9497b69bcfb5a69f3a6f773d5d3f83cf61f2b501296f488f3fadc890fdc17207a41880a5b2d4000b9793b88593ab6d2cf0dce2bcba6a33014483e416c90751ceb0388440102dbfb1e4244b91a70f13a28874fd42b28ab70124987557e292a54beb0755f105fd91ded78987a3af2a2d82bbe96f28fc914d219027555829469dc0249e2fa74d5e113f5e1c4fe0b057a2a7278a29c5fed2eea2c6157e7405f2169b9f8810044c814502de9d8740386ffe649278a55feaf81bbae6d19004b284980591871f816c105bf7bbc699cc749a4a0fd659bdff6e42e076f398af4718a82c6bec21ffaf016d1b5b1603e5edafce2e921bd91c14cadfb60c0c92bc7dfa3b495ee280e654a55dd89c8c320d5fb67d456002a7d233e916c15104455227d13a4d0af61f1e2f34f323b63a0ee72d3fba427aae19f46d19732375b113feddbaf7cc2e58da8b9df046bbc71ee0585add1a5e92332f9c26653af9f35622712327c37b3b492a2b2222f67d79bb73ea863f078b88ac206efcaf02aeb0d5b17563162a395c2d603bdc84ba843936c7d167d1069dc26a52f6fb989b64acdc3764635bcf92bef379a0d1bf097337a9ea8183fec05ebaf8c78b60f8beb1643883942574f4c6c8c41140734692f83b4a73601405ed30154126bf785acaf0f8c18d7867ff37ddc8005492ab05cc86671ff7ed129b8bc912ddeb0eb1e6683a1a75f28297276ff2769ece81592e91c1f61eec801eade17ec3ff13db390b737a76c363045cc8317bd79eed503f003e437dea69fc2db39b0c001290b90afab13460ce98a9883ea54b53b9d7a5881f2af028d2fa3e646283002278044e54a44d1202538ef08c18a075cc7f2b5b05b4022b2edf22c7be01e3664a5fd00b6d2c7c90f06648f8ae2b9acd1e51ac22035541d48e9c155038cda6006a0d8a872c20e0259a7cd819f665051a53398c93845668e90b77072228cbbcaa63f33e159f69c12b64218fb1d151d67db47620d7a062f396bd43724aeb7ad940c7135c7e4b25584d3b44103ea75710ebe007b6ce996d3eb8ef9f23292395e2b4776efb1780c62fbb079612a3096211bea0ac99113fe50e26738db59c1af3b89769089fe9c61b8be06707d3aac46bb50118c5ad8a27b78f48fb1fbf980501215f357e8bdae43004917be829db72af2c562abf707bf777d6797347c7fe9ad176696d91157bdf743fc30e976970609f0525ad2a763f5c929c0395d4a55cb443040845bb525430413edca528fea2806b091851e7b60a4c25b7e73915e13fbc06a1a3a5b4e539ef7db686d4cc3e5c6a745571776a81a8d3b97c92b6e1bf83b6f99403630c588812d40ca35d17453703493cdda1227945e0b9e6e0d0702033ade92e1139d99596fb5a230d2055c65770bd0957ee5c58c8dbdbbb0623553907b50878808f7273c023d754b4d830b2d42f5481f665ce202eaa5934f93e443d2ea4c17ea5e91afd348caafc8fd22c2f12e16e60e483688dbb04c10051823fab049cc2f8da194e39de0cf9957a990fb0858000b452e6192a71e1314dca0cf373faf673336615864159c5d908acfb4a1e178f3d74a71a2edf4811cd1c73e6c8c4901231790391e3e3fac17a7fae830082b050db56a6ab7e2a2eed2fa987cc4007e2df7a4a76f2e43070fea66cf09773bda8dd2f4eda03ce0c3f1a06686df1ccc1342b8706071ec361f5d157c37b1346641a7ee9f55b7d4be46f75c1114ba35d2b7ab27db7b9b980c0dd63b4e3553b55dc4f82cc71faf9a881b4ec04392db94ab4a555304a715ff03add3963ff698e3b76d2c6186aa70ad96886cb24193c84db408e384ca00e24e0987a855751baba6534e97ba245fde3014a81692fc167d45f73555eba58c5a6c0649376843c2ee5281d99e941ec43caf39520664da52c1af1f5532301435a71e7cadb590cbefeb2bbd1c65b6ee8340be93745127e1c47457ee7a608dc56f22cdfeac6f6985d4e420b16765308ed0879258ff5ef80719bc3f7139f3a9bc54e9e25dd1db5903675a4426f934c618ead760332eacf97e56fda706dbe3d782113901c1b66497a5405f39409af7cd3c5a8e07e1376bc41dcaeaab89fec7807078f901a8026d1eb4979e3588ffefbf8100602772276231c71ccb50edafd6de2ad2942d6a0fe14d17b676be25deec30858f0a25fadf8950abe6f15169dbe73bbaea411cbe575b052b94c73654369413fa9674675d466d76ba900c9f6d2c8559290c4d475f22b0c7178894077e79416e2c47a240bb90054f471111632609ac2a7ddf045c1a52e0c848ab9f72b3bedf24088d9ce82fdba83befc8d147ef304feb6811b1d61c26a3d3a4f64ca83601c3dff17d2342af07131841e5e2383da5a65d45cb2ea68284c9b3eca23e1e07fc61f9af4e80398b2f7e0f13bfad2fbfaa2b26854044b84ea1bf514a524f7092f45df38b33f6e85022079360df1c2724542dfe06e0bcf4d9d2ab5aca28b87097a11146e6eb3077e5b0bda32ebd76db72bcca1acbcb18d8b7160ffe724398657a9ca5046834e55ef6c5a68e76dad029c6dda8f713197b884d14c1f237ec4e5fb882e7f674d1cee2bc8fb92fb3ef7463b8d95929bd723faecec32be19f7affc294ecadf90caedf05db7876f0287815133d0e88d58dbba780951bd37b1ff99d5985ea6ef115104c900f048d8da6beb3f969001b89c2bda2216fb3b8289791ed6f6aa874bf239d8ab6383bee9e3e2d2367140b6ef683b1f47e48cfa4c93f313a279194939f17896208ae93d37d3e66b6f12cea8aae7a6d1fa5c0d212e573de19ae68e33ac38edcb9378e761a97c974849dd9f33a104a4415980d64c0ed7951e5a68ed839f629916235752166e90cc1dc7d484bab5b1c190509e566817257f6180fb9669680a357a122c78ffb9191ba2e8e17f6dacdca34b5b642d4bfb052c70b64cb7220154f8bd12ba8fc25aa3eb34a14648f4b69b0f16cab1a1b96b50f079bfb7e985a4de3791825e3cd449aacb419c63413d1ec6f369f1454e6120f90866ae2daf31356ff2ce17ad46a358524f3e23a423f3f578a9e2264cce0ec9d0935407c3d0e073612c332d04fb4bd9430ef2dc388436db57149f0fc389463e8e0d4d1033884daa1bfa21b8ceb161b409634a9b1d1a6ed30ac893c71d455f8363e44703cf81f970c2c40bf08a643a6dab1f902a3c1a104a3777be613db15b8e5bbe29617d644f665a8b6a2d77bdefbfbc0dbc705c2ea4f8bff9a10d071f495c</script> <div class="hbe hbe-content"> <div class="hbe hbe-input hbe-input-default"> <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass"> <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass"> <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span> </label> </div> </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html"><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOP</summary>
<category term="3D Engine" scheme="http://luhao.wiki/categories/3D-Engine/"/>
<category term="3D Engine" scheme="http://luhao.wiki/categories/3D-Engine/3D-Engine/"/>
<category term="Python" scheme="http://luhao.wiki/tags/Python/"/>
<category term="C++" scheme="http://luhao.wiki/tags/C/"/>
<category term="OpenGL" scheme="http://luhao.wiki/tags/OpenGL/"/>
</entry>
<entry>
<title>【工具】LaTeX教程(附模板)</title>
<link href="http://luhao.wiki/posts/2TXFVDF/"/>
<id>http://luhao.wiki/posts/2TXFVDF/</id>
<published>2023-10-24T15:05:19.000Z</published>
<updated>2023-11-04T18:03:22.240Z</updated>
<content type="html"><![CDATA[<p>Latex 对于学术党的作用是写论文,对于工作党的作用,那便是写简历了!<br>github 上有非常多的优秀简历 latex 模板,可以自己去搜,改起来非常容易。<br>本文简要记录 <strong>Latex 的安装、开发和发布流程</strong>。<br>github resume: <a href="https://github.com/593413198/Resume">https://github.com/593413198/Resume</a></p><h3 id="安装">安装</h3><p>只讨论 Windows 环境下的安装使用,Linux 实在很难搞定中文字体(你都没好用的图形界面用个der的Latex啊…)</p><p>下载下面的安装包,直接一路点到底(就像你小时候装盗版单机游戏那样…)</p><ul><li><a href="https://www.tug.org/texlive/windows.html#install">latex easy install</a></li></ul><h3 id="开发">开发</h3><p>Latex 每次编辑,依赖于<strong>重新编译</strong>生成可视化文件(如pdf),这是非常影响开发效率的(对比markdown)。<br>聪明的 VSCode 爱好者没有错过这样的机会,勤劳的他们开发出一款 <code>LaTeX Workshop</code> 的插件,真正做到了 <strong>所见即所得</strong>。</p><ul><li><a href="https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop">LaTeX Workshop</a></li></ul><p>使用方式,在VSCode市场安装该插件后,将如下配置写入到 <code>setting.json</code>:</p><pre><code>{ "latex-workshop.synctex.afterBuild.enabled": true, "latex-workshop.latex.autoBuild.run": "onSave", "latex-workshop.view.pdf.viewer": "tab",}</code></pre><p>此时每次修改完 latex 文件,按下 <code>ctrl + s</code> 保存键后,VSCode会<strong>自动编译生成</strong>同名的pdf文件。<br>接着使用 <code>LaTeX Workshop: View LaTeX PDF file</code> 新开一个窗口预览 pdf 的实时修改即可!</p><h3 id="发布">发布</h3><ul><li><code>pdflatex ${filename}</code><br>其中 <code>$filename</code> 是 tex 的文件名(不需要输入后缀)</li></ul><h3 id="模板">模板</h3><p>附github上一些优秀的简历模板:</p><ul><li>中文简历:<a href="https://github.com/dyweb/awesome-resume-for-chinese">Awesome Resume for Chinese</a></li><li>英文简历:<a href="https://github.com/posquit0/Awesome-CV">Awesome-CV</a></li></ul>]]></content>
<summary type="html"><p>Latex 对于学术党的作用是写论文,对于工作党的作用,那便是写简历了!<br>
github 上有非常多的优秀简历 latex 模板,可以自己去搜,改起来非常容易。<br>
本文简要记录 <strong>Latex 的安装、开发和发布流程</strong>。<br>
gi</summary>
<category term="工具" scheme="http://luhao.wiki/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="工具" scheme="http://luhao.wiki/categories/%E5%B7%A5%E5%85%B7/%E5%B7%A5%E5%85%B7/"/>
<category term="linux" scheme="http://luhao.wiki/tags/linux/"/>
<category term="markdown" scheme="http://luhao.wiki/tags/markdown/"/>
</entry>
<entry>
<title>【SIGGRAPH23】Large Scale Terrain Rendering in Call of Duty</title>
<link href="http://luhao.wiki/posts/siggraph-23-terrain-of-cod/"/>
<id>http://luhao.wiki/posts/siggraph-23-terrain-of-cod/</id>
<published>2023-10-19T16:55:34.000Z</published>
<updated>2024-03-01T05:33:17.404Z</updated>
<content type="html"><![CDATA[<p><img src="../../images/siggraph23-cod-ppt-demo.png" alt=""></p><div class="admonition note"><p class="admonition-title">导读</p><ul><li>这篇 SIGGRAPH 主要描述 COD 中的大规模地形渲染,并大量引用了之前GDC的技术分享</li><li>NETEASE WARNING: 已脱敏、与工作无关</li></ul></div><h3 id="技术参考">技术参考</h3><ul><li><a href="https://advances.realtimerendering.com/s2023/index.html#CODTerrain">SIGGRAPH 2023: Large Scale Terrain Rendering in Call of Duty</a></li><li><a href="https://research.activision.com/publications/2021/09/boots-on-the-ground--the-terrain-of-call-of-duty">GDC 2021: The Terrain of CoD</a></li><li><a href="https://www.gdcvault.com/play/1025480/Terrain-Rendering-in-Far-Cry">GDC 2018: Terrain Rendering in Far Cry 5</a></li><li><a href="https://www.gdcvault.com/play/1021761/Adaptive-Virtual-Texture-Rendering-in">GDC 2015: Adaptive Virtual Texture Rendering in Far Cry 4</a></li></ul><h3 id="大纲">大纲</h3><ul><li><a href="#why-new-terrain">Why New Terrain</a></li><li><a href="#render-process">Render Process</a></li><li><a href="#virtual-texture">Virtual Texture</a><ul><li><a href="#adaptive-virtual-texture">Adaptive Virtual Texture</a></li></ul></li><li><a href="#cliff-shading">Cliff Shading</a><ul><li><a href="#%E5%8E%9F%E5%9B%A0">原因</a></li><li><a href="#tri-planar-mapping">Tri-Planar Mapping</a></li><li><a href="#%E9%97%AE%E9%A2%98">问题</a></li></ul></li><li><a href="#stiching-fix">Stiching Fix</a><ul><li><a href="#1-lod%E4%B9%8B%E9%97%B4%E7%BC%9D%E9%9A%99">1. LOD之间缝隙</a></li><li><a href="#2-%E4%B8%8E%E6%A0%91%E6%9C%A8%E7%9F%B3%E5%A4%B4%E4%B9%8B%E9%97%B4%E7%9A%84%E7%BC%9D%E9%9A%99">2. 与树木、石头之间的缝隙</a></li></ul></li><li><a href="#%E6%80%BB%E7%BB%93%E5%B1%95%E6%9C%9B">总结、展望</a></li></ul><p>TODO:</p><ul><li>Displacement Decal</li><li>One Material Per Vertex (OMPV)</li></ul><h2 id="Why-New-Terrain">Why New Terrain</h2><p>作者阐述,之所需要开发新的地形系统,是因为传统的地形系统无法满足 现有的游戏需求(即策划不满意)。而传统地形主要有如下两个特征:</p><ol><li>地形范围小(如室内地面装饰)</li><li>依赖手动编辑(如魔兽争霸地图)</li></ol><p>作为对比,新的地形系统提出如下三个要求和挑战:</p><ol><li>Lerge-Scale(水平 + 垂直两个维度) -> 解决性能问题 runtime</li><li>画面多样性、细节保证 -> 解决表现问题 bugfix, tradeoff</li><li>依赖程序化生产 -> 解决工具问题 PCG</li></ol><p><img src="../../images/siggraph23-cod-new-terrain.png" alt=""></p><h2 id="Render-Process">Render Process</h2><p>作者展示如何在forward管线下绘制一个朴素的地形:</p><ul><li><p><strong>VS Input</strong><br>Vertex Shader 的输出就是一些平铺状的mesh顶点,它是没有任何高度信息的<br><img src="../../images/siggraph23-cod-vs-input.png" alt=""></p></li><li><p><strong>VS Offset</strong><br>这一步通过采样 Height Map 的高度信息,对 VS 顶点作出一些偏移,从而模拟出地形高度的形状<br><img src="../../images/siggraph23-cod-vs-offset.png" alt=""></p></li><li><p><strong>PS Shading</strong><br>Pixel Shader 阶段采样各种 Diffuse、Normal 贴图进行着色计算,为了弥补地形的细节,美术会叠加多层的 Layers 进行混合。<br>通常还会引入底层API支持的 <code>Texture Array</code> 技术进行优化<br><img src="../../images/siggraph23-cod-ps.png" alt=""></p></li><li><p><strong>Quad - Tree</strong><br>因为地形只考虑平面结构,所以想到利用四叉树进行遍历、剔除的优化。<br>以COD的地形为例,<code>10km x 10km</code>的总规模,<code>64m x 64m</code>的单个地块尺寸,所以约有2w多个地块,即使经过视锥剔除等优化手段,还是有成百上千个Chunks需要渲染,因此<strong>性能压力非常大</strong>!<br><img src="../../images/siggraph23-cod-quard-tree.png" alt=""></p></li></ul><h2 id="Virtual-Texture">Virtual Texture</h2><p><code>Virtual Texture</code> 是 GDC-2015 提出的一个技术,在弄懂改技术之前,先了解它在尝试解决什么问题。</p><p>继续上一节的结论,当开发大世界游戏逐渐兴起,每帧需要渲染的 Chunks 数量急剧增长,每个 Chunks 都拥有自己的贴图,<strong>这无疑给硬件内存带来巨大的挑战。</strong> 但是如何解决呢?</p><p>参考 <a href="/posts/virtual-memory/"><strong><code>Virtual Memory</code></strong></a> 的提出(即物理内存无法满足需求时,计算机抽象出了虚拟内存这样的中间层),计算机科学有一条非常重要的公理:<strong>即软件(或硬件)层面无法解决的问题,往往可以引入一个中间层。</strong></p><p>因此 <code>Virtal Texture</code> 基于此思想,它将所有贴图试做 内存 意义上的贴图,不会一次性加载到显存,而是使用的时候才会去加载。参考 GDC 的图片:</p><ul><li>Virutal Texture:内存(磁盘)意义上的贴图</li><li>Indirection Texture:寻址结构,类似与虚拟内存中的 页表</li><li>Physical Texture:实际采样的贴图</li></ul><p><img src="../../images/siggraph23-cod-vt.png" alt=""></p><hr><p>VT 建议单独列一篇文章讲解,可惜缺少实战落地经验,这里先贴一些有价值的参考链接:</p><ul><li><a href="https://zhuanlan.zhihu.com/p/138484024">浅谈Virtual Texture - 知乎</a></li><li><a href="https://zhuanlan.zhihu.com/p/143709152">游戏引擎随笔 0x14:UE4 Runtime Virtual Texture 实现机制及源码解析 - 知乎</a></li></ul><hr><h3 id="Adaptive-Virtual-Texture">Adaptive Virtual Texture</h3><p>VT 技术有一个非常明显的缺陷:<strong>不论 Chunk 距离相机远近如何,但在 VT 中的像素比重却相同</strong>。</p><p>基于这点,AVT 提出了一种基于相机距离的 VT优化技术。其中红色框表示距离相机较近的 Chunk(像素也较高即 64k x 64k),而绿色框距离相机较远(像素分辨率也很低 16k x 16k)</p><p><img src="../../images/siggraph23-cod-avt.png" alt=""></p><h2 id="Cliff-Shading">Cliff Shading</h2><p>COD游戏中有大量山坡和悬崖的渲染(参考PUBG),因此开发人员在 GDC-18 中花费大量笔墨讲述了 <code>Cliff Shading</code>,Cliff又翻译成 <strong>悬崖、峭壁</strong>。</p><p>先看看 Cliff Shading 首要解决的是什么问题,当 Chunk 的贴图使用在平坦地面时,表现是完美。但因为山坡峭壁的y轴是非常陡峭的,因此会出现严重的拉伸 tiling 现象:</p><p><img src="../../images/siggraph23-cod-cliff-bug.png" alt=""></p><h3 id="原因">原因</h3><p>将世界坐标 (x, y, z) 映射到 贴图空间 (u, v),首先考虑如下几种朴素的方式(即选取任意两个轴采样):<br><img src="../../images/siggraph23-cod-cliff-xy.png" alt=""></p><p>因为丢失了某一个维度的信息,因此效果是不尽满意的。</p><h3 id="Tri-Planar-Mapping">Tri-Planar Mapping</h3><p>COD团队首先尝试了业界著名的 Tri-Planar Mapping 方案,即分别从 x, y, z 三个方向投影得到映射效果,然后根据法线与三个轴的夹角关系,将三个结果作融合得到最终的效果:</p><p><img src="../../images/siggraph23-cod-cliff-triplanar.png" alt=""></p><p><img src="../../images/siggraph23-cod-cliff-shader.png" alt=""></p><p>关于 <code>Tri-Planar Mapping</code> 参考阅读:</p><ul><li><a href="https://catlikecoding.com/unity/tutorials/advanced-rendering/triplanar-mapping/">Triplanar Mapping</a></li><li><a href="https://gamedevelopment.tutsplus.com/use-tri-planar-texture-mapping-for-better-terrain--gamedev-13821a">Use Tri-Planar Texture Mapping for Better Terrain</a></li></ul><h3 id="问题">问题</h3><p>采用 <code>Tri-Planar Mapping</code> 方案后有明显的两个问题:</p><ol><li>性能非常差(采样数 x3)</li><li>远处 Texture Tiling 非常严重<br><img src="../../images/siggraph23-cod-cliff-tiling.png" alt=""></li></ol><p>性能问题先不考虑(因为是PC端游戏),开发团队后面主要描述如何解决山坡上的Tiling问题。<br>关于如何消除重复,最简单的方案就是<strong>引入随机数</strong>,这里采用了 Nividia 发表的一篇论文中的噪声函数,最终的做法是将 正常的Blending值 + 噪声值,</p><p><img src="../../images/siggraph23-cod-cliff-random.png" alt=""></p><p>观察黑色框中放大的部分,可以看到明显的噪声值,但是整体的效果反而更好的。引用GDC中非常经典的一句话:<strong>“单个像素是错误的,但是放到整体(平均下来)又是正确的”</strong></p><p><img src="../../images/siggraph23-cod-cliff-improve.png" alt=""></p><h2 id="Stiching-Fix">Stiching Fix</h2><p>新的地形系统采用将不同 Chunk 拼接渲染的方式,那么是否会导致渲染的缝隙、不连贯呢?<br>答案是:会的。</p><p><code>Stiching Fix</code> 重点讲述了COD如何修复地形渲染的一些缝隙,而 <code>Stiching</code> 的中文翻译便是缝隙。</p><h3 id="1-LOD之间缝隙">1. LOD之间缝隙</h3><p>如下图,不同LOD层级之间的 mesh data 差异较大,会出现同一个顶点在两边的信息不共享,这在渲染时会出现明显的错误。<strong>(具体是什么错误?)</strong><br><img src="../../images/siggraph23-cod-stiching-1.png" alt=""></p><p>修复的方式比较朴素,即 <strong>将边缘的顶点,移动到另一个lod最近的顶点</strong></p><ul><li><p>跨一层LOD的情形<br><img src="../../images/siggraph23-cod-stiching-2.png" alt=""></p></li><li><p>跨两层LOD的情形<br><img src="../../images/siggraph23-cod-stiching-3.png" alt=""></p></li></ul><h3 id="2-与树木、石头之间的缝隙">2. 与树木、石头之间的缝隙</h3><p>主要是做一个贴地处理,即根据 Chunk HeightMap 的高度信息,对树木、石头的高度在 Vertex Shader 中做一个高度插值处理,从而实现将它们 <strong>贴在地表</strong></p><p><img src="../../images/siggraph23-cod-stiching-4.png" alt=""></p><h2 id="总结、展望">总结、展望</h2><ul><li>地形系统主要细分两个方面:<ol><li>卷细节: pcg, texture layers …</li><li>卷性能: avt,gpu pipeline …</li></ol></li><li>从 GDC 2015、2018、2021 再到 Siggraph 的集大成者,任何一项技术的发展都需要持续迭代、集思广益</li><li>了解一个技术,和实现一个技术,两者相差 1~2 个数量级</li></ul>]]></content>
<summary type="html"><p><img src="../../images/siggraph23-cod-ppt-demo.png" alt=""></p>
<div class="admonition note"><p class="admonition-title">导读
</p><ul>
<li></summary>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/"/>
<category term="Graphics" scheme="http://luhao.wiki/categories/Graphics/Graphics/"/>
</entry>
<entry>
<title>csv, hdf5, feather 数据性能对比</title>
<link href="http://luhao.wiki/posts/data-perf/"/>
<id>http://luhao.wiki/posts/data-perf/</id>
<published>2023-10-12T14:06:39.000Z</published>
<updated>2023-10-24T15:35:37.691Z</updated>
<content type="html"><![CDATA[<div class="admonition note"><p class="admonition-title">导读</p><ul><li><a href="/posts/quant-data/">这篇blog</a> 介绍了金融相关的数据特性,它对于读写和存储性能有极高要求</li><li>本篇blog 会结合跑测数据,分析三种格式的性能</li></ul></div><h2 id="测试结论">测试结论</h2><p>结论放在最前面,测试数据见 <a href="#%E6%80%A7%E8%83%BD%E6%B1%87%E6%80%BB">文末</a></p><ul><li>数据量小,无脑使用 csv</li><li>数据量大,如果坚持 csv,请使用 <strong>zip</strong> 参数压缩(尽管这会降低读写速率)</li><li>百M级别以上数据,推荐使用 hdf5(而不是feather)<ul><li>性能强于 csv,<strong>读写快5~10倍</strong></li><li>hdf5 对于 Python/C++ 的API支持较好</li><li>feather 虽然性能更强,但限制更多 TODO</li></ul></li><li>切记! <strong>此结论并不适用于所有类型的数据样本</strong>,最好自己针对性跑测,找到最适合自己数据的格式!<ul><li>对于期货 tick 数据(由于大量重复值),csv.zip 压缩比率能达到 15%,feather 能达到 30%</li></ul></li></ul><h2 id="基本介绍">基本介绍</h2><h3 id="csv"><a href="https://en.wikipedia.org/wiki/Comma-separated_values">csv</a></h3><p><code>csv</code> 全称是 <code>Comma-separated values</code>,<strong>即以逗号分隔的纯文本格式</strong>,常用后缀是 <code>*.csv</code>。<br>正因为其纯文本的性质,常见的编辑器(或者excel)都可以预览csv文件,所以它的优点是 <strong>简单直观</strong>。<br>然而事物都具有两面性,就像 <code>json</code> 存储格式,直观的代价往往是性能的损失。</p><h3 id="hdf5"><a href="https://en.wikipedia.org/wiki/Hierarchical_Data_Format">hdf5</a></h3><p>全称是 <code>Hierarchical Data Format version 5</code>,<strong>即高度层次化的二进制格式</strong>,常用后缀是 <code>*.h5</code>。<br>hdf5格式从设计之初,就是服务于大型数据。</p><h3 id="feather"><a href="https://arrow.apache.org/docs/python/feather.html">feather</a></h3><p>feather 是一种用于存储数据的 <strong>快速地、轻量级的二进制格式</strong>,常用后缀是 <code>*.fea</code>。<br>它早起就是为 Python(Pandas) 和 R 这两种编程语言所设计的。<br>目前广泛使用的其实是 feather v2 版本,它区别于早期的 v1 版本,这个不用过多了解。</p><h2 id="测试标准">测试标准</h2><p>主要从四个维度测量性能:</p><ul><li>1.写入速度</li><li>2.读取速度</li><li>3.磁盘大小</li><li>4.读取内存 (TODO)</li></ul><p>同时考虑到金融数据的存储格式,大多是 <code>int64</code>、<code>float64</code> 和 <code>timestamp</code>,因此也会分别考量 在这三种格式下的性能表现。</p><h3 id="压缩性">压缩性</h3><p>因为写入的csv文件较大,所以考虑 csv 结合各种压缩算法测试(压缩本质是牺牲性能、换取空间)。<br><code>df.to_csv('', compression)</code> 可以传入 <code>zip</code>、<code>gz</code>、<s><code>bz2</code></s> 等等参数。</p><h3 id="benchmark">benchmark</h3><p>使用 800w x 10 的DataFrame数据,取值范围是 0 ~ 16亿,格式是 <code>np.int64</code>。<br>数据大概长这样:</p><pre><code class="language-csv">0 d0 d1 d2 d3 d4 d5 d6 d70 5680658661046001 6886564689964211 777947290120004 6223515736992396 2823728071993317 8416657213663291 3500805963228465 10097481526053971 8952870328278778 306015862731108 9726241400443289 4237512935832667 2875425479333067 6311517969042662 878720088161354 87871182730650332 6858523177136352 1733196075769152 840572662722070 8438133907754012 6671944540650125 1147224095855703 1583106816125259 27980685687731413 2940819554234759 3142545317839947 4641209159206074 8194125756197731 4958881218032026 4405637321734842 500692399773906 35316179424621364 8149680042981168 6793238579260437 3891639455885689 3690167863144449 7552248224604567 6411717840330018 9556078695826276 4851668202438122......[8000000 rows x 9 columns]</code></pre><h3 id="测试源码">测试源码</h3><pre><code class="language-python">class DataPerf(object): """ 数据 性能测试 """ @staticmethod def get_random_datas(rows, cols): """ 随机生成 rows行 x cols列 的DataFrame数据 :param rows: 行 :param cols: 列 """ data = np.random.randint(low=0, high=pow(10, 16), size=(rows, cols), dtype=np.int64) df = pd.DataFrame(data, columns=[f'd{i}' for i in range(cols)]) return df @staticmethod def perf_write_speed(): """ 测试写入速度 """ df = DataPerf.get_random_datas(800 * 10000, 8) PERF_TIME() df.to_hdf('perf.h5', 'data') PERF_TIME('hdf5') df.to_feather('perf.fea') PERF_TIME('fea') df.to_csv('perf.csv') PERF_TIME('csv') df.to_csv('perf.csv.gz', compression='gzip') PERF_TIME('csv (gz)') df.to_csv('perf.csv.zip', compression='zip') PERF_TIME('csv (zip)') @staticmethod def perf_read_speed(): """ 测试读取速度 """ PERF_TIME() d1 = pd.read_csv('perf.csv') PERF_TIME('csv') d2 = pd.read_csv('perf.csv.gz', compression='gzip') PERF_TIME('csv (gz)') d3 = pd.read_csv('perf.csv.zip', compression='zip') PERF_TIME('csv (zip)') d4 = pd.read_hdf('perf.h5') PERF_TIME('hdf5') d5 = pd.read_feather('perf.fea') PERF_TIME('fea')if __name__ == '__main__': dp = DataPerf() dp.perf_write_speed() dp.perf_read_speed()</code></pre><h2 id="测试数据">测试数据</h2><h3 id="1-写入速度">1. 写入速度</h3><p>注意:因为 追加、覆盖 等模式会影响性能,所以重复测试前,记得删除已写入的数据。</p><table><thead><tr><th style="text-align:center">format</th><th style="text-align:center">write time (s)</th></tr></thead><tbody><tr><td style="text-align:center">csv</td><td style="text-align:center">33.5</td></tr><tr><td style="text-align:center">csv (gz)</td><td style="text-align:center">135</td></tr><tr><td style="text-align:center">csv (zip)</td><td style="text-align:center">128</td></tr><tr><td style="text-align:center">hdf5</td><td style="text-align:center">1.8</td></tr><tr><td style="text-align:center">feather 👍</td><td style="text-align:center">1.3</td></tr></tbody></table><h3 id="2-读取速度">2. 读取速度</h3><p>测试接口,全部选择 <code>pandas read_***</code> 系列,会全部转化为 DataFrame 格式。</p><table><thead><tr><th style="text-align:center">format</th><th style="text-align:center">read time (s)</th></tr></thead><tbody><tr><td style="text-align:center">csv</td><td style="text-align:center">10.8</td></tr><tr><td style="text-align:center">csv (gz)</td><td style="text-align:center">15.0</td></tr><tr><td style="text-align:center">csv (zip)</td><td style="text-align:center">14.1</td></tr><tr><td style="text-align:center">hdf5</td><td style="text-align:center">4.2</td></tr><tr><td style="text-align:center">feather 👍</td><td style="text-align:center">2.0</td></tr></tbody></table><h3 id="3-磁盘大小">3. 磁盘大小</h3><table><thead><tr><th style="text-align:center">format</th><th style="text-align:center">file size (GB)</th></tr></thead><tbody><tr><td style="text-align:center">csv</td><td style="text-align:center">1.10</td></tr><tr><td style="text-align:center">csv (gz)</td><td style="text-align:center">0.53 (48%)</td></tr><tr><td style="text-align:center">csv (zip)</td><td style="text-align:center">0.53 (48%)</td></tr><tr><td style="text-align:center">hdf5</td><td style="text-align:center">0.55 (50%)</td></tr><tr><td style="text-align:center">feather 👍</td><td style="text-align:center">0.49 (44%)</td></tr></tbody></table><h2 id="性能汇总">性能汇总</h2><ul><li>以下统计的是 <strong>相对得分</strong>,数值越高说明性能越好</li></ul><iframe src="/html/data-perf-echart.html" 替换这里的html="" height="600" width="100%" 修改高度即可="" frameborder="0" scrolling="yes"> 支持滚动条</iframe>]]></content>
<summary type="html"><div class="admonition note"><p class="admonition-title">导读
</p><ul>
<li><a href="/posts/quant-data/">这篇blog</a> 介绍了金融相关的数据特性,它对于读写和存储性能有极高要</summary>
<category term="量化交易" scheme="http://luhao.wiki/categories/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
<category term="量化交易" scheme="http://luhao.wiki/categories/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
<category term="Python" scheme="http://luhao.wiki/tags/Python/"/>
<category term="C++" scheme="http://luhao.wiki/tags/C/"/>
</entry>
<entry>
<title>【量化】爬虫获取东财数据</title>
<link href="http://luhao.wiki/posts/spider-easymoney/"/>
<id>http://luhao.wiki/posts/spider-easymoney/</id>
<published>2023-10-05T14:40:04.000Z</published>
<updated>2024-03-01T05:33:17.408Z</updated>
<content type="html"><![CDATA[<div class="admonition note"><p class="admonition-title">导读</p><ul><li>这部分主要讨论<strong>基本面数据</strong>,获取<strong>行情数据</strong>看<a href="http://localhost:4000/posts/quant-data/#%E6%95%B0%E6%8D%AE%E6%8F%90%E4%BE%9B%E5%95%86">这篇文章</a></li><li>详细的爬取标准文档见这篇:<a href="/posts/cores/datasource/#Eastmoney">cores/datasource</a></li><li><strong>NOTE</strong>: <a href="https://akshare.xyz/index.html"><strong>akshare</strong></a> 已收录所有内容,不要重复造轮子啦</li></ul></div><div class="markmap-container" style="height:190px"> <svg data="{"t":"root","d":0,"v":"","c":[{"t":"heading","d":1,"p":{"lines":[0,1]},"v":"<a href=\"#爬虫基础\">爬虫基础</a>"},{"t":"heading","d":1,"p":{"lines":[1,2]},"v":"<a href=\"#爬取规则\">爬取规则(东财)</a>"},{"t":"heading","d":1,"p":{"lines":[2,3]},"v":"功能实现","c":[{"t":"heading","d":2,"p":{"lines":[3,4]},"v":"<a href=\"#功能实现\">源码</a>"},{"t":"heading","d":2,"p":{"lines":[4,5]},"v":"<a href=\"#处理json\">处理json</a>"},{"t":"heading","d":2,"p":{"lines":[5,6]},"v":"<a href=\"#数据存储\">数据存储</a>"}]},{"t":"heading","d":1,"p":{"lines":[6,7]},"v":"<a href=\"#数据应用\">数据应用</a>","c":[{"t":"heading","d":2,"p":{"lines":[7,8]},"v":"<a href=\"#示例一:寻找-“A股大鳄”\">示例一:寻找“A股大鳄”</a>"},{"t":"heading","d":2,"p":{"lines":[8,9]},"v":"<a href=\"#示例二:财报公布后股价走向\">示例二:财报公布后股价走向</a>"}]}],"p":{}}"></svg></div><hr><h3 id="爬虫基础">爬虫基础</h3><p>本章要爬取的东方财富,数据结构非常简单,在爬虫领域中属于入门级别,使用 <code>request</code> 库即可。<br>下面代码示例,爬取 贵州茅台<code>600519</code> 的一些基础操盘信息:</p><pre><code class="language-py">import requestsimport jsonurl = 'https://emweb.securities.eastmoney.com/PC_HSF10/OperationsRequired/PageAjax?code=%s'res = requests.get(url % 'SH600519') # 贵州茅台info = json.loads(res.text) # dict</code></pre><h3 id="爬取规则">爬取规则</h3><p>东方财富的数据(<a href="https://emweb.securities.eastmoney.com/PC_HSF10/OperationsRequired/Index">示例链接</a>)主要有两个特点:</p><ul><li>优点:链接条理清晰,便于爬取</li><li>缺点:采用拼音缩写的命名,贼坑(例如 <code>yjbg</code> 表示<code>研究报告</code>)…</li></ul><hr><p>先分析信息页的基本结构,<strong>第一层级</strong>是下图红框部分,<strong>东财将它分为十六个板块</strong>。<br>我们会挑选需要的数据板块来爬取。</p><p><img src="../../images/eastmoney-head.png" alt=""></p><hr><p>以 <strong>股东研究</strong> 为例,<strong>内部还会细分为多个第二层级</strong>,例如:</p><ul><li>股东人数:每隔一段时间公布股东数量</li><li>十大股东:前十大持股的对象,包含增减比例</li><li>机构持仓:有哪些公募、私募基金的持仓</li><li>…</li></ul><p><img src="../../images/eastmoney-partners.png" alt=""></p><hr><h3 id="功能实现">功能实现</h3><p>源码: <a href="https://github.com/593413198/Alpha-Bet/blob/master/cores/datasource/Eastmoney.py"><em>cores/datasource/Eastmoney.py</em></a></p><p>先实现一个基础的爬取指定 url + code 的函数:</p><pre><code class="language-py">def crawl_base(self, code, url, fields): """ 根据指定规则爬取 :param code: 股票代码,如SH600519 (str) :param fields: 爬取的键值,映射到中文 (dict) :param url: 爬取的链接,股票代码用%s代替 (str) """ url = url % code res = requests.get(url) info = json.loads(res.text) info = { fields[k] : v for k, v in info.items() if k in fields} return info</code></pre><p>后面依次实现爬取不同模块的函数,并选取有价值的字段 …</p><ul><li>操盘必读 板块</li><li>股东研究 板块</li><li>其他略 …</li></ul><pre><code class="language-py">def _crawl_cpbd(self, code): """ 【操盘必读】 需要字段如下: √ tszb: 特殊指标 √ ssbk: 所属板块 √ zxzbhq: 最新指标 https://emweb.securities.eastmoney.com/PC_HSF10/OperationsRequired/Index?type=soft&code=SH600519# """ fields = { 'tszb' : '特殊指标', 'ssbk' : '所属板块', 'zxzbhq': '最新指标', } url = 'https://emweb.securities.eastmoney.com/PC_HSF10/OperationsRequired/PageAjax?code=%s' return self.crawl_base(code, url, fields)def _crawl_partner(self, code): """ 【股东研究】 需要字段如下: √ gdrs: 股东人数,通常股东数越少,代表股价越集中,则更容易上涨 √ sdgd: 十大股东,包含持股数和变动比例 sdltgd: 十大流通股东 jgcc: 机构持仓 jjcg: 基金持股 https://emweb.securities.eastmoney.com/PC_HSF10/ShareholderResearch/Index?type=soft&code=SH600519# """ fields = { 'gdrs' : '股东人数', 'sdgd' : '十大股东', } url = 'https://emweb.securities.eastmoney.com/PC_HSF10/ShareholderResearch/PageAjax?code=%s' return self.crawl_base(code, url, fields)# 其他省略 ...</code></pre><hr><h3 id="处理json">处理json</h3><p>写入 <code>json</code> 文件时,需要注意中文编码问题,下面是一个万能模板:</p><pre><code class="language-py">with open(json_path, 'w', encoding='utf8') as f: # infos是要写入的 dict content = json.dumps(infos, ensure_ascii=False, indent=4) f.write(content)</code></pre><h3 id="数据存储">数据存储</h3><p>结合多进程爬取五千多只股票池,注意处理空数据、网络错误等情形,最后分别以 <code>json</code> 格式存储在本地。</p><p><img src="../../images/eastmoney-json.png" alt=""></p><hr><h3 id="数据应用">数据应用</h3><p>量化金融追求一个实用注意,那么获取这些数据究竟有什么用途?<br>除了常见的<strong>用作策略因子</strong>外,下面展示几个与众不同的用法:</p><h3 id="示例一:寻找-“A股大鳄”">示例一:寻找 “A股大鳄”</h3><p>股票市场的股权拥有者,一般有两种,要么是个人,要么是企业/地方政府/国家。</p><p>下面我们尝试统计 5000 多支股票的十大股东(从东财爬取的数据),然后稍作拟合,再按照持有数值(也可以是持有公司数量)排名,便得到如下图表(绘图来自 pyecharts):</p><blockquote><ol><li>带 <strong>香港结算</strong> 字样的主体(及HKSCC),一般香港/外国投资者通过港交所购买的股份,即所谓的 “北向资金”</li><li>实际持仓最多的是 <strong>中国财政部</strong>,他基本持有了各大银行的股份</li><li>其他排名高的主体,基本分布在 <strong>石油、保险、电信、证券</strong> 等国有行业</li></ol></blockquote><iframe src="/html/sort_by_amout.html" 替换这里的html="" height="500" width="100%" 修改高度即可="" frameborder="0" scrolling="yes"> 支持滚动条</iframe><hr><p>看这些国有巨头的数据没啥意思,下面尝试筛选个人持股的排名。(筛选条件很简单,长度 <code><=3</code> 的便当做个人)</p><p>排名第一的大哥叫 <strong>魏巍</strong>(竟然是28家上市公司的前十大股东…),冲浪查一下,发现他位列 <strong>牛散F4</strong> 之一。<br>而排名第二的 <strong>徐开东</strong>,也是A股赫赫有名的个人投资者,跪了!<br>靠一己之力的买买买,坐拥如此多的上市公司(虽然只是前十股份),其艰难程度可想而知!</p><iframe src="/html/sort_by_num_personal.html" 替换这里的html="" height="500" width="100%" 修改高度即可="" frameborder="0" scrolling="yes"> 支持滚动条</iframe><hr><h3 id="示例二:财报公布后股价走向">示例二:财报公布后股价走向</h3><p><font color="#FF1E10"><strong>TODO</strong></font></p>]]></content>
<summary type="html"><div class="admonition note"><p class="admonition-title">导读
</p><ul>
<li>这部分主要讨论<strong>基本面数据</strong>,获取<strong>行情数据</strong>看<a href="http</summary>
<category term="量化交易" scheme="http://luhao.wiki/categories/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
<category term="量化交易" scheme="http://luhao.wiki/categories/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
</entry>
<entry>
<title>日志:2023年10月</title>
<link href="http://luhao.wiki/posts/2023-10/"/>
<id>http://luhao.wiki/posts/2023-10/</id>
<published>2023-10-03T12:39:50.000Z</published>
<updated>2024-03-01T05:33:17.408Z</updated>
<content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <script id="hbeData" type="hbeData" data-hmacdigest="70633d927d709480bbed1900c6022d41ab8aac0dc51eaccd23f7c75881c20b45">b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475de0b1305a637ea913974dff7410a14ed4dbbb40a5278eb14bbac34e59e12a284c7a294ab69a495d47e11e78ad50feb1070946a0084538613a8500fde76ee182cdc92156f5cc629c04691a80beeb70e034667984a29cb1db27ea11974521ef316310dd65dbfbb2a0d60f2a8ce6cab9f6cd825044604fed45aadefb22ba2321e19dee0566e6736947a45ad09061320fc246487e86167f696ce0b59ebd851781c74c35b8570834a7fb16ccddf85c541da4399962ab09e1e1ffa50d762e18e62c34615594554095f4effae7f3173f3242f7d285478fc1e6d0eb779e3ad11f1012997a7691d132e80e6dbc913b732d2137b6b8f57f694a4bbc70f994c2da6ae0c94e2f6c8c831f133927494996ba5792fd93c2fdfe0eb6f6f9331eea96358ec38968250bac0d7c070ca45382255703402e14b1c12f4b6575d19e98b134e304b397b7aae8ea19642fe8f01427dc67ab652cf56b4ef5303ef984d86a43e75e38f31fa2ee88ecf4338d7b0525c22f6a0239dae2eaa2bd1f594c7ec15c9ecc638c89d4a07eedaacb68410825e683c07c0953c99245fdb7567893d747df61cd912a11503c3fd4da64a36e87856618b775733cabcdeaad064836cce2c64d7777e31bffbf3b39f702ab793910862d380501b3be50aa526265eeffb3b510802d4dad98e511ae0ffed757f91e649b5fcc0f8cde1f9bcf6054be8c2434c50711ad3d54cefc96c0776fd334c84f3108aa4b6417393d4310021ade3ea87c841c093886cd78524a429172dd29da1e7d5002c8e3f28656021a6591edce5666dd9ebe1ebb1576689139f329ef1b3c741bfaec6a1b6de1d6f8c143382824e1ddd5744cb9ad223885f43f7541ff956a5041c29f957d7769b29b067fa9e16305dddeba77fd3d04a2555f5c4ce9a76454a000b2b4a15d2a70892d99bad0d2d90ec4b4d60dc1a6d7b8985a49136637a08f639935102fa843b0443b7e75c9c5d7d86d28a203937faaef4dbd7c0ec8cdb379bd118e686bf5b65b558f325b738f12bc4c1652b0e1d2b65cf17afadec738ae39b244ec2208fe3e1bdd5177f6d94070e482093d1d5978e52145163cd7d94c8778e1a680be7ba2f3f264ffd2a5bf487519e45a4623775b22a2e801bb986fbe8a6133c32a786e9c66149f540ca4273f922926e6460a0cd2caa5e13d3652f2fe9a9c7e7ce8681b3f5c0b404fc870b0c61404454d36dfb3b1bf7b3909136046e8290be28e6c82d5e974abd3f0d207ce677d455bb5652fe53927ebb2e50a3ad6dc1a77f5e10e74070ff59cadad82fdecffee5f60f8805f4cddc270416c343e126325b9b49963b9df72ea0f287ab445fc1043cb9aef960efaa7a3d0a07f47737e5fafc5f31872ae381de5d018a507ccf9c5448f48bcd96516a314284643c241f2f5bbb525829025833bcec3e927b1c932b24826c601d2601d2e6146f3c8e46aaec0820ffab38a31c0f50b209b3b7c70c50e09c4467049ed8336723a3f20d97294072fabad5419102c589a6f6f3c4d5468ec67265c88039e30424385ca8fe9b51a50cc77e21b4d73cc71e4c5754762c60d83470e3ce0022755d7630bf3ee009c2fd32128da5d155cd35950b47870348dcb92e52402d73f450b4ae56ca94748c88787f93e5733555a57ca77c19fba83dae595d97246d435444a4727be3dcad505593a74a4ade670032088934539d924c3421e219af2ac2a2958ccfcc5fff60a1e47c6af06e0774055f0c0d2de9438dae41dfe9c6bd0a89788d32a233d77987ac3980510444340c6f8d11750631d3175bde177b8de7dbfb0ff25056c396cd9dc597cdbcecff5483b543d0b61141bdacbc76edc296657bf3ed88730ffe741ef90d1ccbb96fc265249afd200e7d8c9c08c45033cb197d713a054d6acd59cda40c54b0b030a60902e1b3d56fec60e709de21d706604e83333058be491185d29eafc2e09269503fa7652ad30dcddd84707bb35ec91aaa80614753e2914023a70c015ac6523427291a18d2ce214643949fd34bee0c7041cae1560f10499faecfb8ad48b8a3dcd4d9ef69e1f5c3787935147bc2eee9148843ccad1e9c8432650d5264bdaec7a30567126e75d40df65d85805a1ba9ca65819c9d7243d222074570717b270263fe110785473367e29bfc344acfa6db173b1c493eaa081c002380484c10a20b9f2e547e499c7b962e6d525bafea81b322aa5e3107ea01f095e0ccbfac0dce867ed1ba0574b012b3f644314c22cfc0f5004c7ae06ac7169c5fc35d711e9e577b18bcf4c2d02ac44aa220d1ad5849852e9e6790786b0fbb57e3a0433492067ab8b272b275ec9f645c0553a4531feb03bb9a34e876c7b5fac23f3fd63ad4236d5eceddb7f5368d2d3379b657affc6722f46140b31e719d1aa60ffc182c06bda7b7132e9e95fd229cd2fa6be754dacd1fc7380a2cdeeeecf1652731694b935280793a66f55e84abc0939d3dfc37f9376c817a69db0f9aec926c62dd7ed73c18934e8464071e2438cdd41df20875220542ac80c0e42446050e34ac6c2b82c48b1a09d827963ceabad89fe0d2f27e706e994698edefd67f6c731fa454e864e439894ed3f2b2822bded480a79fd89b9b1eb2aa88f756bf833ad0e5f0b6cca0e6acc7b3905b17bb51b7a36ab6e64552515ede61d857ef4d9deb9216f1045d88fe20f12916c13c2d35c3c8160bcea7475ee055dab046ec85d6451b72ef6b746f25b48f246a1ef0bd8f6c187f40f362e3e3bfaed162ee482e4e90eef9c761b03c24d21e8428e34753a3c59f332ff38300514805d1e0439e0c31526fd95aa8057fcbeeecb21191d65c71f881b62d2d32c0932abcce78ce06c196b69d3a52235a2e111fa91844fc2e4a35c2109adf47f8fc7514e7cf9d6d952193955c9f5015782be51be7f6b865e246671950599d9622f8a5caa07087852873ea794853e026b52d9ec8ac97c906157dcf22fc0ab0a9ad96d13235511031257462db138b40592cce32641b7c7e9d4437346a223b8cc84aeecce2e0c567110c3c7359caf7af45a859cbb4cf5db22849eacc9e4f646b87b104be5bf2cf19090b637e0b6def1c42aeb4df72c802237f84bfc02f8a89ee7c752f5899fde421962f2d161ef0d496e5322d06c132bb9233feda99aa5bc0d40c6614a199813f4516ba9a09b7815c9d36f4dd75d0583b59616dece07380381a0bffb0f4208893603e9c7cdc2a58a4046229b5b2ae349b57ad4c4ebc859bae8660c9f6d70e473334eb93620444d39b496d16055ff13139cc45967df24fa7ed83020f1d6582cd0514e20e0e5c223ccbb248b3a49949748009f9cae16d0444ba5cbece68e2f363c963781a3ebdcf8bb7cae8104cde698040da97ca058b1142b22317c6239955e3a6f64fa652ae3fea57ab30d924a176db399fe01e7ac5cc5b2a9c9eb6c3bc23c349ad434598e1629f47cd7ef8b26261dc0e7d5381188dfc84da3333ce06cf53365a116f82fb3c923b358de4d71f172c13e2f63e05032e53bed7aeb24d2cf8c9139e4ef654d7d8fdd96b1eaefa19551f4251866c5d64e50b36e958872f784a33f962e9e3461a100b69e1f582ad56c157cb03ee3555ff810ee77674ad863fc441d16faf4b5ede8bd9a27a448dd1b56ca0beb534821532a0a8f94abfae979025eedf79bf09d46edcb3945aec4d39814ff9496abe7d7290b5a7ec2805aba7f1aa2cd1372ee01b08ec80d4e261be65e56706d3cdcad34ee48cab6cbf3759f572bf37be3fbe073e5f8a1231a893cb5de0fbf3a263bf81f83099e65266a4c7001e491126d6dba91dba73066b885e816269dafa4dcf3ce0001629a66f84c9ac6745da2c500c3b7adf669b0e58ea9d1e1079986e48a3f8f4e1f13cb652a3133dcb987c9bc3389b3fcc768100d993a757f4b4bdb8afd938b52ac7c5d61b0f6ddff386f2e4d912d83a2912fd2706023f40c46e7aca9ab04bfcd28d32dc021f556f640c51f7e4ebf359f952f05632aa849d97ecacc4f530f41264dafd11ad98db8023ba1a37ba3b7e85506739a52bef589a5d31559729be39b0f9e907c42ff7baeb03dff2d9aed0f0b648992aca08d58d5a74edc69d3892b3f2da6b719411b8adf55e8d8e6b24e40476c28a66bf4b9993920c4054c63c17a27451c1590bdb1330b1406f0e527bc9d9aae5ccfe0a023c7226f4b4c241db936a8c1f1002aa6d0d53da8b7e3a9af8b3a03015678abb07cabf06ccc9412fa3a4a6d1ba80a70841591bd300a9cf0044a3ad2c80a0ead283cb856137b6625c8e7dba6b5435085ebc2f82015dc60e17260e58a1edc7f11456780b6d2cff008b1d39a4c712c9eaf0841dbce5f3afa0512c4ff1009124e78e624a60760ab864c4dc1bbac03d2a3514b95cc9dd0930f6342aa895253cfd566221379818e52acf7cd1bd81102f03bc746717c90da817ad3512cd04223c8c0264e1c132c1ba99bdc393c6bf6f1e0c3fd9606392a586aeb832d47ef3443f471e8aadc53940ee7af5bd09d4cdb902bec979494423797ceb224e3835aab7d83dd3ff30e9ddd2bfb21f3c875011e3d057ea216fe67c429def8efbde1cbbc4a817d2eff6515c8d41fc4437e4c557fed3e11f301a2176d9a9975dfa7bb26b5a01ddb2dd2a029ad768b35b9ffd0c0182b1363715aa3712e2ac4d9e4639dbe1706c87ac633520a33f561bff60b47c5348e444f31dcb5f29f8c91b472abbbe11bba21d0708cc00c295baaff6deb696b6a51b17929ea32968f135e3cdbb8eb2409389106ff82a9d16449e99f5ca66a08fb647245f1c67860a1af24b2dabafd651f8e465f7cb724505aa6af55d00f8c24b7e4abaa09b8ba863880df79122b3a1f278c3612ae87d893cc9e64c38efa5600f9b88d01139a5fb90dbcf5853da2bf6f277d1bd7307b60f45e0286761364fde5cf15df78df87cbc6d84d8f42aac9582c64de72c710f3ae897eede27990d1821c8b0e8f66bbe58ebca1483ba5532e9cc84139961597f385b73f71673215c1df7dc53ad7a7cda73358bc2b79c7679ace10c262de19a6ee8e268b892b55b5dcf7215e9aa0947415b7060a85d5e4474ed98b5109452a8ad06f1afbf3b9330d66e2b5f2a17551d78d7f00f2dd3154074770f7b2a15bd82750a57ee1e75f8ea299ca50ff1ad4b05b5761b2cf7aca695cfb4789ec59d7ad60cb68d5d4b589c3d98784313d7b739ba6aa7f87bfec47b5631b9b4b5538f79347f9a5810b5dc06f35a91fe03b4119c8a34ec656fa66795717976edf0e3139a379b97def36bc47e7d6396724459212cc2fcd7dd7bb937939ab2c9b8c60996d534b2161920a6c3b1e006104080646182867139100115ff5ad598967e90337e31be254ab62c643cc38c43d9caafe25c15b831c0360cbd6e981a7a9ebcb32b16cc9d1f8942cd64d060823a54f4ff446342c2ba1c85b4787f5a3f838710751ed280d1de49f2f511707bdde67b9d7e61d21d6708bdaa2a962f0b16c6067320a6b2d6b6348abdf9adf4ca9ff0adad3709a4495835811869970b1028864c971b44eb4f5209b461dc916d627329bcac943329b759578bf7eea4e5fb7162565759ccc157100d37efeb0a6590e1807d29b1a6db569eceb58768a6bcce0988eee5441b362db819b545bbfa13af723aec5e9ef9ffad8ab78371d7c962c6a08519dc841fc0390804f45cb168c98fabbc1aa7ed77091c9f971497faddc6c1f81038f5ef585c191d162ada31e1c6086dca1ea0b7eceb1d4740765241de5baee0fce2dd819f39ab54bf20af4fe49cc8b96d5d0159ab8a292233ceb7619ebf2cb01d984f5e5565e9706e039c0dd83c4d0796e85008d8a24f271d0b43713a7ff8817a8d9ea726e01033d6a314a34a1d7386ab9a9338b8570370a279d2a9d729ec24d19627a693ca47f8864574b329e55e560fd19860ccf024280af1803a4b1b9303a072eccdcfc319337ac7ed1b4003f7589949493c0f9019da27d9006674093882a9a61f5a29c19581d50df32d9407f1c1ab1b9e02a27657e547861f4b7916ffe6b86cb8ba96a05826d250cb88b1184e28a7dd9620217740ac1c697b8033c8cb7f13d65c178ace97ba8f772c14f094d1c99740498881bca7820709de94c2b452b87c7922bff7c4626ff58841b0f6096f1e5fe1b6e7dffc6e196e7defa94b26c204cf7216c2e061985a38ee880897b65dffd29a3e18c532b74d9eefd9ab47ba1882969a2db6460d7116a97da69d7fcc096cc6ad06d858814d58d3d75a2e29d4b109e6cac09e80c7586e1e6dd94e01d9a555a533aa0679dd504fca015aa43c6d8e2cea29ac4b3b4dddbfd87634033cf254c2584ca6010e6bc885cd13000ea8d2a1fdc3fd867834d374571c52670be4950e9a811627126a36feed5e51ef85fa57474b64e8ab072a3359b0307a9b18f9e891777b2fe1c6a8959c07d109c80499d1d20fdfb6b7e3fef3a073c4549fe1a38d645cdb699c9030ffd390e8016fed7b7198a8c7393313199822a665048aa979fb005ada5a52c492b3ba58486ec967325e212bf499ca26e6d1df4dba2e40d9622e45e58b282858dbf2836518dc9afe5b08769bf5f311</script> <div class="hbe hbe-content"> <div class="hbe hbe-input hbe-input-default"> <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass"> <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass"> <span class="hbe hbe-input-label-content hbe-input-label-content-default">Hey, password is required here.</span> </label> </div> </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html"><div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOP</summary>
<category term="个人日志" scheme="http://luhao.wiki/categories/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/"/>
<category term="个人日志" scheme="http://luhao.wiki/categories/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/%E4%B8%AA%E4%BA%BA%E6%97%A5%E5%BF%97/"/>
<category term="Python" scheme="http://luhao.wiki/tags/Python/"/>
<category term="C++" scheme="http://luhao.wiki/tags/C/"/>
</entry>
</feed>