-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
1484 lines (1484 loc) · 423 KB
/
search.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
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>《Deep Learning for Rendering》阅读笔记</title>
<url>/2022/07/27/DL%20for%20Render%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/</url>
<content><![CDATA[<p>中科大第九届《计算机图形学前沿》暑期课程</p>
<p>主讲人:过洁</p>
<p>原视频地址:<a href="https://www.bilibili.com/video/BV1Kf4y157WW?p=17&vd_source=e4b21da575ef85d87ce1b8e9f64ca190">https://www.bilibili.com/video/BV1Kf4y157WW?p=17&vd_source=e4b21da575ef85d87ce1b8e9f64ca190</a></p>
<span id="more"></span>
<h3 id="一、渲染"><a href="#一、渲染" class="headerlink" title="一、渲染"></a>一、渲染</h3><p> 场景里的几何、材质、光照等通过一个渲染器,生成一张图片</p>
<h4 id="几何"><a href="#几何" class="headerlink" title="几何"></a>几何</h4><p>三角网格(传统)、体素、点云</p>
<p><img src="https://s2.loli.net/2022/07/26/siXuLP1tWjwrgET.png" alt="image-20220619150852443" style="zoom:67%;"></p>
<p>三角网格在复杂场景中数据量巨大</p>
<h4 id="材质"><a href="#材质" class="headerlink" title="材质"></a>材质</h4><p>材质 ≠ 纹理</p>
<p>描述 光线打到物体上后经过反射、折射等各种交互,出来后的形态各种各样</p>
<h4 id="光源"><a href="#光源" class="headerlink" title="光源"></a>光源</h4><p>次生光线、递归迭代</p>
<p>渲染中的某些场景元素(信号)的特点</p>
<ul>
<li>高纬度(High dimensionality)</li>
<li>奇点 (Singularity)</li>
<li>多模态 (Multi-modality)—网格、体素、点云</li>
<li>数据量巨大 (Huge data size)</li>
</ul>
<p>导致的问题:渲染很困难</p>
<h3 id="二、深度学习中的渲染"><a href="#二、深度学习中的渲染" class="headerlink" title="二、深度学习中的渲染"></a>二、深度学习中的渲染</h3><h4 id="深度学习中的功能"><a href="#深度学习中的功能" class="headerlink" title="深度学习中的功能"></a>深度学习中的功能</h4><ul>
<li>处理高纬度和多模态的数据(高纬度可以压缩、多模态可以多模态融合)</li>
<li>神经网络比某些人手写的公式更好、更快、更鲁棒</li>
<li>可以在很复杂的问题中探索 low-rank 特征</li>
</ul>
<h4 id="深度学习在渲染中的两个角色"><a href="#深度学习在渲染中的两个角色" class="headerlink" title="深度学习在渲染中的两个角色"></a>深度学习在渲染中的两个角色</h4><p>1、深度学习可以学习某种表征来对复杂场景元素进行编码</p>
<ul>
<li>对几何材质光源进行抽象</li>
<li>可在 forward rendering 和 inverse rendering 中使用</li>
</ul>
<p>2、学习一个domain到其他domain 的映射函数</p>
<ul>
<li>图片到图片</li>
<li>传统方法是从手工推出的函数</li>
</ul>
<h4 id="第一个角色"><a href="#第一个角色" class="headerlink" title="第一个角色"></a>第一个角色</h4><p>学习一个 表示(representation)</p>
<p>可学习对象:几何、材质、光源、场景</p>
<h5 id="(1)几何"><a href="#(1)几何" class="headerlink" title="(1)几何"></a>(1)几何</h5><p>用神经网络表示各种各样的几何体</p>
<p>在场景中表示几何:</p>
<ul>
<li>多视角图片</li>
<li>Signed distance function (SDF)三维重建</li>
<li>稠密体素</li>
<li>稀疏体素</li>
<li>点云</li>
<li>网格</li>
<li>……</li>
</ul>
<p><img src="https://s2.loli.net/2022/07/26/ZPLiWTC6mK57Njk.png" alt="image-20220619153326564" style="zoom:80%;"></p>
<p><img src="https://s2.loli.net/2022/07/26/nvHthz87Erx9fUD.png" alt="image-20220619153443878" style="zoom:80%;"></p>
<p>用网络去处理 mesh 的很少</p>
<p><img src="https://s2.loli.net/2022/07/26/9iJXat7hTjRKyYU.png" alt="image-20220619153520455" style="zoom:80%;"></p>
<h5 id="(2)材质"><a href="#(2)材质" class="headerlink" title="(2)材质"></a>(2)材质</h5><p>用神经网络去表示材质</p>
<p>材质可以抽象成一个函数,包含16个变量(入射、出射光方向、时间)</p>
<p><img src="https://s2.loli.net/2022/07/26/ec4HV9loQOugvbp.png" alt="image-20220619153743048" style="zoom: 80%;"></p>
<ul>
<li>用经验模型简化材质</li>
<li>直接采集:<ul>
<li>密集采样,</li>
<li>移动相机光源采样、</li>
<li>将数据保存在一个table里面。</li>
<li>耗内存、速度慢</li>
</ul>
</li>
</ul>
<p>措施:压缩、降维</p>
<p>用深度学习去压缩材质</p>
<p>DeepBRDF : </p>
<p><img src="https://s2.loli.net/2022/07/26/aAd5OT3LMYKQjeR.png" alt="image-20220619154412703" style="zoom: 80%;"></p>
<p>latent space :潜在空间、隐空间</p>
<p>参考: <a href="https://zhuanlan.zhihu.com/p/369946876">https://zhuanlan.zhihu.com/p/369946876</a></p>
<ul>
<li>潜在空间只是压缩数据的表示,其中相似的数据点在空间上更靠近在一起。</li>
<li>潜在空间对于学习数据功能和查找更简单的数据表示形式以进行分析很有用。</li>
<li>我们可以通过分析潜在空间中的数据(通过流形,聚类等)来了解数据点之间的模式或结构相似性。</li>
<li>我们可以在潜在空间内插值数据,并使用模型的解码器来“生成”数据样本。</li>
<li>我们可以使用t-SNE和LLE之类的算法来可视化潜在空间,该算法将我们的潜在空间表示形式转换为2D或3D。</li>
</ul>
<p>将 BRDF Slice作为输入</p>
<p><img src="https://s2.loli.net/2022/07/26/ujit8nwPaBczkpy.png" alt="image-20220619154908240" style="zoom:80%;"></p>
<p>BRDF 不考虑材质的表面变化,若考虑表面变化还有一些光照效果等,我们采用 BTF</p>
<p>BTF: Bidirectional Texture Function</p>
<p><img src="https://s2.loli.net/2022/07/26/AsGQZ5ESu2Kw4Vx.png" alt="image-20220619155652279" style="zoom:80%;"></p>
<p>对 SVBRDF 进行压缩</p>
<p><img src="https://s2.loli.net/2022/07/26/t5hyQjx6GVYLmBz.png" alt="image-20220619163330460"></p>
<p><img src="https://s2.loli.net/2022/07/26/Edkq21cTwmauA6U.png" alt="image-20220619163830172" style="zoom:80%;"></p>
<p>用神经网络去压缩材质空间,这个工作远没有结束</p>
<p><strong>【三维体素型材质】</strong>:玉石、烟雾、半透明物体</p>
<p><img src="https://s2.loli.net/2022/07/26/Ww7fTkAgjhUPlDL.png" alt="image-20220619163930515" style="zoom:80%;"></p>
<p>借鉴风格迁移</p>
<p><img src="https://s2.loli.net/2022/07/26/oVrcLBdASTzpWy9.png" alt="image-20220619164041610" style="zoom:80%;"></p>
<p>只是风格相似,不一定是完全match</p>
<p><img src="https://s2.loli.net/2022/07/26/JQPgB89dzIsfnWR.png" alt="image-20220619164444074" style="zoom:80%;"></p>
<p>对于任何一张图片,可以抽取其风格,然后迁移到三维物体上</p>
<h5 id="(3)用神经网络表示光源"><a href="#(3)用神经网络表示光源" class="headerlink" title="(3)用神经网络表示光源"></a>(3)用神经网络表示光源</h5><p><img src="https://s2.loli.net/2022/07/26/stSf6JbiqdkVARc.png" alt="image-20220619164521562"></p>
<p>环境光也是一种高维信号,同样的思想,采用神经网络去压缩,压缩形成的latent space并不是用于渲染,而是用于光照估计。</p>
<p><img src="https://s2.loli.net/2022/07/26/SNwBvZG1YWsFq48.png" alt="image-20220619164717253" style="zoom:80%;"></p>
<p>先用神经网路抽取一个室外全景图的latent space,可以用于光照估计,输入一张图,去预测光照,先预测latent code,然后再用decode去恢复这张环境图。</p>
<p>应用:把虚拟物体插入到真实环境下</p>
<p><img src="https://s2.loli.net/2022/07/26/W3BxpHyi4NdASTm.png" alt="image-20220619164957649" style="zoom:80%;"></p>
<p>这项工作在深度学习里有个非常重要的思想:disentanglement</p>
<p>刚才讲到是室外光,那么室内光是否也可以用auto-encode、GAN等进行压缩?</p>
<p>答案是不行,室内光过于复杂,难以压缩。</p>
<p><img src="https://s2.loli.net/2022/07/26/A9wceK8X34Ms7EJ.png" alt="image-20220619165815866" style="zoom:80%;"></p>
<p>该项工作把光源压缩到一个神经网络上</p>
<h5 id="(4)场景"><a href="#(4)场景" class="headerlink" title="(4)场景"></a>(4)场景</h5><p>那么是否可以直接对整个场景,不分几何材质光照,进行压缩?</p>
<p> <strong>Neural scene</strong></p>
<p><img src="https://s2.loli.net/2022/07/26/pCl8LxNBkGfgwFj.png" alt="image-20220619170013686"></p>
<p>限制:容易丢失细节</p>
<h4 id="第二个角色:-Mapping-映射"><a href="#第二个角色:-Mapping-映射" class="headerlink" title="第二个角色: Mapping 映射"></a>第二个角色: Mapping 映射</h4><p>从一个 domain 到另一个 domain</p>
<p><img src="https://s2.loli.net/2022/07/26/bnFoW63Sc5E8TYm.png" alt="image-20220619170931536" style="zoom:80%;"></p>
<ul>
<li>去噪</li>
<li>超分</li>
<li>直接光—间接光</li>
<li>渲染图片—材质贴图</li>
</ul>
<h5 id="(1)去噪"><a href="#(1)去噪" class="headerlink" title="(1)去噪"></a>(1)去噪</h5><p><img src="https://s2.loli.net/2022/07/26/SprU5tKHXZAwLDx.png" alt="image-20220619171826353"></p>
<ul>
<li>漫反射-镜面反射的分解</li>
<li>G-buffer</li>
<li>无限的训练样本(理论上)</li>
<li>解调 Demodulation (图象 / albedo)</li>
</ul>
<p>在无监督去噪中,loss是关键</p>
<p><img src="https://s2.loli.net/2022/07/26/LxShwJz81ZV523B.png" alt="image-20220619172234941" style="zoom:80%;"></p>
<h5 id="(2)超分"><a href="#(2)超分" class="headerlink" title="(2)超分"></a>(2)超分</h5><p>典例:DLSS、Neual SS</p>
<p><img src="https://s2.loli.net/2022/07/26/KzS6Nf3WbpeGrwk.png" alt="image-20220619172322173"></p>
<p><img src="https://s2.loli.net/2022/07/26/sBYl1rdX8ZzP59Q.png" alt="image-20220619172333668"></p>
<h5 id="(3)从直接光照预测间接光照"><a href="#(3)从直接光照预测间接光照" class="headerlink" title="(3)从直接光照预测间接光照"></a>(3)从直接光照预测间接光照</h5><p><img src="https://s2.loli.net/2022/07/26/CE6QdXzFxOZi2pr.png" alt="image-20220619172447406" style="zoom:80%;"></p>
<p>该方向的研究较少</p>
<p>直接光照很快,间接光照很慢</p>
<h5 id="(4)从渲染图片出发,反推场景元素"><a href="#(4)从渲染图片出发,反推场景元素" class="headerlink" title="(4)从渲染图片出发,反推场景元素"></a>(4)从渲染图片出发,反推场景元素</h5><p><img src="https://s2.loli.net/2022/07/26/wedM7pWRiGBCVg8.png" alt="image-20220619172605439"></p>
<p>从单张图片恢复 各种光照贴图,对于光泽效果的呈现尤为重要</p>
]]></content>
<categories>
<category>CG</category>
</categories>
<tags>
<tag>CG</tag>
<tag>笔记</tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<url>/2022/07/27/hello-world/</url>
<content><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><span id="more"></span>
<h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>
]]></content>
</entry>
<entry>
<title>【学习记录】Ray Tracing in One Weekend</title>
<url>/2022/09/10/%E3%80%90%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%91Ray%20Tracing%20in%20One%20Weekend/</url>
<content><![CDATA[<p><img src="https://s2.loli.net/2022/09/09/RLE6mcZ79OjhJHf.png" alt="image-20220909102129774" style="zoom:67%;"></p>
<p>之前完成的光追是依靠GAMES101中闫老师提供的一个完成度非常高的框架实现的,自己也就补充完成了几个函数,虽然最后也成功渲染出了一张图,但是总觉得只是学会了光追的中的重要的几个点,而对全局的架构理解不深。这门课程虽然仅仅只是实现了最基础的光追,但却是从零开始逐步完善整个项目工程,同样令我收获颇丰。</p>
<p>原文地址: <a href="https://raytracing.github.io/books/RayTracingInOneWeekend.html">https://raytracing.github.io/books/RayTracingInOneWeekend.html</a></p>
<span id="more"></span>
<h3 id="一、原理概述"><a href="#一、原理概述" class="headerlink" title="一、原理概述"></a>一、原理概述</h3><p>渲染主要考虑每一个物体对每一个像素点的影响,它主要包括两种普遍的方式:</p>
<p><strong>1、物体顺序渲染(object-order rendering)</strong></p>
<p> 依次考虑每一个物体,并且对于每一个物体,我们会查找它影响了哪些像素的值并更新这些像素值</p>
<p><strong>2、图像顺序渲染(image-order rendering)</strong></p>
<p>轮流考虑每一个像素,对于每一个像素,查找所有会影响它的物体,计算最终的像素值。</p>
<p>光线追踪就是一种根据图像顺序渲染的方式。</p>
<p>基本的光线追踪其包含三个部分:</p>
<ol>
<li><p><strong>光线生成 ray generation</strong>,基于相机几何学计算每一个像素上”光线“的起点和方向</p>
</li>
<li><p><strong>光线求交 ray intersection</strong>,找到光线和最靠近物体的交点</p>
</li>
<li><p><strong>着色 shading</strong> ,通过光线求交的结果和其它信息计算像素颜色</p>
</li>
</ol>
<p>基本的光线追踪算法的算法流程为</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">对于每一个像素点:</span><br><span class="line"> 从每一个像素发射一系列的采样光线,计算观察方向发出的光线</span><br><span class="line"> 计算每一条采样光线在场景中与物体发生的折射反射,将颜色累加</span><br><span class="line"> 将上述颜色取平均值作为一个像素对应的颜色值</span><br></pre></td></tr></table></figure>
<p>具体执行的大致流程如下:</p>
<p>(1)设置一些参数,如像素宽高,每个像素采样次数,递归深度等</p>
<p>(2)对于每一个像素使用随机函数打出多条随即方向的光线</p>
<p>(3)每条光线都通过 ray_color() 这个函数与场景中的物体进行碰撞,发生反射、折射</p>
<p>(4)根据物体的材质计算反射光线,递归计算光线与物体碰撞得到的颜色</p>
<p>(5)将上述得到的颜色值累加起来写入当前像素的位置</p>
<p>(6)循环重复步骤(2) —(5)</p>
<p>(7)将颜色值除以光线个数取平均,有时还需用到Gamma矫正</p>
<h3 id="二、教程的几个关键步骤"><a href="#二、教程的几个关键步骤" class="headerlink" title="二、教程的几个关键步骤"></a>二、教程的几个关键步骤</h3><h4 id="1、对于光线的定义"><a href="#1、对于光线的定义" class="headerlink" title="1、对于光线的定义"></a>1、对于光线的定义</h4><p><img src="https://s2.loli.net/2022/09/09/SzyJYRswZxMEQ9H.png" alt="image-20220907232615527" style="zoom:50%;"></p>
<script type="math/tex; mode=display">
\vec{Ray} = O + t * \vec{Dir}</script><ul>
<li>光线是一个三维向量</li>
<li><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex;" xmlns="http://www.w3.org/2000/svg" width="1.726ex" height="1.643ex" role="img" focusable="false" viewBox="0 -704 763 726"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g></g></g></svg></mjx-container> 为光线起始位置,是一个三维向量</li>
<li><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex;" xmlns="http://www.w3.org/2000/svg" width="0.817ex" height="1.441ex" role="img" focusable="false" viewBox="0 -626 361 637"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></svg></mjx-container> 为光线起始位置到光线打到物体上的点的距离,是一维标量</li>
<li><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex;" xmlns="http://www.w3.org/2000/svg" width="3.674ex" height="1.57ex" role="img" focusable="false" viewBox="0 -683 1624 694"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D437" d="M287 628Q287 635 230 637Q207 637 200 638T193 647Q193 655 197 667T204 682Q206 683 403 683Q570 682 590 682T630 676Q702 659 752 597T803 431Q803 275 696 151T444 3L430 1L236 0H125H72Q48 0 41 2T33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM703 469Q703 507 692 537T666 584T629 613T590 629T555 636Q553 636 541 636T512 636T479 637H436Q392 637 386 627Q384 623 313 339T242 52Q242 48 253 48T330 47Q335 47 349 47T373 46Q499 46 581 128Q617 164 640 212T683 339T703 469Z"></path></g><g data-mml-node="mi" transform="translate(828,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1173,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g></g></g></svg></mjx-container> 为光线的方向,是一个三维向量</li>
</ul>
<h4 id="2、光线与球体求交"><a href="#2、光线与球体求交" class="headerlink" title="2、光线与球体求交"></a>2、光线与球体求交</h4><p>该教程的场景全都是由球体构成,那些看起来像地面的物体是一个超大半径的球体,所以该教程与物体有关的碰撞求教基本上是与球体之间的碰撞。具体体现在如下函数中:</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">double</span> <span class="title">hit_sphere</span><span class="params">(<span class="type">const</span> point3 &center, <span class="type">double</span> radius, <span class="type">const</span> ray &r)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> vec3 oc = r.<span class="built_in">origin</span>() - center;</span><br><span class="line"> <span class="keyword">auto</span> a = <span class="built_in">dot</span>(r.<span class="built_in">direction</span>(), r.<span class="built_in">direction</span>());</span><br><span class="line"> <span class="keyword">auto</span> b = <span class="number">2.0</span> * <span class="built_in">dot</span>(oc, r.<span class="built_in">direction</span>());</span><br><span class="line"> <span class="keyword">auto</span> c = <span class="built_in">dot</span>(oc, oc) - radius * radius;</span><br><span class="line"> <span class="keyword">auto</span> discriminant = b * b - <span class="number">4</span> * a * c;</span><br><span class="line"> <span class="keyword">if</span> (discriminant < <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1.0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">return</span> (-b - <span class="built_in">sqrt</span>(discriminant)) / (<span class="number">2.0</span> * a);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>该函数接受三个参数</p>
<ul>
<li>与光线碰撞的球体的中心点 center</li>
<li>与光线碰撞的球体的半径 radius</li>
<li>光线 r</li>
</ul>
<p><img src="https://s2.loli.net/2022/09/09/NagPncTemUoMJIX.png" alt="image-20220908003512493" style="zoom:67%;"></p>
<p>教程中对于光线与球体是否有交点是根据二次方程是否有解来判断的,有三个注意点:如果两个解中更小的解在区间内,那么更小的解是交点,体现在:</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">return</span> (-b - <span class="built_in">sqrt</span>(discriminant)) / (<span class="number">2.0</span> * a);</span><br></pre></td></tr></table></figure>
<p>和</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">auto</span> root = (-half_b - sqrtd) / a;</span><br><span class="line"><span class="keyword">if</span> (root < t_min || t_max < root) </span><br><span class="line">{</span><br><span class="line"> root = (-half_b + sqrtd) / a;</span><br><span class="line"> <span class="keyword">if</span> (root < t_min || t_max < root)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>否则,如果更大的解在区间内,那么更大的解是交点。否则没有交点。</p>
<h4 id="3、对可命中对象进行抽象"><a href="#3、对可命中对象进行抽象" class="headerlink" title="3、对可命中对象进行抽象"></a>3、对可命中对象进行抽象</h4><p>建立一个名为hittable的抽象类</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">hittable</span> </span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">bool</span> <span class="title">hit</span><span class="params">(<span class="type">const</span> ray& r, <span class="type">double</span> t_min, <span class="type">double</span> t_max, hit_record& rec)</span> <span class="type">const</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>该类拥有一个 hit 函数,该函数接受如下参数:</p>
<ul>
<li><p>光线 r</p>
</li>
<li><p>碰撞最短距离 t_min</p>
</li>
<li><p>碰撞最长距离 t_max</p>
</li>
<li><p>碰撞记录 rec</p>
<p>碰撞记录存放着与光线碰撞的物体上的点的相关信息,包括碰撞处点的位置、法线(由球心指向外)、光线移动的距离。</p>
</li>
</ul>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">hit_record</span></span><br><span class="line">{</span><br><span class="line"> point3 p; </span><br><span class="line"> vec3 normal;</span><br><span class="line"> <span class="type">double</span> t;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>之后便可以让sphere类继承该碰撞类,并覆写 hit 函数</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">sphere::hit</span><span class="params">(<span class="type">const</span> ray& r, <span class="type">double</span> t_min, <span class="type">double</span> t_max, hit_record& rec)</span> <span class="type">const</span> </span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// 光线起始方向到球心的距离</span></span><br><span class="line"> vec3 oc = r.<span class="built_in">origin</span>() - center;</span><br><span class="line"> <span class="comment">// 通过二次方程是否有解判断是否发生碰撞</span></span><br><span class="line"> <span class="keyword">auto</span> a = r.<span class="built_in">direction</span>().<span class="built_in">length_squared</span>();</span><br><span class="line"> <span class="keyword">auto</span> half_b = <span class="built_in">dot</span>(oc, r.<span class="built_in">direction</span>());</span><br><span class="line"> <span class="keyword">auto</span> c = oc.<span class="built_in">length_squared</span>() - radius*radius;</span><br><span class="line"> <span class="keyword">auto</span> discriminant = half_b*half_b - a*c;</span><br><span class="line"> <span class="keyword">if</span> (discriminant < <span class="number">0</span>) </span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">auto</span> sqrtd = <span class="built_in">sqrt</span>(discriminant);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 求出在位于区间内的最近的那个解</span></span><br><span class="line"> <span class="comment">// 如果两个解中更小的解在区间内,那么更小的解是交点</span></span><br><span class="line"> <span class="comment">// 否则,如果更大的解在区间内,那么更大的解是交点。</span></span><br><span class="line"> <span class="comment">// 否则没有交点。</span></span><br><span class="line"> <span class="keyword">auto</span> root = (-half_b - sqrtd) / a;</span><br><span class="line"> <span class="keyword">if</span> (root < t_min || t_max < root) {</span><br><span class="line"> root = (-half_b + sqrtd) / a;</span><br><span class="line"> <span class="keyword">if</span> (root < t_min || t_max < root)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若有交点,则将该交点记录保存在 hit_record中</span></span><br><span class="line"> rec.t = root;</span><br><span class="line"> rec.p = r.<span class="built_in">at</span>(rec.t);</span><br><span class="line"> rec.normal = (rec.p - center) / radius; <span class="comment">// 对法向量进行归一化</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后建立一个的列表类 <em>hittable_list</em> ,存储可命中对象</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> std::shared_ptr; </span><br><span class="line"><span class="keyword">using</span> std::make_shared;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">hittable_list</span> : <span class="keyword">public</span> hittable {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">hittable_list</span>() {}</span><br><span class="line"> <span class="built_in">hittable_list</span>(shared_ptr<hittable> object) { <span class="built_in">add</span>(object); }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">clear</span><span class="params">()</span> </span>{ objects.<span class="built_in">clear</span>(); }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(shared_ptr<hittable> object)</span> </span>{ objects.<span class="built_in">push_back</span>(object); }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">bool</span> <span class="title">hit</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">const</span> ray& r, <span class="type">double</span> t_min, <span class="type">double</span> t_max, hit_record& rec)</span> <span class="type">const</span> <span class="keyword">override</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> std::vector<shared_ptr<hittable>> objects;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">hittable_list::hit</span><span class="params">(<span class="type">const</span> ray& r, <span class="type">double</span> t_min, <span class="type">double</span> t_max, hit_record& rec)</span> <span class="type">const</span> </span>{</span><br><span class="line"> hit_record temp_rec;</span><br><span class="line"> <span class="type">bool</span> hit_anything = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">auto</span> closest_so_far = t_max;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">const</span> <span class="keyword">auto</span>& object : objects) {</span><br><span class="line"> <span class="keyword">if</span> (object-><span class="built_in">hit</span>(r, t_min, closest_so_far, temp_rec)) {</span><br><span class="line"> hit_anything = <span class="literal">true</span>;</span><br><span class="line"> closest_so_far = temp_rec.t;</span><br><span class="line"> rec = temp_rec;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> hit_anything;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>该类使用了智能指针(shared_ptr),会记录有多少个 shared_ptr 共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。具体可见 <a href="https://blog.csdn.net/shaosunrise/article/details/85228823">C++ 智能指针 shared_ptr 详解与示例</a></p>
<h4 id="4、关于抗锯齿"><a href="#4、关于抗锯齿" class="headerlink" title="4、关于抗锯齿"></a>4、关于抗锯齿</h4><p><strong>主要方法:</strong></p>
<p>1、在采样时使用随机函数</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">auto</span> u = (i + <span class="built_in">random_double</span>()) / (image_width - <span class="number">1</span>);</span><br><span class="line"><span class="keyword">auto</span> v = (j + <span class="built_in">random_double</span>()) / (image_height - <span class="number">1</span>);</span><br></pre></td></tr></table></figure>
<p>2、对颜色多次采样,最后取平均值</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> samples_per_pixel = <span class="number">500</span>;</span><br><span class="line">...</span><br><span class="line"><span class="keyword">auto</span> scale = <span class="number">1.0</span> / samples_per_pixel;</span><br></pre></td></tr></table></figure>
<h4 id="5、漫反射的材质"><a href="#5、漫反射的材质" class="headerlink" title="5、漫反射的材质"></a>5、漫反射的材质</h4><p><img src="https://s2.loli.net/2022/09/09/Yabg7S5BcmQOhj1.png" alt="image-20220908004652800" style="zoom:67%;"></p>
<p>上图表示两个单位球在 <strong>P</strong> 点处相切,<strong><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="2.009ex" height="2.457ex" role="img" focusable="false" viewBox="0 -1086 888 1086"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="TeXAtom" data-mjx-texclass="ORD"><g data-mml-node="mover"><g data-mml-node="mi"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g><g data-mml-node="mo" transform="translate(548.6,272) translate(-250 0)"><path data-c="20D7" d="M377 694Q377 702 382 708T397 714Q404 714 409 709Q414 705 419 690Q429 653 460 633Q471 626 471 615Q471 606 468 603T454 594Q411 572 379 531Q377 529 374 525T369 519T364 517T357 516Q350 516 344 521T337 536Q337 555 384 595H213L42 596Q29 605 29 615Q29 622 42 635H401Q377 673 377 694Z"></path></g></g></g></g></g></svg></mjx-container></strong> 为表面法线。两个单位球的球心为 (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.186ex;" xmlns="http://www.w3.org/2000/svg" width="6.474ex" height="1.731ex" role="img" focusable="false" viewBox="0 -683 2861.4 765"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(973.2,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z"></path></g><g data-mml-node="mi" transform="translate(1973.4,0)"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g></g></g></svg></mjx-container>)与 (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.186ex;" xmlns="http://www.w3.org/2000/svg" width="6.474ex" height="1.731ex" role="img" focusable="false" viewBox="0 -683 2861.4 765"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(973.2,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mi" transform="translate(1973.4,0)"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g></g></g></svg></mjx-container>),我们将球心为 (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.186ex;" xmlns="http://www.w3.org/2000/svg" width="6.474ex" height="1.731ex" role="img" focusable="false" viewBox="0 -683 2861.4 765"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(973.2,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z"></path></g><g data-mml-node="mi" transform="translate(1973.4,0)"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g></g></g></svg></mjx-container>)的球视为表面外的球,在该单位求内随机取一点 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex;" xmlns="http://www.w3.org/2000/svg" width="1.459ex" height="1.645ex" role="img" focusable="false" viewBox="0 -705 645 727"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D446" d="M308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24Z"></path></g></g></g></svg></mjx-container> ,从 <strong>P</strong> 点出发出一条光线,方向为 (<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.186ex;" xmlns="http://www.w3.org/2000/svg" width="5.924ex" height="1.781ex" role="img" focusable="false" viewBox="0 -705 2618.4 787"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D446" d="M308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24Z"></path></g><g data-mml-node="mo" transform="translate(867.2,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mi" transform="translate(1867.4,0)"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g></g></g></svg></mjx-container>),表示发生漫反射时随机反射出去的一条光线。</p>
<p>光线不可能无止境的递归反射下去,所以我们还需要确立一个最大反射次数用于终止递归</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> max_depth = <span class="number">50</span>;</span><br><span class="line"></span><br><span class="line"><span class="function">color <span class="title">ray_color</span><span class="params">(<span class="type">const</span> ray& r, <span class="type">const</span> hittable& world, <span class="type">int</span> depth)</span> </span>{</span><br><span class="line"><span class="keyword">if</span> (depth <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">color</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>);</span><br><span class="line"> <span class="comment">// ..............</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>示例效果如下:</p>
<p><img src="https://s2.loli.net/2022/09/09/8OUx5Lz2vj6Fy1X.png" alt="image-20220908011724278" style="zoom:67%;"></p>
<h4 id="6、金属材质"><a href="#6、金属材质" class="headerlink" title="6、金属材质"></a>6、金属材质</h4><p>对于光滑金属,光线不会随机散射</p>
<p><img src="https://s2.loli.net/2022/09/09/on1GSKkCVt74x2s.png" alt="image-20220908010855254" style="zoom:67%;"></p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 上图对应的反射代码如下</span></span><br><span class="line"><span class="function">vec3 <span class="title">reflect</span><span class="params">(<span class="type">const</span> vec3& v, <span class="type">const</span> vec3& n)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> v - <span class="number">2</span>*<span class="built_in">dot</span>(v,n)*n;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>对于金属材质,创建对应的材质类:</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">metal</span> : <span class="keyword">public</span> material </span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">metal</span>(<span class="type">const</span> color& a) : <span class="built_in">albedo</span>(a) {}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">bool</span> <span class="title">scatter</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">const</span> ray& r_in, <span class="type">const</span> hit_record& rec, color& attenuation, ray& scattered</span></span></span><br><span class="line"><span class="params"><span class="function"> )</span> <span class="type">const</span> <span class="keyword">override</span> </span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> vec3 reflected = <span class="built_in">reflect</span>(<span class="built_in">unit_vector</span>(r_in.<span class="built_in">direction</span>()), rec.normal);</span><br><span class="line"> scattered = <span class="built_in">ray</span>(rec.p, reflected);</span><br><span class="line"> attenuation = albedo;</span><br><span class="line"> <span class="comment">// 若散射方向与法线夹角小于90°,说明发生了正确的反射</span></span><br><span class="line"> <span class="keyword">return</span> (<span class="built_in">dot</span>(scattered.<span class="built_in">direction</span>(), rec.normal) > <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> color albedo;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>示例效果如下:</p>
<p><img src="https://s2.loli.net/2022/09/09/A39cdUPRyioBJjN.png" alt="image-20220908011704309"></p>
<h3 id="三、成果"><a href="#三、成果" class="headerlink" title="三、成果"></a>三、成果</h3><p><sup><a href="#fn_格式问题" id="reffn_格式问题">格式问题</a></sup>:尝试了多种网页在线转换和python代码都没法把 .ppm 格式的文件转换成 .jpg,只好直接截图了。</p>
<p>按照教程上的代码渲染出的图,花费了一晚上才渲染好</p>
<p><img src="https://s2.loli.net/2022/09/09/w4CmaTXrHFh9Rcx.png" alt="image-20220909102013774" style="zoom:80%;"></p>
<p>自己修改了一些球的材质位置,减少了球的数量,耗时15分钟左右</p>
<p><img src="https://s2.loli.net/2022/09/09/RLE6mcZ79OjhJHf.png" alt="image-20220909102129774" style="zoom:67%;"></p>
]]></content>
<categories>
<category>CG</category>
</categories>
<tags>
<tag>CG</tag>
</tags>
</entry>
<entry>
<title>Unity编辑器开发练习——图片资源导入配置工具</title>
<url>/2022/08/10/%E5%9B%BE%E7%89%87%E8%B5%84%E6%BA%90%E5%AF%BC%E5%85%A5%E9%85%8D%E7%BD%AE%E5%B7%A5%E5%85%B7/</url>
<content><![CDATA[<h2 id="一、什么是-AssertPostprocessor"><a href="#一、什么是-AssertPostprocessor" class="headerlink" title="一、什么是 AssertPostprocessor"></a>一、什么是 AssertPostprocessor</h2><p>官方解释: AssetPostprocessor 允许您挂接到导入管线并在导入资源前后运行脚本。</p>
<p>详情见官方 API 介绍: <a href="https://docs.unity.cn/cn/current/ScriptReference/AssetPostprocessor.html">AssetPostprocessor</a></p>
<p>如果项目中需要导入的资源较多,资源的属性就需要在 Inspector 面板中一个一个的进行配置,这很麻烦。而 AssetPostprocessor 可以在我们导入资源时,Unity 自动帮我们将资源的属性按照我们在脚本里写的设置配置好。</p>
<span id="more"></span>
<p>一些常用的方法如下:</p>
<ul>
<li>OnPreprocessTexture:在导入纹理贴图之前调用</li>
<li>OnPreprocessModel:在导入模型之前调用</li>
<li><p>OnPreprocessAudio:在导入音频之前调用</p>
</li>
<li><p>OnPostprocessTexture:在导入纹理贴图之后调用</p>
</li>
<li>OnPostprocessModel:在导入模型之后调用</li>
<li>OnPostprocessAudio:在导入音频之后调用</li>
<li>OnPostprocessAllAssets:所有资源的导入,删除,移动操作都会调用该方法</li>
</ul>
<h2 id="二、简单应用实列"><a href="#二、简单应用实列" class="headerlink" title="二、简单应用实列"></a><a id="example"></a>二、简单应用实列</h2><p><strong>1、在 Assets 文件夹下新建一个 Editor 文件夹,</strong></p>
<blockquote>
<p>关于 Editor 文件夹:</p>
<ul>
<li>该文件夹可以放在项目的任何文件夹下,可以有多个”Editor”文件夹。</li>
<li>编辑器扩展相关的脚本都要放在该文件夹内,该文件夹中的脚本只会对 Unity 编辑器起作用。</li>
<li>项目打包的时候,不会被打包到项目中。如果编辑器相关脚本不放在该文件夹中,打包项目可能会出错。</li>
<li>如果非要有些编辑器相关脚本不放在该文件夹中,需要在该类的前后加上 UNITY_EDITOR 的宏定义</li>
</ul>
<p>关于 Editor Default Resources 文件夹</p>
<p>必须放在 Project 视图的根目录下,可以把编辑器用到的一些资源放在这里。该文件夹和 Editor 文件夹一样都不会被打到最终发布包里,仅仅用于开发时使用。可以直接通过 EditorGUIUtility.Load 去读取该文件夹下的资源。</p>
</blockquote>
<p><strong>2、在 Editor 下面新建一个 C#脚本,该脚本必须继承自 AssetPostprocessor</strong></p>
<p>【具体代码】</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> UnityEngine;</span><br><span class="line"><span class="keyword">using</span> UnityEditor;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">MyEditor</span> : <span class="title">AssetPostprocessor</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Texture导入之前调用,针对Texture进行设置</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">OnPreprocessTexture</span>()</span></span><br><span class="line"> {</span><br><span class="line"> Debug.Log (<span class="string">"纹理资源预处理: "</span>+<span class="keyword">this</span>.assetPath);</span><br><span class="line"> TextureImporter impor = <span class="keyword">this</span>.assetImporter <span class="keyword">as</span> TextureImporter;</span><br><span class="line"> impor.textureType = TextureImporterType.Sprite;</span><br><span class="line"> impor.alphaSource = TextureImporterAlphaSource.FromGrayScale;</span><br><span class="line"> impor.isReadable = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Texture导入之后调用,针对Texture进行设置</span></span><br><span class="line"> <span class="comment">// public void OnPostprocessTexture(Texture2D tex)</span></span><br><span class="line"> <span class="comment">// {</span></span><br><span class="line"> <span class="comment">// Debug.Log ("纹理资源后处理: "+ this.assetPath);</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 所有的资源的导入,删除,移动,都会调用此方法,注意,这个方法是static的</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">OnPostprocessAllAssets</span>(<span class="params"><span class="built_in">string</span>[] importedAssets,<span class="built_in">string</span>[] deletedAssets,<span class="built_in">string</span>[] movedAssets,<span class="built_in">string</span>[] movedFromAssetPaths</span>)</span></span><br><span class="line"> {</span><br><span class="line"> Debug.Log (<span class="string">"======================资源发生变化========================="</span>);</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="built_in">string</span> str <span class="keyword">in</span> importedAssets)</span><br><span class="line"> {</span><br><span class="line"> Debug.Log(<span class="string">"重新导入资源: "</span> + str);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">foreach</span> (<span class="built_in">string</span> str <span class="keyword">in</span> deletedAssets)</span><br><span class="line"> {</span><br><span class="line"> Debug.Log(<span class="string">"删除资源: "</span> + str);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i < movedAssets.Length; i++)</span><br><span class="line"> {</span><br><span class="line"> Debug.Log(<span class="string">"Moved Asset: "</span> + movedAssets[i] + <span class="string">" from: "</span> + movedFromAssetPaths[i]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>3、将资源拖入项目中,即可实现资源属性的自动配置</strong></p>
<p>【效果】</p>
<p>将图片拖入后,Inspector 面板设置如下</p>
<p><img src="https://s2.loli.net/2022/08/08/UpihX6xAc2KdHNG.png" alt=""></p>
<h2 id="三、打包并使用编辑器"><a href="#三、打包并使用编辑器" class="headerlink" title="三、打包并使用编辑器"></a>三、打包并使用编辑器</h2><h3 id="1、创建一个自定义的包"><a href="#1、创建一个自定义的包" class="headerlink" title="1、创建一个自定义的包"></a>1、创建一个自定义的包</h3><p>具体细则见官方文档: <a href="https://docs.unity.cn/cn/2021.3/Manual/CustomPackages.html">创建自定义包</a></p>
<h4 id="【标准包布局】"><a href="#【标准包布局】" class="headerlink" title="【标准包布局】"></a>【标准包布局】</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><root></span><br><span class="line"> ├── package.json</span><br><span class="line"> ├── README.md</span><br><span class="line"> ├── CHANGELOG.md</span><br><span class="line"> ├── LICENSE.md</span><br><span class="line"> ├── Editor</span><br><span class="line"> │ ├── Unity.[YourPackageName].Editor.asmdef</span><br><span class="line"> │ └── EditorExample.cs</span><br><span class="line"> ├── Runtime</span><br><span class="line"> │ ├── Unity.[YourPackageName].asmdef</span><br><span class="line"> │ └── RuntimeExample.cs</span><br><span class="line"> ├── Tests</span><br><span class="line"> │ ├── Editor</span><br><span class="line"> │ │ ├── Unity.[YourPackageName].Editor.Tests.asmdef</span><br><span class="line"> │ │ └── EditorExampleTest.cs</span><br><span class="line"> │ └── Runtime</span><br><span class="line"> │ ├── Unity.[YourPackageName].Tests.asmdef</span><br><span class="line"> │ └── RuntimeExampleTest.cs</span><br><span class="line"> └── Documentation~</span><br><span class="line"> └── [YourPackageName].md</span><br></pre></td></tr></table></figure>
<h4 id="【Package-命名规范】"><a href="#【Package-命名规范】" class="headerlink" title="【Package 命名规范】"></a>【Package 命名规范】</h4><ul>
<li>包名起始必须为 com.<company-name>,例如 com.unity.timeline</company-name></li>
<li>若在 UI 显示则需低于 50 个字符,否则可低于 214 个字符</li>
<li>仅包含小写字母、数字、连字符-、下划线_和点.</li>
<li>为表明命名空间,可在命名空间后缀加点。如 com.unity.2d.animation 和 com.unity.2d.ik</li>
</ul>
<p>以下是简化版的步骤,<strong>并不规范</strong>。</p>
<p>首先在 <em>项目名/Packages</em> 目录下创建一个文件夹作为自定义的包,在该文件夹下创建一个 Editor 文件夹、一个 Runtime 文件夹、一个 package.json 文件,在 Editor 文件夹下创建 MyTextureImportProcess.cs 、MyPackage.EditorTests.asmdef。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><root></span><br><span class="line"> ├── package.json</span><br><span class="line"> ├── Editor</span><br><span class="line"> │ ├── MyPackage.EditorTests.asmdef</span><br><span class="line"> │ └── MyTextureImportProcess.cs</span><br><span class="line"> └── Runtime</span><br></pre></td></tr></table></figure>
<p>MyPackage.EditorTests.asmdef 的标准格式如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"MyPackage.Editor.Tests"</span>,</span><br><span class="line"> <span class="string">"references"</span>: [</span><br><span class="line"> <span class="string">"MyPackage.Editor"</span>,</span><br><span class="line"> <span class="string">"MyPackage"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">"optionalUnityReferences"</span>: [</span><br><span class="line"> <span class="string">"TestAssemblies"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">"includePlatforms"</span>: [</span><br><span class="line"> <span class="string">"Editor"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">"excludePlatforms"</span>: []</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>此处仅为测试用,所以可以稍作修改</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// MyPackage.EditorTests.asmdef</span></span><br><span class="line">{</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"EKKO测试.Editor.Tests"</span>,</span><br><span class="line"> <span class="string">"rootNamespace"</span>: <span class="string">""</span>,</span><br><span class="line"> <span class="string">"references"</span>: [],</span><br><span class="line"> <span class="string">"autoReferenced"</span>: <span class="literal">true</span>,</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// package.json 文件</span></span><br><span class="line">{</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"com.ekko.ekko-package"</span>,</span><br><span class="line"> <span class="string">"displayName"</span>:<span class="string">"EKKO测试package"</span>,</span><br><span class="line"> <span class="string">"version"</span>: <span class="string">"1.0.1"</span>,</span><br><span class="line"> <span class="string">"author"</span>: <span class="string">"EKKO"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"这是一个测试"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><img src="https://s2.loli.net/2022/08/10/2EuZGHz4XKa6qwg.png" alt="hh.png"></p>
<h3 id="2、编辑器开发"><a href="#2、编辑器开发" class="headerlink" title="2、编辑器开发"></a>2、编辑器开发</h3><h4 id="(1)创建编辑器"><a href="#(1)创建编辑器" class="headerlink" title="(1)创建编辑器"></a>(1)创建编辑器</h4><p>先写一个工具函数</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">MyTools</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">GetParentPath</span>(<span class="params"><span class="built_in">string</span> path</span>)</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> str = path.Split(<span class="string">"/"</span>);</span><br><span class="line"> path = path.Replace(str[str.Length - <span class="number">1</span>], <span class="string">""</span>);</span><br><span class="line"> <span class="keyword">return</span> path;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>首先在代码中加上</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">[<span class="meta">CreateAssetMenu(menuName = <span class="string">"EKKO/图片资源导入配置"</span>, fileName = <span class="string">"图片资源导入配置"</span>)</span>]</span><br></pre></td></tr></table></figure>
<p>此时在 Unity 中右键即可看到创建面板</p>
<blockquote>
<p>注意:<br>点击创建后不要重命名</p>
</blockquote>
<p><img src="https://s2.loli.net/2022/08/10/sY56UyN92fAikr4.png" alt="333.png"></p>
<p>然后创建一个类继承自 ScriptableObject</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">MyTextureImportProcessSetting</span> : <span class="title">ScriptableObject</span></span><br><span class="line">{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注:</p>
<p>MonoBehaviour 是以组件形式挂在 GameObject 上的,而 ScriptableObject 则以 Assets 资源的形式存在的</p>
</blockquote>
<p>类中写上要进行的设置,此处依据需要自定义</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">[<span class="meta">Header(<span class="string">"图片导入设置"</span>)</span>]</span><br><span class="line">[<span class="meta">Space(10)</span>]</span><br><span class="line"><span class="comment">// 以下自定义,此处仅为测试用</span></span><br><span class="line"><span class="keyword">public</span> TextureImporterType textureType = TextureImporterType.NormalMap;</span><br><span class="line"><span class="keyword">public</span> <span class="built_in">bool</span> sRGB = <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="built_in">bool</span> readAble = <span class="literal">true</span>;</span><br></pre></td></tr></table></figure>
<p>unity 编辑器开发需要引用 UnityEditor 命名空间,并且脚本需要继承 Editor 类,用于在 Unity 软件界面编辑自己的控件。</p>
<p>CustormEditor 一般与类 Editor 配合使用,以实现在 Inspector 面板中的自定义显示,使用时有几个注意点:</p>
<p>1、CustomEditor + typeof + 类名</p>
<p>2、自定义类是 Editor 的派生类</p>
<p>3、绘制自定义 UI 要重写 OnInspectorGUI 函数。此函数是虚函数</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">[<span class="meta">CustomEditor(typeof(MyTextureImportProcessSetting))</span>]</span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">TextureImportSetting</span> : <span class="title">Editor</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment">//target 被Inspector的对象。比如某个脚本</span></span><br><span class="line"> <span class="keyword">private</span> MyTextureImportProcessSetting myTarget;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>继承 UnityEditor 脚本的几个基本事件函数,详情查看<a href="https://docs.unity3d.com/cn/2021.1/ScriptReference/index.html">官方文档</a>。</p>
<ul>
<li>OnEnable() 在挂载目标脚本的物体被选中时调用。</li>
<li>OnInspectorGUI() 在 Inspector 面板绘制自定义 UI 的语句必须在这个函数中执行。</li>
<li>OnSceneGUI() 在场景视图中绘制自定义 UI 的语句必须在这个函数执行。</li>
<li>OnDestory() 在脚本被销毁时调用。</li>
</ul>
<p>在 OnEnable()函数中添加</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">myTarget = target <span class="keyword">as</span> MyTextureImportProcessSetting;</span><br></pre></td></tr></table></figure>
<p>在 OnInspectorGUI()函数中添加</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">base</span>.OnInspectorGUI();</span><br><span class="line"></span><br><span class="line"><span class="comment">// GUILayout.Button()方法的返回值表示该按钮是否被点击,所以只需要if判断</span></span><br><span class="line"><span class="keyword">if</span> (GUILayout.Button(<span class="string">"应用"</span>))</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">var</span> path = AssetDatabase.GetAssetPath(myTarget);</span><br><span class="line"> <span class="keyword">var</span> files = Directory.GetFiles(MyTools.GetParentPath(path));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">foreach</span> (<span class="keyword">var</span> file <span class="keyword">in</span> files)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 此处可扩展,将需要修改的纹理的后缀加到这里</span></span><br><span class="line"> <span class="keyword">if</span> (dir.IndexOf(<span class="string">".jpg"</span>) > <span class="number">0</span> || dir.IndexOf(<span class="string">".png"</span>) > <span class="number">0</span> || dir.IndexOf(<span class="string">".tga"</span>) > <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> AssetDatabase.ImportAsset(file);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="2-使用设置"><a href="#2-使用设置" class="headerlink" title="(2) 使用设置"></a>(2) 使用设置</h4><p>对上述<a href="#example">简单应用实例</a>脚本进行修改,将 OnPreprocessTexture()函数修改为:</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">TextureImporter impor = <span class="keyword">this</span>.assetImporter <span class="keyword">as</span> TextureImporter;</span><br><span class="line"><span class="keyword">var</span> parentPath = MyTools.GetParentPath(<span class="keyword">this</span>.assetPath);</span><br><span class="line"><span class="keyword">var</span> settings = AssetDatabase.LoadAssetAtPath<MyTextureImportProcessSetting>( parentPath + <span class="string">"图片资源导入配置.asset"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (settings)</span><br><span class="line">{</span><br><span class="line"> Debug.Log(<span class="string">"纹理资源: "</span> + <span class="keyword">this</span>.assetPath + <span class="string">" 使用了自定义的配置"</span>);</span><br><span class="line"> impor.textureType = settings .textureType;</span><br><span class="line"> impor.sRGBTexture = settings .sRGB;</span><br><span class="line"> impor.isReadable = settings .readAble;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="3、效果展示"><a href="#3、效果展示" class="headerlink" title="3、效果展示"></a>3、效果展示</h3><p><strong>首先将图片拖入 Unity 工程下的文件夹中</strong></p>
<p><img src="https://s2.loli.net/2022/08/10/SMzmycoT8jP2rZs.png" alt="444.png"></p>
<p><strong>当前图片属性设置如下</strong></p>
<p><img src="https://s2.loli.net/2022/08/10/XqjyDcLE4lwQtU1.png" alt="555.png"></p>
<p><strong>在该文件夹下创建一个编辑器</strong></p>
<p><img src="https://s2.loli.net/2022/08/10/xpCShf6MOyHktG3.png" alt="666.png"></p>
<p><strong>对编辑器进行设置并点击应用</strong></p>
<p><img src="https://s2.loli.net/2022/08/10/XLQAmGhnoWtkOfe.png" alt="777.png"></p>
<p><strong>可以看到图片属性已经被重新设置</strong></p>
<p><img src="https://s2.loli.net/2022/08/10/fvn2yx7tBoiKgYI.png" alt="888.png"><br><img src="https://s2.loli.net/2022/08/10/1fqArQZamNUVX4H.png" alt="999.png"></p>
<p><strong>此时控制台的打印如下</strong></p>
<p><img src="https://s2.loli.net/2022/08/10/F8irA7HD4wCxJpg.png" alt="101010.png"></p>
<h2 id="四、扩展"><a href="#四、扩展" class="headerlink" title="四、扩展"></a>四、扩展</h2><p>以上功能仅限于与 “图片资源导入配置.asset” 处于同一文件夹中的纹理图片资源生效<br>如果要扩展,使得对该目录中的子文件夹里面的图片也生效,可以使用</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取path下的文件夹</span></span><br><span class="line"><span class="keyword">var</span> dirs = Directory.GetDirectories(path);</span><br><span class="line"></span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">var</span> dir <span class="keyword">in</span> dirs)</span><br><span class="line">{</span><br><span class="line"> AssetDatabase.ImportAsset(dir, ImportAssetOptions.ImportRecursive);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中 ImportAssetOptions.ImportRecursive 功能为在导入一个文件时,同时导入其内容,详情见<a href="https://docs.unity3d.com/ScriptReference/ImportAssetOptions.ImportRecursive.html">官方 API 介绍</a></p>
<h2 id="五、项目源码"><a href="#五、项目源码" class="headerlink" title="五、项目源码"></a>五、项目源码</h2><p>项目源码: <a href="https://github.com/Volleria/texture-import-tool">GitHub 地址</a></p>
]]></content>
<categories>
<category>Unity</category>
</categories>
<tags>
<tag>Unity</tag>
<tag>客户端开发</tag>
</tags>
</entry>
<entry>
<title>【GAMES202】作业1—实时阴影</title>
<url>/2022/09/10/%E3%80%90GAMES202%E3%80%91%E4%BD%9C%E4%B8%9A1%E2%80%94%E5%AE%9E%E6%97%B6%E9%98%B4%E5%BD%B1/</url>
<content><)</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// View transform</span></span><br><span class="line">mat4.<span class="title function_">identity</span>(viewMatrix);</span><br><span class="line">mat4.<span class="title function_">lookAt</span>(viewMatrix, <span class="variable language_">this</span>.<span class="property">lightPos</span>, <span class="variable language_">this</span>.<span class="property">focalPoint</span>, <span class="variable language_">this</span>.<span class="property">lightUp</span>);</span><br></pre></td></tr></table></figure>
<p><strong>(3) Projection transform</strong></p>
<p>作业指导中在此处推荐使用正交投影</p>
<p><img src="https://s2.loli.net/2022/09/09/QxH29lkpFBYEzCo.png" alt="image-20220908143520351"></p>
<p>这里依旧可以直接调用 glMatrix 库提供的接口 mat4.ortho()</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Generates a orthogonal projection matrix with the given bounds</span></span><br><span class="line">(<span class="keyword">static</span>) <span class="title function_">ortho</span>(out, left, right, bottom, top, near, far) → {mat4}</span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Projection transform</span></span><br><span class="line">mat4.<span class="title function_">identity</span>(projectionMatrix);</span><br><span class="line">mat4.<span class="title function_">ortho</span>(projectionMatrix, -<span class="number">150</span>, <span class="number">150</span>, -<span class="number">150</span>, <span class="number">150</span>, <span class="number">1e-2</span>, <span class="number">400</span>);</span><br></pre></td></tr></table></figure>
<h4 id="2、坐标空间转换"><a href="#2、坐标空间转换" class="headerlink" title="2、坐标空间转换"></a>2、坐标空间转换</h4><p>由作业提示可知,首先需要将 vPositionFromLight 的坐标范围映射到 0 ~ 1 之间,因为纹理采样的坐标范围是 0 ~ 1 ,Shadowmap 中存储的纹理深度范围也是 0 ~ 1 </p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vec3 shadowCoord = vPositionFromLight.<span class="property">xyz</span> / vPositionFromLight.<span class="property">w</span>;</span><br><span class="line">shadowCoord.<span class="property">xyz</span> = (shadowCoord.<span class="property">xyz</span> + <span class="number">1.0</span>) / <span class="number">2.0</span>;</span><br></pre></td></tr></table></figure>
<p>然后调用 useShadowMap() 函数</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">visibility = <span class="title function_">useShadowMap</span>(uShadowMap, <span class="title function_">vec4</span>(shadowCoord, <span class="number">1.0</span>));</span><br></pre></td></tr></table></figure>
<h4 id="3、硬阴影"><a href="#3、硬阴影" class="headerlink" title="3、硬阴影"></a>3、硬阴影</h4><p><img src="https://s2.loli.net/2022/09/09/LUktCWMVzosZHPe.png" alt="image-20220908150237548"></p>
<p>由作业提示可知,硬阴影主要需要实现phongFragment.glsl 中的 useShadowMap 函数</p>
<p>该函数具体实现如下:</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> useShadowMap(<span class="type">sampler2D</span> shadowMap, <span class="type">vec4</span> shadowCoord)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 获取距离最近的深度值</span></span><br><span class="line"> <span class="type">float</span> shadowDepth = unpack(<span class="built_in">texture2D</span>(shadowMap,shadowCoord.xy));</span><br><span class="line"> <span class="comment">// 获取当前深度值</span></span><br><span class="line"> <span class="type">float</span> currentDepth = shadowCoord.z;</span><br><span class="line"> <span class="comment">// 将二者进行比较</span></span><br><span class="line"> <span class="keyword">return</span> shadowDepth + EPS <= currentDepth ? <span class="number">0.0</span> : <span class="number">1.0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中作业框架以及提供了 unpack 函数</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> unpack(<span class="type">vec4</span> rgbaDepth) </span><br><span class="line">{</span><br><span class="line"> <span class="keyword">const</span> <span class="type">vec4</span> bitShift = <span class="type">vec4</span>(<span class="number">1.0</span>, <span class="number">1.0</span>/<span class="number">256.0</span>, <span class="number">1.0</span>/(<span class="number">256.0</span>*<span class="number">256.0</span>), <span class="number">1.0</span>/(<span class="number">256.0</span>*<span class="number">256.0</span>*<span class="number">256.0</span>));</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">dot</span>(rgbaDepth, bitShift);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>运行结果如下:</p>
<p><img src="https://s2.loli.net/2022/09/09/fWsPhaLKQ6iyH3r.png" alt="image-20220908150601727" style="zoom:80%;"></p>
<p>可以观察到,当光线方向与顶点法线方向夹角较大时容易出现阴影瑕疵(shadow acne),这是由于 ShadowMap 的精度问题(采样率低)而产生的自遮挡(self occlusion)现象,在Lecture 03中有相关的论述。</p>
<p><img src="https://s2.loli.net/2022/09/09/CRn8wHhlSbvojVq.jpg" style="zoom:67%;"></p>
<p>我们可以引入 bias 来对阴影纹理的采样值进行一定程度的偏移来处理自遮挡问题,但这种处理方式同样会产生漏光现象(一些本应处于阴影中的位置被意外地照亮了)</p>
<p>此处可参考 <a href="[自适应Shadow Bias算法 - 知乎 (zhihu.com">自适应Shadow Bias算法</a>](<a href="https://zhuanlan.zhihu.com/p/370951892">https://zhuanlan.zhihu.com/p/370951892</a>))</p>
<p>可以在phongVertex.glsl里令 gl_Position = vPositionFromLight,就可以可视化出shadow map渲染的范围</p>
<h4 id="4、软阴影-PCF"><a href="#4、软阴影-PCF" class="headerlink" title="4、软阴影 PCF"></a>4、软阴影 PCF</h4><p><img src="https://s2.loli.net/2022/09/09/z4E1usnrgSWjBha.png" alt="image-20220908151043069" style="zoom:80%;"></p>
<p>PCF的实现需要完善代码框架中的 PCF 函数</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> PCF(<span class="type">sampler2D</span> shadowMap, <span class="type">vec4</span> coords) </span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1.0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>对采样点周围的一片区域进行随机采样</p>
<script type="math/tex; mode=display">
被遮挡的程度 = \frac{被采样点遮挡的顶点数量}{采样点的总数量}</script><p>关于随机采样的方法,框架中提供了泊松圆盘采样和均匀圆盘采样两种采样函数,<a href="[Sampling function (codepen.io">参考链接</a>](<a href="https://codepen.io/arkhamwjz/pen/MWbqJNG?editors=1010))中还给出了两种采样方式的可视化展示">https://codepen.io/arkhamwjz/pen/MWbqJNG?editors=1010))中还给出了两种采样方式的可视化展示</a></p>
<p><strong>【泊松圆盘采样 poisson disk】</strong></p>
<p><img src="https://s2.loli.net/2022/09/09/CD86ToPMSOiVvHa.png" style="zoom: 80%;"></p>
<p><strong>【均匀圆盘采样 uniform disk】</strong></p>
<p><img src="https://s2.loli.net/2022/09/09/uoSClpNhFJyXTAU.png" style="zoom:80%;"></p>
<p>两种采样方法都在一个单位圆内随机地生成二维坐标向量,并根据 NUM_SAMPLES 的值确定采样点的数量,最后将生成的采样坐标存储在 poissonDisk 数组中,用于后续在PCF中对着色点对应的<strong>SM坐标</strong>加上该数组中的值来得到采样点的<strong>SM坐标</strong>。</p>
<p>上述两种采样方式对应的代码如下:</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">vec2</span> poissonDisk[NUM_SAMPLES];</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> poissonDiskSamples( <span class="keyword">const</span> <span class="keyword">in</span> <span class="type">vec2</span> randomSeed ) </span><br><span class="line">{</span><br><span class="line"> <span class="type">float</span> ANGLE_STEP = PI2 * <span class="type">float</span>( NUM_RINGS ) / <span class="type">float</span>( NUM_SAMPLES );</span><br><span class="line"> <span class="type">float</span> INV_NUM_SAMPLES = <span class="number">1.0</span> / <span class="type">float</span>( NUM_SAMPLES );</span><br><span class="line"></span><br><span class="line"> <span class="type">float</span> angle = rand_2to1( randomSeed ) * PI2;</span><br><span class="line"> <span class="type">float</span> radius = INV_NUM_SAMPLES;</span><br><span class="line"> <span class="type">float</span> radiusStep = radius;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>( <span class="type">int</span> i = <span class="number">0</span>; i < NUM_SAMPLES; i ++ ) {</span><br><span class="line"> poissonDisk[i] = <span class="type">vec2</span>( <span class="built_in">cos</span>( angle ), <span class="built_in">sin</span>( angle ) ) * <span class="built_in">pow</span>( radius, <span class="number">0.75</span> );</span><br><span class="line"> radius += radiusStep;</span><br><span class="line"> angle += ANGLE_STEP;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> uniformDiskSamples( <span class="keyword">const</span> <span class="keyword">in</span> <span class="type">vec2</span> randomSeed ) </span><br><span class="line">{</span><br><span class="line"> <span class="type">float</span> randNum = rand_2to1(randomSeed);</span><br><span class="line"> <span class="type">float</span> sampleX = rand_1to1( randNum ) ;</span><br><span class="line"> <span class="type">float</span> sampleY = rand_1to1( sampleX ) ;</span><br><span class="line"></span><br><span class="line"> <span class="type">float</span> angle = sampleX * PI2;</span><br><span class="line"> <span class="type">float</span> radius = <span class="built_in">sqrt</span>(sampleY);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>( <span class="type">int</span> i = <span class="number">0</span>; i < NUM_SAMPLES; i ++ ) {</span><br><span class="line"> poissonDisk[i] = <span class="type">vec2</span>( radius * <span class="built_in">cos</span>(angle) , radius * <span class="built_in">sin</span>(angle) );</span><br><span class="line"></span><br><span class="line"> sampleX = rand_1to1( sampleY ) ;</span><br><span class="line"> sampleY = rand_1to1( sampleX ) ;</span><br><span class="line"></span><br><span class="line"> angle = sampleX * PI2;</span><br><span class="line"> radius = <span class="built_in">sqrt</span>(sampleY);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>根据随机采样的得到的uv偏移值,继续完善 PCF 函数,遍历 poissonDisk 数组,为每一个uv采样点的坐标加上偏移值,然后将采样得到的深度值与当前顶点在 light space 的深度值比较,若判断顶点存在于阴影中,则将 blocker 的值+1。</p>
<p>最后可见程度 = 1 - 存在于阴影中的采样点 / 采样点总数</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> PCF(<span class="type">sampler2D</span> shadowMap, <span class="type">vec4</span> coords) </span><br><span class="line">{</span><br><span class="line"> <span class="type">float</span> blocker = <span class="number">0.0</span>; <span class="comment">// 统计被遮挡的数量</span></span><br><span class="line"> <span class="type">float</span> stride = <span class="number">5.0</span>;</span><br><span class="line"> <span class="type">float</span> <span class="built_in">textureSize</span> = <span class="number">2048</span>; <span class="comment">// 2048为生成的纹理的分辨率</span></span><br><span class="line"> poissonDiskSamples(coords.xy);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < NUM_SAMPLES; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="type">vec2</span> uvBias = poissonDisk[i] * stride / <span class="built_in">textureSize</span>;</span><br><span class="line"> <span class="type">float</span> shadowDepth = unpack(<span class="built_in">texture2D</span>(shadowMap, coords.xy + uvBias));</span><br><span class="line"> <span class="keyword">if</span>( coords.z > shadowDepth + EPS)</span><br><span class="line"> {</span><br><span class="line"> blocker++;</span><br><span class="line"> } </span><br><span class="line"> }</span><br><span class="line"> <span class="type">float</span> visibility = <span class="number">1.0</span> - blocker / <span class="type">float</span>(NUM_SAMPLES);</span><br><span class="line"> <span class="keyword">return</span> visibility;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>效果图如下:</p>
<p><strong>【stride = 5 , poissonDiskSamples】</strong></p>
<p><img src="https://s2.loli.net/2022/09/09/x4orwOB5NUuF3WI.png" alt="image-20220908172843462" style="zoom:80%;"></p>
<p><strong>【stride = 5 , uniformDiskSamples】</strong></p>
<p><img src="https://s2.loli.net/2022/09/09/Hniud3a8PLIT9Nf.png" alt="image-20220908173106800" style="zoom:80%;"></p>
<p><strong>【NUM_SAMPLES = 200, stride = 5 ,poissonDiskSamples】</strong></p>
<p><img src="https://s2.loli.net/2022/09/09/LCSkYW5Fdvebpif.png" alt="image-20220908222730875" style="zoom:80%;"></p>
<h4 id="5、软阴影-PCSS"><a href="#5、软阴影-PCSS" class="headerlink" title="5、软阴影 PCSS"></a>5、软阴影 PCSS</h4><p>PCSS 依据现实当中观测到的物体产生的阴影离物体越近,就会感觉越阴影越”浓”,离物体越远,就会感觉阴影越”淡“,PCSS就是用来还原这一现象的,具体原理以及在上文提过,此处不再赘述。</p>
<p><img src="https://s2.loli.net/2022/09/09/fwGibXOd8VQsSRl.png" alt="image-20220908173342602"></p>
<p>有作业说明可知,PCSS要求完成 phongFragment.glsl 中的 findBlocker 和 PCSS 函数</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> findBlocker( <span class="type">sampler2D</span> shadowMap, <span class="type">vec2</span> uv, <span class="type">float</span> zReceiver ) </span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1.0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> PCSS(<span class="type">sampler2D</span> shadowMap, <span class="type">vec4</span> coords)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// STEP 1: avgblocker depth,遮挡点以及其周围的遮挡点的平均深度</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// STEP 2: penumbra size,利用相似三角形的原理计算新的半影直径</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// STEP 3: filtering</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">1.0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>【findBlocker】</strong></p>
<p>该函数需要完成对遮挡物平均深度的计算,同PCF一样,首先是用采样函数生成随机偏移数组,对于每一个偏移的 uv 坐标采样得到深度,将产生了遮挡的部分进行累加,然后计算得到被遮挡的平均深度。此处uv的偏移对结果影响较大,若选择的stride的值较小,则对于距离光源较远的部分而言,打到该部分上的光更容易被遮挡,从而影响平均遮挡深度的计算。若选择的stride的值较大,会造成顶点周围的平均遮挡深度相似,无法表现出距离越远阴影越模糊的效果。</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> findBlocker( <span class="type">sampler2D</span> shadowMap, <span class="type">vec2</span> uv, <span class="type">float</span> zReceiver ) </span><br><span class="line">{</span><br><span class="line"> <span class="comment">// 定义相关参数</span></span><br><span class="line"> <span class="type">float</span> blocker = <span class="number">0.0</span>;</span><br><span class="line"> <span class="type">float</span> blockDepth = <span class="number">0.0</span>;</span><br><span class="line"> <span class="type">float</span> <span class="built_in">textureSize</span> = <span class="number">2048.0</span>;</span><br><span class="line"> <span class="type">float</span> stride = <span class="number">5.0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 泊松采样</span></span><br><span class="line"> poissonDiskSamples(uv);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 对被遮挡的部分进行累加</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span> ;i < NUM_SAMPLES; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="type">vec2</span> uvBias = poissonDisk[i] * stride / <span class="built_in">textureSize</span>;</span><br><span class="line"> <span class="type">float</span> shadowDepth = unpack( <span class="built_in">texture2D</span>(shadowMap, uv + uvBias));</span><br><span class="line"> <span class="keyword">if</span>( shadowDepth + <span class="number">0.01</span> <= zReceiver)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 在阴影中</span></span><br><span class="line"> blocker++;</span><br><span class="line"> blockDepth += shadowDepth;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( blocker < <span class="number">0.1</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1.0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">float</span> avgblocker = blockDepth / blocker;</span><br><span class="line"> <span class="keyword">return</span> avgblocker;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>【PCSS】</strong></p>
<p>由相似三角形计算半影范围的公式:</p>
<script type="math/tex; mode=display">
W_{Penumbra} = W_{light} * \frac{(d_{Receiver} - d_{Blocker})}{d_{Blocker}}</script><p>具体代码如下:</p>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="type">float</span> PCSS(<span class="type">sampler2D</span> shadowMap, <span class="type">vec4</span> coords)</span><br><span class="line">{</span><br><span class="line"></span><br><span class="line"> <span class="type">float</span> w_Light = <span class="number">1.0</span>;</span><br><span class="line"> <span class="type">float</span> d_Receiver = coords.z;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// STEP 1: avgblocker depth</span></span><br><span class="line"> <span class="type">float</span> d_Blocker = findBlocker(shadowMap, coords.xy, coords.z);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// STEP 2: penumbra size 可根据公式计算得到</span></span><br><span class="line"> <span class="type">float</span> w_penumbra = w_Light * ( d_Receiver - d_Blocker ) / d_Blocker;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// STEP 3: filtering </span></span><br><span class="line"> <span class="comment">// 对上面的 PCF 进行一些变化: PCF 的uvBias不再固定,而是根据上面计算出来的半影直径动态地调整</span></span><br><span class="line"> <span class="comment">// 采样点距离遮挡点越远,其偏移半径越大</span></span><br><span class="line"> <span class="type">float</span> visibility = <span class="number">0.0</span>;</span><br><span class="line"> <span class="keyword">for</span>( <span class="type">int</span> i = <span class="number">0</span>; i < NUM_SAMPLES ; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="type">vec2</span> uvBias = poissonDisk[i] * <span class="number">10.0</span> / <span class="number">2048.0</span> * w_penumbra;</span><br><span class="line"> <span class="type">float</span> shadowDepth = unpack( <span class="built_in">texture2D</span>(shadowMap, coords.xy + uvBias ));</span><br><span class="line"> <span class="keyword">if</span>( coords.z < (shadowDepth + <span class="number">0.01</span>) )</span><br><span class="line"> {</span><br><span class="line"> visibility += <span class="number">1.0</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> visibility / <span class="type">float</span>(NUM_SAMPLES);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>效果图如下:</strong></p>
<p>采样数为20,stride为10,可以看到噪声明显</p>
<p><img src="https://s2.loli.net/2022/09/09/ytBURxIcKkAbweS.png" alt="image-20220909000312797" style="zoom: 67%;"></p>
<p>采样数为20,stride为10,可以看到噪声情况有明显改善</p>
<p><img src="https://s2.loli.net/2022/09/09/MFJlwxGNScqaTCP.png" alt="image-20220909100245809" style="zoom:80%;"></p>
<p><img src="https://s2.loli.net/2022/09/09/OFA348qL1J6aByT.png" alt="image-20220909100316452" style="zoom: 67%;"></p>
<p>经过一番调参,在采样数为200的情况下,stride为25时效果较好</p>
<p><img src="https://s2.loli.net/2022/09/09/UTZbXVF2CGSPNcq.png" alt="image-20220909100542134" style="zoom:67%;"></p>
]]></content>
<categories>
<category>CG</category>
</categories>
<tags>
<tag>CG</tag>
</tags>
</entry>
<entry>
<title>【学习记录】—— Decorator(修饰器)</title>
<url>/2022/08/10/%E5%AE%9E%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Decorator(%E4%BF%AE%E9%A5%B0%E5%99%A8)/</url>
<content><![CDATA[<h3 id="一、Decorator-Pattern(修饰器模式)"><a href="#一、Decorator-Pattern(修饰器模式)" class="headerlink" title="一、Decorator Pattern(修饰器模式)"></a>一、Decorator Pattern(修饰器模式)</h3><p>设计模式的一种,主要功能是向一个现有的对象添加额外功能的同时又不改变其结构。传统方法是使用子类继承的方式来对某一个类进行扩展,但是会导致出现非常多的子类。</p>
<span id="more"></span>
<h3 id="二、python-实例"><a href="#二、python-实例" class="headerlink" title="二、python 实例"></a>二、python 实例</h3><blockquote>
<p>在 python 中,函数可以是一个对象</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">Add</span>(<span class="params">原函数名</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">新函数名</span>():</span><br><span class="line"> <span class="comment"># 额外的功能</span></span><br><span class="line"> <span class="comment"># xxxxxxxxxx</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 原来的功能</span></span><br><span class="line"> 原函数名</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 会返回新函数,该新函数在保留原函数功能情况下增加了一个新功能</span></span><br><span class="line"> <span class="keyword">return</span> 新函数名</span><br><span class="line"></span><br><span class="line"><span class="comment"># 然后便可以调用 Add() 这个函数来为原函数增加额外的功能,这也叫做对函数的修饰</span></span><br><span class="line">函数 = Add(原函数)</span><br></pre></td></tr></table></figure>
<p>以上相当于 Decorator 的实现</p>
<p>下面是具体使用</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">Add</span>(<span class="params">func</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">new_func</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"new"</span>)</span><br><span class="line"> func</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> new_func</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@Add</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">func_a</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"a"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@Add</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">func_b</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"b"</span>)</span><br><span class="line"></span><br><span class="line">func_a()</span><br><span class="line">func_b()</span><br></pre></td></tr></table></figure>
<p>输出如下</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">new</span><br><span class="line">a</span><br><span class="line">new</span><br><span class="line">b</span><br></pre></td></tr></table></figure>
<p>可以看出以下两组代码等价</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">fun_x</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"x"</span>)</span><br><span class="line"></span><br><span class="line">fun_x = Add(fun_x)</span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Add</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fun_x</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"x"</span>)</span><br></pre></td></tr></table></figure>
<p>可以在修饰器里面为原函数增加功能,甚至可以完全更改原函数的功能。</p>
<p>修饰器是一个函数,它返回一个新的函数,这个返回的函数用于替代原来的函数的行为。<br>以上只是对装饰器(修饰器)的简单了解,后面还有带参数的修饰器、修饰器类等更多复杂知识,有需要再说吧</p>
<h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><p><a href="https://zhuanlan.zhihu.com/p/51035016">Python 之修饰器</a></p>
<p><a href="https://www.tutorialspoint.com/design_pattern/decorator_pattern.htm">Design Patterns - Decorator Pattern</a></p>
<p><a href="https://www.runoob.com/design-pattern/decorator-pattern.html">装饰器模式|菜鸟教程</a></p>
]]></content>
<categories>
<category>客户端开发</category>
</categories>
<tags>
<tag>客户端开发</tag>
</tags>
</entry>
<entry>
<title>【学习记录】—— RPC通信</title>
<url>/2022/08/10/%E5%AE%9E%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94RPC%E9%80%9A%E4%BF%A1/</url>
<content><![CDATA[<p>在写代码时经常看到类似如下的函数调用<br><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="title class_">XXXXXRpc</span>.<span class="title function_">XXXXXXXX</span>({}, <span class="function">(<span class="params">res</span>) =></span> {<span class="variable constant_">XXXXXXXXX</span>});</span><br></pre></td></tr></table></figure><br>经学习后了解到这是向服务端发送请求的一类函数,所使用的是RPC通信的方式,起初我对此一无所知,在代码中多次调用该类函数向服务端发送请求,造成许多逻辑错误,后来在导师的讲解和自行学习下我逐渐对RPC有了一定的了解。</p>
<span id="more"></span>
<h3 id="一、RPC的基本概念"><a href="#一、RPC的基本概念" class="headerlink" title="一、RPC的基本概念"></a>一、RPC的基本概念</h3><p>1、全称 Remote Procedure Call(远程过程调用),是分布式系统常见的一种通信方法。<br>2、RPC通信是端到端的直接数据交互(客户端和服务端)<br>2、使得我们能够像调用本地服务一样调用远程服务,并且让调用者对网络通信这些细节透明,隐藏了远程调用网络通信底层的复杂性。</p>
<h3 id="二、服务的调用方式"><a href="#二、服务的调用方式" class="headerlink" title="二、服务的调用方式"></a>二、服务的调用方式</h3><p>服务的调用方式有三种</p>
<ul>
<li><p><strong>同步调用</strong></p>
<p>客户端发送请求后,同步等待服务端返回,容易导致服务端长时间无应答使得客户端线程挂死,一般需要设置线程等待时间</p>
</li>
<li><p><strong>异步服务调用</strong></p>
<p>异步服务调用有两种实现方式:一种是只通过Future来实现,还有一种是通过构造Listener对象并将其添加到Future中,用于服务端应答的异步回调。通过Future方式时,线程会阻塞在get结果的操作上;而使用Listener的方式是监听器异步的获取执行结果</p>
</li>
<li><p><strong>并行服务调用</strong></p>
<p>通过并行方式降低端到端的时延,如果依赖RPC接口返回值,并且连续调用的多个RPC之间没有依赖关系,可以采用并行化处理,可采用线程池实现</p>
</li>
</ul>
<h3 id="三、客户端和服务端相互通信的消息结构"><a href="#三、客户端和服务端相互通信的消息结构" class="headerlink" title="三、客户端和服务端相互通信的消息结构"></a>三、客户端和服务端相互通信的消息结构</h3><p>客户端发出的请求消息:</p>
<ul>
<li>接口名称: 调用哪个接口 XXXRPC()</li>
<li>方法名: 调用哪个方法</li>
<li>参数类型</li>
<li>参数值</li>
<li>requestID</li>
<li>…………</li>
</ul>
<p>服务端的返回消息:</p>
<ul>
<li>返回值</li>
<li>状态code</li>
<li>requestID</li>
<li>…………</li>
</ul>
<h3 id="四、RPC的通信流程"><a href="#四、RPC的通信流程" class="headerlink" title="四、RPC的通信流程"></a>四、RPC的通信流程</h3><p>客户端首先发出请求,因为网络传输的数据为二进制,所以需要把请求的数据(包括参数、方法等)序列化,将消息发送给服务端后,服务端会接收并将其反序列化,根据客户端传过来的数据调用本地的服务,执行完毕后将得到的结果封装并进行序列化,发送给客户端,客户端接收到消息后将其反序列化获得需要的数据。</p>
<p>我的工作经常就是在客户端调用RPC发出一个请求,然后通过回调接收来自服务端的返回,一般会返回一个叫GCODE全局码的数据,根据返回的res参数进行判断接下来的操作(该请求成功与否,此时的玩家状态等),该请求一般和按钮功能绑定,通常在玩家点击某处后触发。</p>
]]></content>
<categories>
<category>Unity</category>
</categories>
<tags>
<tag>Unity</tag>
<tag>客户端开发</tag>
<tag>计算机网络</tag>
</tags>
</entry>
<entry>
<title>传送门特效及实现</title>
<url>/2022/07/27/%E5%A5%87%E5%BC%82%E5%8D%9A%E5%A3%AB%E4%BC%A0%E9%80%81%E9%97%A8/</url>
<content><![CDATA[<h4 id="一、传送门特效"><a href="#一、传送门特效" class="headerlink" title="一、传送门特效"></a>一、传送门特效</h4><h5 id="1、使用组件"><a href="#1、使用组件" class="headerlink" title="1、使用组件"></a>1、使用组件</h5><p>采用Unity内置渲染管线中的Particle System制作</p>
<h5 id="2、实现思路"><a href="#2、实现思路" class="headerlink" title="2、实现思路"></a>2、实现思路</h5><p>(1)首先创建四个圆球用于特效的定位,并且采用动画效果使其分散并旋转。<br><span id="more"></span><br><img src="https://s3.bmp.ovh/imgs/2022/04/19/0c8e40d99e10be4b.png" alt=""></p>
<p>(2)新建一个Particle System,将其作为上述小球的子物体,将shape改为box,并修改颜色,添加自定义材质。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/19/2b276d77ae52b808.png" alt=""></p>
<p>(3)再新建一个Particle System,将其作为上述小球的子物体,修改同上,并且设置Start Delay、Start Speed等,增加粒子量与Emission,使其呈现出喷散效果。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/19/6d6a8b76460555c7.png" alt=""></p>
<p>(4)将 (2) (3) 组合,并复制一份到另一个小球上。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/19/3428e2c420bd454b.png" alt=""></p>
<p>(5)再次新建Particle System,将shape改为Circle,适当减少粒子量</p>
<p>,同时调整Start Delay、Start Lifetime、Start Speed与Velocity over LifeTime,添加碰撞效果,增加半径,并改为从边缘发射,得到如下效果。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/19/3aabfd25bb4ec3dd.png" alt=""></p>
<p>(6)添加底部倒影。将Emission的粒子量减小到5,Start Speed设为0,进行旋转缩放,使其贴近地面</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/19/bdc12186e59e727c.png" alt=""></p>
<p>(7)制作两侧溅射效果。将(5)处效果复制一份,对圆进行扇形裁减,扩大其 Velocity of LifeTime,使其溅射距离加大,同时添加与地面的碰撞效果。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/19/e3e461e0841c9e32.png" alt=""></p>
<p>(8)将(7)处效果复制一份,旋转180置于另一侧,将上述效果组合起来,开启后处理(Bloom效果)。同时将一开始创建的四个小球利用起来,为其添加HDR颜色,伪装成高光闪烁部分。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/19/b87263501c6b29a9.png" alt=""></p>
<h5 id="3、分析"><a href="#3、分析" class="headerlink" title="3、分析"></a>3、分析</h5><p>共计使用了8个Particle System,总粒子数在10000左右。材质贴图为自定义制作。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/20/f773ed0159d8543d.png" alt="image-20220419173927582"></p>
<p>可以通过不同的材质贴图对粒子数进行修改,若将图中白色部分拉长,可适当减少粒子数量。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/20/b64566f4efc838a2.png" alt="image-20220419174033159"></p>
<p>经过测试,粒子将会显得十分细长,且颜色不好控制,即使将bloom效果开的很低,也难以分辨。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/20/ea4777b3378991e9.png" alt=""></p>
<h4 id="二、具体传送效果"><a href="#二、具体传送效果" class="headerlink" title="二、具体传送效果"></a>二、具体传送效果</h4><h5 id="1、组件使用情况"><a href="#1、组件使用情况" class="headerlink" title="1、组件使用情况"></a>1、组件使用情况</h5><p>玩家:一个人物和摄像机移动的控制脚本</p>
<p>传送门:一个总控制脚本,两个传送脚本</p>
<p>Shader:使用屏幕空间的坐标,材质附加在传送门上</p>
<p>传送门:一个Cylinder模型,将其压缩,模拟一个圆形面。</p>
<h5 id="2、实现思路-1"><a href="#2、实现思路-1" class="headerlink" title="2、实现思路"></a>2、实现思路</h5><p>场景中共有三个摄像头(两个传送门)。一个玩家主摄像头,两个传送门摄像头PortalCamA 和 PortalCamB,首先新建两个Render Texture 作为两个传送门所用的摄像头的Target Texture,将PortalCamA 渲染出的Render Texture添加到传送门B上,作为主纹理。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/20/8cdbac208214e128.png" alt="image-20220419175956871"></p>
<p>配合上述Shader中的裁剪效果</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// vert</span></span><br><span class="line">o.vertex = UnityObjectToClipPos(v.vertex);</span><br><span class="line">o.screenPos = ComputeScreenPos(o.vertex);</span><br><span class="line">..........................</span><br><span class="line"><span class="comment">//frag </span></span><br><span class="line">float2 uv = i.screenPos.xy / i.screenPos.w;</span><br><span class="line">fixed4 portalCol = tex2D(_MainTex, uv);</span><br></pre></td></tr></table></figure>
<p>传送门B中显示的便是PortalCamA摄像机所观察到的画面。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/20/051b58db13d21f8d.png" alt="image-20220419175906347"></p>
<p>对另一个传送门采用同样的设置。</p>
<p>根据向量加减的原理,可以同玩家相对于传送门A的位置 和 传送门B的位置求出PortalCamB的位置,此时PortalCamB的观察图像就相当于玩家在另一个场景的观察图像。</p>
<p><img src="https://s3.bmp.ovh/imgs/2022/04/20/0f074cea5e3f6b33.png" alt=""></p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">Yangle = player.transform.eulerAngles.y;</span><br><span class="line">Xangle = playerCam.transform.eulerAngles.x;</span><br><span class="line"></span><br><span class="line">portalCamB.transform.position = DoorB.position + player.position - DoorA.position;</span><br><span class="line">portalCamB.transform.localRotation = Quaternion.Euler(Xangle,Yangle,<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">portalCamA.transform.position = DoorA.position + player.position - DoorB.position;</span><br><span class="line">portalCamA.transform.localRotation = Quaternion.Euler(Xangle,Yangle,<span class="number">0</span>);</span><br></pre></td></tr></table></figure>
<p>为传送门添加一个碰撞体,当玩家进入到传送门A时,触发传送效果,将此时 PortalCamB 的位置和旋转角度赋值给玩家及摄像头,即可实现玩家位置的传送效果。</p>
<p>此处添加了一个判断,用于对摄像机进行渲染层级的裁剪,避免视角重叠情况。</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="comment">// camera.cullingMask &= ~(1 << x); // 关闭层x</span></span><br><span class="line">playerCam.cullingMask &= ~(<span class="number">1</span> << PortalLayer);</span><br><span class="line">...............................</span><br><span class="line">...............................</span><br><span class="line">MyPortal01.transflag = MyPortal01.transflag + <span class="number">1</span>;</span><br><span class="line"><span class="keyword">if</span>(MyPortal01.transflag == <span class="number">3</span>)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// camera.cullingMask |= (1 << x); // 打开层x</span></span><br><span class="line"> playerCam.cullingMask |= (<span class="number">1</span> << PortalLayer);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="踩坑点:"><a href="#踩坑点:" class="headerlink" title="踩坑点:"></a>踩坑点:</h6><p>1、玩家控制脚本使用了Character Controller组件导致 </p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">other.transform.position = transPos;</span><br></pre></td></tr></table></figure>
<p>赋值后不起作用,需要在cc. move() 前面添加如下代码</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">Physics.autoSyncTransforms = <span class="literal">true</span>;</span><br></pre></td></tr></table></figure>
<p>2、在使用碰撞体触发传送时,进入第一个传送门触发一次OnTriggerEnter(),然后传送到第二个传送门,触发一次来自第一个传送门的OnTriggerExit() 和来自第二个传送门的OnTriggerEnter()。</p>
<hr>
]]></content>
<categories>
<category>Unity</category>
</categories>
<tags>
<tag>Unity</tag>
</tags>
</entry>
<entry>
<title>简单水墨风格Shader</title>
<url>/2022/07/27/%E6%B0%B4%E5%A2%A8%E9%A3%8E%E6%A0%BCshader/</url>
<content><![CDATA[<h5 id="效果图"><a href="#效果图" class="headerlink" title="效果图"></a><strong>效果图</strong></h5><p><img src="https://s2.loli.net/2022/07/25/cwRglNjD6TOJZVk.png" alt="image-20220414191627658.png"></p>
<span id="more"></span>
<h5 id="代码"><a href="#代码" class="headerlink" title="代码"></a><strong>代码</strong></h5><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 需要一个MainTex主纹理贴图、一个水墨笔刷贴图</span></span><br><span class="line"></span><br><span class="line">Shader <span class="string">"Unlit/shuimo"</span></span><br><span class="line">{</span><br><span class="line"> Properties</span><br><span class="line"> {</span><br><span class="line"> _MainTex(<span class="string">"Main"</span>, <span class="number">2</span>D) = <span class="string">"white"</span> {}</span><br><span class="line"> _Thred(<span class="string">"Edge Thred"</span> , <span class="built_in">Range</span>(<span class="number">0.01</span>,<span class="number">1</span>)) = <span class="number">0.25</span></span><br><span class="line"> _Range(<span class="string">"Edge Range"</span> , <span class="built_in">Range</span>(<span class="number">0</span>,<span class="number">5</span>)) = <span class="number">1</span> </span><br><span class="line"> _Pow(<span class="string">"Edge Intensity"</span>,<span class="built_in">Range</span>(<span class="number">0</span>,<span class="number">5</span>))=<span class="number">1</span></span><br><span class="line"> _BrushTex(<span class="string">"Brush Texture"</span>, <span class="number">2</span>D) = <span class="string">"white"</span> {}</span><br><span class="line"> _Saturation(<span class="string">"Saturation"</span> ,<span class="built_in">Range</span>(<span class="number">0</span>,<span class="number">1</span>)) = <span class="number">0</span></span><br><span class="line"> _BlenderRow(<span class="string">"BlenderRow"</span>,<span class="built_in">Range</span>(<span class="number">0</span>,<span class="number">1</span>)) = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"> [<span class="built_in">Enum</span>(Opacity,<span class="number">1</span>,Darken,<span class="number">2</span>,Lighten,<span class="number">3</span>,Multiply,<span class="number">4</span>,Screen,<span class="number">5</span>,Overlay,<span class="number">6</span>,SoftLight,<span class="number">7</span>)]</span><br><span class="line"> _BlendType(<span class="string">"Blend Type"</span>, Int) = <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line"> SubShader</span><br><span class="line"> {</span><br><span class="line"> Tags { <span class="string">"RenderType"</span>=<span class="string">"Opaque"</span> }</span><br><span class="line"> LOD <span class="number">100</span></span><br><span class="line"></span><br><span class="line"> Pass</span><br><span class="line"> {</span><br><span class="line"> CGPROGRAM</span><br><span class="line"> <span class="meta">#<span class="keyword">pragma</span> vertex vert</span></span><br><span class="line"> <span class="meta">#<span class="keyword">pragma</span> fragment frag</span></span><br><span class="line"> <span class="meta">#<span class="keyword">include</span> <span class="string">"UnityCG.cginc"</span></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">struct</span> appdata</span><br><span class="line"> {</span><br><span class="line"> float4 vertex : POSITION;</span><br><span class="line"> float2 uv : TEXCOORD0;</span><br><span class="line"> float3 normal : NORMAL;</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">struct</span> <span class="title class_">v2f</span></span><br><span class="line"> {</span><br><span class="line"> float2 uv : TEXCOORD0;</span><br><span class="line"> float4 vertex : SV_POSITION;</span><br><span class="line"> float3 worldPos : TEXCOORD1;</span><br><span class="line"> float3 worldNormal : TEXCOORD2;</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> sampler2D _MainTex;</span><br><span class="line"> float4 _MainTex_ST;</span><br><span class="line"> sampler2D _BrushTex;</span><br><span class="line"> <span class="type">float</span> _BlendType;</span><br><span class="line"> <span class="type">float</span> _Thred;</span><br><span class="line"> <span class="type">float</span> _Range;</span><br><span class="line"> <span class="type">float</span> _Pow;</span><br><span class="line"> <span class="type">float</span> _Saturation;</span><br><span class="line"> <span class="type">float</span> _BlenderRow;</span><br><span class="line"></span><br><span class="line"> <span class="function">v2f <span class="title">vert</span> <span class="params">(appdata v)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> v2f o;</span><br><span class="line"> o.vertex = <span class="built_in">UnityObjectToClipPos</span>(v.vertex);</span><br><span class="line"> o.uv = <span class="built_in">TRANSFORM_TEX</span>(v.uv, _MainTex);</span><br><span class="line"> o.worldPos = <span class="built_in">mul</span>(unity_ObjectToWorld,v.vertex).xyz;</span><br><span class="line"> o.worldNormal = <span class="built_in">UnityObjectToWorldNormal</span>(v.normal);</span><br><span class="line"> <span class="keyword">return</span> o;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function">fixed4 <span class="title">frag</span> <span class="params">(v2f i)</span> : SV_Target</span></span><br><span class="line"><span class="function"> {</span></span><br><span class="line"> float3 ViewDir = <span class="built_in">normalize</span>(_WorldSpaceCameraPos.xyz - i.worldPos);</span><br><span class="line"> <span class="type">float</span> NdotV = <span class="built_in">max</span>( <span class="built_in">dot</span>(ViewDir,i.worldNormal) , <span class="number">0</span>);</span><br><span class="line"> fixed4 mainTex = <span class="built_in">tex2D</span>(_MainTex, i.uv);</span><br><span class="line"> fixed4 brushTex = <span class="built_in">tex2D</span>(_BrushTex, i.uv);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// fixed texGrey = (mainTex.r + mainTex.g + mainTex.b)*0.33; // 平均值法求灰度</span></span><br><span class="line"> <span class="comment">// GrayScale=0.299*R+0.578*G+0.114*B 加权平均求灰度</span></span><br><span class="line"> fixed texGrey = <span class="number">0.299</span> * mainTex.r + <span class="number">0.578</span> * mainTex.g + <span class="number">0.114</span> * mainTex.b;</span><br><span class="line"> texGrey = <span class="built_in">pow</span>(texGrey, <span class="number">0.3</span>);</span><br><span class="line"> texGrey *= <span class="number">1</span> - <span class="built_in">cos</span>(texGrey * <span class="number">3.14</span>);</span><br><span class="line"> fixed brushGrey = (brushTex.r + brushTex.g + brushTex.b)*<span class="number">0.33</span>;</span><br><span class="line"></span><br><span class="line"> fixed blend = texGrey * <span class="number">0.5</span> + brushGrey * <span class="number">0.5</span>;</span><br><span class="line"> fixed4 col = <span class="built_in">fixed4</span>(blend, blend, blend, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> fixed edge = <span class="built_in">pow</span>(NdotV, <span class="number">1</span>) / _Range;</span><br><span class="line"> edge = edge > _Thred ? <span class="number">1</span> : edge;</span><br><span class="line"> edge = <span class="built_in">pow</span>(edge, _Pow);</span><br><span class="line"> fixed4 edgeColor = <span class="built_in">fixed4</span>(edge, edge, edge, edge);</span><br><span class="line"></span><br><span class="line"> col = edgeColor * (<span class="number">1</span> - edgeColor.a) + col * (edgeColor.a);</span><br><span class="line"></span><br><span class="line"> fixed4 rowtex =<span class="built_in">tex2D</span>(_MainTex,i.uv);</span><br><span class="line"> <span class="comment">//saturation饱和度:首先根据公式计算同等亮度情况下饱和度最低的值:</span></span><br><span class="line"> fixed minGray = <span class="number">0.2125</span> * rowtex.r + <span class="number">0.7154</span> * rowtex.g + <span class="number">0.0721</span> * rowtex.b;</span><br><span class="line"> fixed3 mingrayColor = <span class="built_in">fixed3</span>(minGray, minGray, minGray);</span><br><span class="line"> <span class="comment">//根据Saturation在饱和度最低的图像和原图之间差值</span></span><br><span class="line"> float3 RowColor = <span class="built_in">lerp</span>(mingrayColor, rowtex.rgb, _Saturation);</span><br><span class="line"></span><br><span class="line"> float3 blendrow = RowColor * (<span class="number">1</span> - _BlenderRow) + <span class="built_in">float3</span>(brushGrey,brushGrey,brushGrey) * _BlenderRow;</span><br><span class="line"> float3 finalColor = col * blendrow;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">fixed4</span>(finalColor,<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> ENDCG</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h5 id="实现方法"><a href="#实现方法" class="headerlink" title="实现方法"></a><strong>实现方法</strong></h5><h6 id="一、求灰度图"><a href="#一、求灰度图" class="headerlink" title="一、求灰度图"></a>一、求灰度图</h6><p>对灰度图像的处理一般有以下四种方法:</p>
<p><strong>1.分量法</strong></p>
<p>该方法将彩色图像中的三分量的亮度作为三个灰度图像的灰度值,可根据应用需要选取一种灰度图像。即:可以选取RGB中的任一颜色通道作为灰度值。公式为:GrayScale1 = R,GrayScale2 = G,GrayScale3 = B</p>
<p><strong>2.最大值法</strong></p>
<p>该方法将彩色图像中的三分量亮度的最大值作为灰度图的灰度值,一般的,该方法求取后的灰度图亮度最高。公式为:GrayScale = max(R, G, B)</p>
<p><strong>3.平均值法</strong></p>
<p>该方法将彩色图像中的RGB三分量的颜色值来求取平均值作为灰度值。公式为:GrayScale = (R+G+B) / 3</p>
<p><strong>4.加权平均法</strong></p>
<p>该方法根据重要性及其它指标,将三个分量以不同的权值进行加权平均。由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到相对合理的灰度图像。公式为:GrayScale = 0.299 <em>R + 0.578 </em>G + 0.114 *B</p>
<p>此处对主纹理采用加权平均法、对水墨笔刷采用平均值法</p>
<h6 id="二、利用混合公式混合颜色Texture和水墨笔触Texture"><a href="#二、利用混合公式混合颜色Texture和水墨笔触Texture" class="headerlink" title="二、利用混合公式混合颜色Texture和水墨笔触Texture"></a>二、利用混合公式混合颜色Texture和水墨笔触Texture</h6><p>图片混合的多种模式:<a href="https://www.cnblogs.com/kex1n/p/3663533.html">https://www.cnblogs.com/kex1n/p/3663533.html</a></p>
<h6 id="三、利用-VdotN-求边缘效果"><a href="#三、利用-VdotN-求边缘效果" class="headerlink" title="三、利用 VdotN 求边缘效果"></a>三、利用 VdotN 求边缘效果</h6><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line">fixed edge = <span class="built_in">pow</span>(NdotV, <span class="number">1</span>) / _Range;</span><br><span class="line">edge = edge > _Thred ? <span class="number">1</span> : edge;</span><br><span class="line">edge = <span class="built_in">pow</span>(edge, _Pow);</span><br><span class="line">fixed4 edgeColor = <span class="built_in">fixed4</span>(edge, edge, edge, edge);</span><br></pre></td></tr></table></figure>
<h6 id="四、降低原图饱和度,再将其与水墨笔刷进行混合-(可选项)"><a href="#四、降低原图饱和度,再将其与水墨笔刷进行混合-(可选项)" class="headerlink" title="四、降低原图饱和度,再将其与水墨笔刷进行混合 (可选项)"></a>四、降低原图饱和度,再将其与水墨笔刷进行混合 (可选项)</h6><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line">fixed4 rowtex =<span class="built_in">tex2D</span>(_MainTex,i.uv);</span><br><span class="line"><span class="comment">//saturation饱和度:首先根据公式计算同等亮度情况下饱和度最低的值:</span></span><br><span class="line">fixed minGray = <span class="number">0.2125</span> * rowtex.r + <span class="number">0.7154</span> * rowtex.g + <span class="number">0.0721</span> * rowtex.b;</span><br><span class="line">fixed3 mingrayColor = <span class="built_in">fixed3</span>(minGray, minGray, minGray);</span><br><span class="line"><span class="comment">//根据Saturation在饱和度最低的图像和原图之间差值</span></span><br><span class="line">float3 RowColor = <span class="built_in">lerp</span>(mingrayColor, rowtex.rgb, _Saturation);</span><br><span class="line">float3 blendrow = RowColor * (<span class="number">1</span> - _BlenderRow) + <span class="built_in">float3</span>(brushGrey,brushGrey,brushGrey) * _BlenderRow;</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Shader学习</category>
</categories>
<tags>
<tag>Unity</tag>
<tag>Shader</tag>
</tags>
</entry>
<entry>
<title>【学习记录】—— Unity部分性能优化</title>
<url>/2022/08/10/%E5%AE%9E%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Unity%E9%83%A8%E5%88%86%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/</url>
<content><![CDATA[<h1 id="实习笔记——Unity-性能优化"><a href="#实习笔记——Unity-性能优化" class="headerlink" title="实习笔记——Unity 性能优化"></a>实习笔记——Unity 性能优化</h1><p>本文内容是对以下两个官方教程视频的笔记整理,内容广泛但不深入,可以看作一个目录框架,并且视频发布于 2020.7,时至今日,Unity 经过多次版本更新,有些方法可能已经过时或失效</p>
<p><a href="https://www.bilibili.com/video/BV1Tt4y1X7f6?t=1824&vd_source=e4b21da575ef85d87ce1b8e9f64ca190">[Unity 活动]-Unite Now - (中文字幕)性能优化技巧(上)</a></p>
<p><a href="https://www.bilibili.com/video/BV1Bp4y1i7wK?vd_source=e4b21da575ef85d87ce1b8e9f64ca190">[Unity 活动]-Unite Now - (中文字幕)性能优化技巧(下)</a></p>
<span id="more"></span>
<h3 id="1、分析工具-(Profiling-tools-)"><a href="#1、分析工具-(Profiling-tools-)" class="headerlink" title="1、分析工具 (Profiling tools )"></a>1、分析工具 (Profiling tools )</h3><p>关于分析工具,记住一定要在目标设备上进行分析,在工作设备上的分析结果可能会和目标设备上的实际情况有所出入。</p>
<p><strong>【Unity Editor Profiler】</strong></p>
<p>性能分析器(Profiler)是 Unity 内部集成的一款性能优化工具,可以检查脚本代码,查看运行过程中资源使用情况,边开发边了解资源分配的情况,还可以比较不同平台上的性能。但是在运行过程中可能会增加一些性能消耗,降低程序运行速度。</p>
<p>在 Edit->Preference->Analysis->Profiler 进行基本设置<br><img src="https://s2.loli.net/2022/08/12/34UM7xvmqz2uhVE.png" alt="2.png"></p>
<p>在 Window->Analysis->Profiler 中打开<br><img src="https://s2.loli.net/2022/08/12/7HCSeGkXwgI95Yh.png" alt="1.png"></p>
<p><strong>【Memory Profiler 】</strong></p>
<p>功能:</p>
<ul>
<li>进一步分析应用程序的内存使用状况</li>
<li>比较不同时间的快照,以便找出内存泄漏</li>
<li>查看内存配置碎片化的问题</li>
</ul>
<p>详情可见 <a href="https://zhuanlan.zhihu.com/p/27963992">Memory Profiler 内存分析器使用方法</a></p>
<p><strong>【Frame Debugger】</strong></p>
<p>是时比较常用的工具,可以逐帧分析 Draw Call 等渲染步骤的细节<br>在 Window->Analysis->Frame Debugger 中打开</p>
<p><img src="https://s2.loli.net/2022/08/12/oAHJGLCDUzYSb5m.png" alt="3.png"></p>
<p>以上是 Unity 提供的工具,除此之外还有一些其他厂商提供的工具</p>
<p><img src="https://s2.loli.net/2022/08/12/wV6S8vpKJzm2DjA.png" alt="4.png"></p>
<h3 id="2、栈(stack)与堆(heap)"><a href="#2、栈(stack)与堆(heap)" class="headerlink" title="2、栈(stack)与堆(heap)"></a>2、栈(stack)与堆(heap)</h3><p><strong>【Stack】</strong><br>栈是内存中存储函数和值类型的地方,当我们调用一个函数时,会将该函数体与参数 push 到堆栈中,如果该函数中调用了其他函数,就会继续将那个函数 push 到栈中,知道函数执行完毕后,才会将其 pop 出去,因此我们在看 Debug 信息的时候,就会发现 Log 里面能够做到一层层的方法回溯,方便我们查看整体的调用过程,这也就是栈回溯。<br>在这里不会遇到碎片化或者垃圾收集的问题,但是如果调用了太多的函数,一直 push 进栈却没有 pop,最后会导致栈溢出</p>
<p><strong>【Heap】</strong><br>堆是内存中另一个区域,比栈大。我们将所有的类别、实体和对象存放在这。通常我们每创建一个新的对象,会在堆中找到下一个足够存放的空位置将其存储。但是当我们销毁对象后,内存空间不会马上释放出来,而是标记成未使用,之后交由垃圾收集器去释放这部分内存空间。<br>对象实例化和摧毁的过程其实很慢,所以我们要尽可能的避免在堆中配置内存的行为</p>
<h3 id="3、垃圾收集器-(Garbage-Collection)"><a href="#3、垃圾收集器-(Garbage-Collection)" class="headerlink" title="3、垃圾收集器 (Garbage Collection)"></a>3、垃圾收集器 (Garbage Collection)</h3><p>是用于清理先前配置的内存的机制,它的工作是对所有处于堆上的对象或内存遍历一次,找到需要被释放的东西</p>
<p>每一次 GC,都会遍历堆积上所有的对象,找到需要释放的东西,也就是没有被引用的对象,然后将其释放。但是有时候我们的一些错误引用,导致一些我们希望释放掉的对象没有被 GC 掉,那么就会造成内存泄漏。</p>
<h3 id="选择正确的数据结构"><a href="#选择正确的数据结构" class="headerlink" title="选择正确的数据结构"></a>选择正确的数据结构</h3><p>获取多个游戏对象实体,我们使用特定的数据结构来呈现数据和对象</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">GameObject[] m_NetworkedObjects;</span><br><span class="line"><span class="function"><span class="keyword">or</span></span></span><br><span class="line"><span class="function"><span class="title">Dictionary</span><<span class="title">int</span>, <span class="title">GameObject</span>> m_NetworkedObjects</span>;</span><br><span class="line"><span class="keyword">or</span></span><br><span class="line">List<>() m_NetworkedObjects;</span><br></pre></td></tr></table></figure>
<p>在阵列或串列结构中使用索引的成本很低,但是增加或移除对象时,将比在字典结构中进行更昂贵,所以要<strong>根据需求</strong>使用的合适的数据结构</p>
<h3 id="4、对象池-(Object-pooling)"><a href="#4、对象池-(Object-pooling)" class="headerlink" title="4、对象池 (Object pooling)"></a>4、对象池 (Object pooling)</h3><h4 id="为什么需要对象池?"><a href="#为什么需要对象池?" class="headerlink" title="为什么需要对象池?"></a>为什么需要对象池?</h4><p>在游戏中,创建和销毁对象是十分常见的操作,通常我们是使用游戏对象的实例化 <em>Instantiate()</em> 和摧毁 <em>Destroy()</em> 来实现的,但是如果太过频繁的执行这个行为,每次垃圾收集器执行时其负载就会增加,因为在很多时间点上都会有大量的已摧毁物体存在,这会造成 CPU 负载峰值并导致堆碎片化</p>
<h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p>在真正需要对象之前,先把它们实例化好,然后将其摧毁再创建新的对象,我们要做的是回收对象,将其藏在某处,恢复对象的初始参数,在用到的时候再把它们放到相应的位置。<br>注意点:</p>
<ul>
<li>对象池的大小(要满足需求)</li>
<li>一开始要产生多少数量的对象池在池中</li>
</ul>
<p>可以使用单例模式来设计对象池</p>
<h3 id="5、脚本化对象-(Scriptable-Objects)"><a href="#5、脚本化对象-(Scriptable-Objects)" class="headerlink" title="5、脚本化对象 (Scriptable Objects)"></a>5、脚本化对象 (Scriptable Objects)</h3><h4 id="(1)基本介绍"><a href="#(1)基本介绍" class="headerlink" title="(1)基本介绍"></a>(1)基本介绍</h4><p>Scriptable Objects 是用来存储数据的一个资源文件,有着资源文件的特性,可以用来存储数据。</p>
<p>如果项目中使用了 prefab,并在其内的 MonoBehaviour 中存放固定数据,如下</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AB</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">float</span> a;</span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">float</span> b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>有一个叫 AB 的 MonoBehaviour 对象,里面有 a,b 两个变量,如果我有很多这样的对象,并且 a,b 在每个游戏对象里都通用的话,就能用它们来做游戏参数设置,每次实例化这个 prefab 时,其组件中的这些数据就会重复一份,相当于让这两个浮点数重复出现在有此脚本的 AB 对象上,但是它们的数值都一样,这就造成了不必要的重复。<br>当遇到这种情况时,就可以改用 Scriptable Objects</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">ABConfig</span> : <span class="title">ScriptableObject</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">float</span> a;</span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">float</span> b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AB</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> ABConfig m_ABConfig;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>此时让我们的 prefab 去参考它,就只需要耗费一组这样数据的内存,每个 AB 对象都会指向同一个 Scriptable Object 对象以做参数设置之用,即使我们有几千个 AB 对象,也只需要花费两个浮点数的内存。</p>
<blockquote>
<p>注:</p>
<p>MonoBehaviour 是以组件形式挂在 GameObject 上的,而 ScriptableObject 则以 Assets 资源的形式存在的</p>
</blockquote>
<h3 id="6、变量或属性-(Variables-or-properties)"><a href="#6、变量或属性-(Variables-or-properties)" class="headerlink" title="6、变量或属性 (Variables or properties)"></a>6、变量或属性 (Variables or properties)</h3><p>为了封装的安全性,在写程序时让对象属性通过 getter/setter 产生,所以对象属性基本上是方法调用,调用太多次时,花费在栈中时间就会增加。如果发生在频繁执行的循环中,就需要考虑对其优化。<br><strong>【优化建议】</strong></p>
<p>可以利用 #if 这类的前置指令来处理</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">if</span> DEVELOPMENT_BUILD</span></span><br><span class="line"> <span class="keyword">private</span> <span class="built_in">int</span> health;</span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">int</span> Health { <span class="keyword">get</span> { <span class="keyword">return</span> health; } }</span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">int</span> Health;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure>
<p>如果还在开在开发中,或者程序在编辑器中运行,那就都采用对象属性访问的方式。</p>
<p><em>#else</em> 代表如果在设备上执行已发布的构建版本,那就把它当普通的变量来使用</p>
<h3 id="7、Resources-文件夹"><a href="#7、Resources-文件夹" class="headerlink" title="7、Resources 文件夹"></a>7、Resources 文件夹</h3><p>代码中的资源管理通常会使用 Resources 文件夹以及下面两种方法</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">Resources.Load(...);</span><br><span class="line">Resources.UnloadUnusedAssets();</span><br></pre></td></tr></table></figure>
<p>这些方法有常见的坑:</p>
<ul>
<li>Resources 文件夹里的所有资源都会和游戏一起打包,即使并没有用到</li>
<li>Resources 中的资源数量会直接影响游戏的启动时间</li>
</ul>
<p><strong>【优化建议】</strong></p>
<p>不要直接使用 Resources 文件夹,而是改用 Addressable 资源系统,以更有效率的方式管理资源的载入和卸载</p>
<h3 id="8、移除空的事件函数"><a href="#8、移除空的事件函数" class="headerlink" title="8、移除空的事件函数"></a>8、移除空的事件函数</h3><p>例如:</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">{</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">{</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Awake</span>()</span></span><br><span class="line">{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>即使空的函数,有时也会带来微性能消耗,应该将其删除</p>
<h3 id="9、避免在-Start-和-Awake-中写入负载很重的初始化逻辑"><a href="#9、避免在-Start-和-Awake-中写入负载很重的初始化逻辑" class="headerlink" title="9、避免在 Start() 和 Awake() 中写入负载很重的初始化逻辑"></a>9、避免在 Start() 和 Awake() 中写入负载很重的初始化逻辑</h3><p>如果在 Start() 和 Awake() 中写入了负载很重的初始化逻辑,游戏的启动画面或者载入画面将需要花更长的时间渲染,用户将会看到长时间的黑屏,因为你必须等每个游戏对象都完成 Start 和 Awake 执行</p>
<blockquote>
<p>Unity 会在第一个 Awake() 和 Start() 方法执行后渲染第一个画面</p>
</blockquote>
<p>可以先简单呈现一个东西,然后再开始其他对象初始化的步骤</p>
<h3 id="10、Hash-the-value-instead"><a href="#10、Hash-the-value-instead" class="headerlink" title="10、Hash the value instead"></a>10、Hash the value instead</h3><p>如果我们从代码指定参数或是指定材质和着色器的属性,例如</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">animator.SetTrigger(<span class="string">"Jump"</span>);</span><br><span class="line"></span><br><span class="line">material.SetTexture(<span class="string">"_MainMap"</span>, selectedTexture);</span><br><span class="line"></span><br><span class="line">shader.SetGlobalColor(<span class="string">"_MainColor"</span>, selectedColor);</span><br></pre></td></tr></table></figure>
<p>我们常用包含属性名称的字符串当参数去调用对应的方法,对于程序员来说方便且好看,然而在 Unity 内部根本不用这些字符串,而会把它们杂凑成一个对应属性代号的整数,如果我们把这些函数写在 Update()等频繁调用的函数中,Unity 实际上会反复进行杂凑运算,造成不必要的性能消耗。</p>
<p><strong>【优化建议】</strong></p>
<p>一开始算出杂凑值,然后直接使用可以传入整数代号的重载方法</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="built_in">int</span> parameterId = Animator.StringToHash(<span class="string">"Jump"</span>);</span><br><span class="line">animator.SetTrigger(parameterID);</span><br><span class="line"></span><br><span class="line"><span class="built_in">int</span> propertyId = Shader.PropertyToID(<span class="string">"_MainMap"</span>);</span><br><span class="line">material.SetTexture(propertyId, selectedTexture);</span><br><span class="line"></span><br><span class="line"><span class="built_in">int</span> propertyId = Shader.PropertyToID(<span class="string">"_MainColor"</span>);</span><br><span class="line">shader.SetGlobalColor(propertyId, selectedColor);</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>广泛使用可以省下很多处理器的时间</p>
<h3 id="11、减少层级架构的复杂性"><a href="#11、减少层级架构的复杂性" class="headerlink" title="11、减少层级架构的复杂性"></a>11、减少层级架构的复杂性</h3><p><img src="https://s2.loli.net/2022/08/12/uIV3OtYCcGX5xB1.png" alt="6.png"></p>
<p>处于某些原因,我们的场景中可能有很深的嵌套结构,当我们对有许多子物体的父物体进行平移、旋转、缩放等位置改变时,即使它的子对象在转换前后看不出有什么变化,我们还是造成了不必要的转换运算。较深的层级结构会让垃圾收集器花更多时间在层级结构之间遍历。<br><strong>【优化建议】</strong><br>尽量避免很深的层级结构,将那些真正需要坐标转换的对象从不需要坐标转换的对象中分离出来,这也可以加快垃圾收集器的处理时间</p>
<h3 id="12、Accelerometer-Frequency-(没用过这玩意,不是很懂)"><a href="#12、Accelerometer-Frequency-(没用过这玩意,不是很懂)" class="headerlink" title="12、Accelerometer Frequency (没用过这玩意,不是很懂)"></a>12、Accelerometer Frequency (没用过这玩意,不是很懂)</h3><p>这个功能定义 Unity 从设备读取加速度仪信息的频率,在不需要这个功能的项目中可以关闭,即使有用到也尽量开到最低。</p>
<h3 id="13、移动游戏对象"><a href="#13、移动游戏对象" class="headerlink" title="13、移动游戏对象"></a>13、移动游戏对象</h3><p>Unity 中有很多移动对象的方法,如下</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">{</span><br><span class="line"> transform.Translate(...);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>如果对象需要使用碰撞判定,我们则会加上刚体组件与碰撞体,通过坐标转换移动有刚体组件的对象时,会造成 PhysX 物理引擎整体重新计算,对于复杂的场景成本很高。</p>
<p><strong>【优化建议】</strong></p>
<p>如果要移动一个有刚体组件的对象,使用 Rigidbody 类提供的方法,例如:</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">FixedUpdate</span>()</span></span><br><span class="line">{</span><br><span class="line"> rigidBody.MovePosition();</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h3 id="14、GameObject-AddComponent-…"><a href="#14、GameObject-AddComponent-…" class="headerlink" title="14、GameObject.AddComponent(…)"></a>14、GameObject.AddComponent(…)</h3><p>当我们在运行期间将对象实例化时,常用增加组件来定义对象的行为</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">GameObject newBarrel = Instantiate(m_Template);</span><br><span class="line"></span><br><span class="line">newBarrel.AddComponent(<span class="keyword">typeof</span>(XXXX));</span><br><span class="line">newBarrel.AddComponent(<span class="keyword">typeof</span>(XXXX));</span><br><span class="line">newBarrel.AddComponent(<span class="keyword">typeof</span>(XXXX));</span><br><span class="line">newBarrel.AddComponent(<span class="keyword">typeof</span>(XXXX));</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>实例化一个 Barrel 游戏对象时,如果要增加一些组件,就一直要调用 <em>AddComponent()</em> 函数,在运行期间调用该函数效率很低。当我们在运行期间增加组件时,Unity 会做如下事情:</p>
<p>1、检查组件有无 DisallowMultipleComponent 的设置,如果有,还要去检查是否有同类组件加入<br>2、检查是否存在 RequireComponent 设置,若存在,就代表这个组件需要别的组件同步加入,然后必须加入那些组件,然后再重复一遍上述的检查<br>3、还需要调用所有被加入的 MonoBehaviour 的 Awake 方法</p>
<p>上述步骤都发生在堆(heap)上 ,会增加垃圾收集器的处理时间,影响性能</p>
<p><strong>【优化建议】</strong></p>
<p>尽可能避免在执行期间加入组件</p>
<h3 id="15、为参照建立缓存-(Cache-your-references)"><a href="#15、为参照建立缓存-(Cache-your-references)" class="headerlink" title="15、为参照建立缓存 (Cache your references)"></a>15、为参照建立缓存 (Cache your references)</h3><p>找到场景中的游戏对象是很常见的需求,对象可能是运行阶段或初始化阶段产生的。</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">GameObject.Find(...);</span><br><span class="line">GameObject.GetComponent(...);</span><br></pre></td></tr></table></figure>
<p>GameObject.Find() 一类的方法需要 Unity 遍历所有内存中的游戏对象以及组件,在复杂场景中相当低效。<br>GameObject.GetComponent() 会查询所有附加到游戏对象上的组件,增加运行阶段的成本</p>
<p><strong>【优化建议】</strong></p>
<p>为刚找到的对象参照建立缓存</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">{</span><br><span class="line"> GameObject go = GameObject.Find();</span><br><span class="line"> go.Something();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>也就是只调用一次 Find,充分利用结果,缓存最好在 Start 方法内建立</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">GameObject go;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">{</span><br><span class="line"> go = GameObject.Find();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">{</span><br><span class="line"> go.Something();</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>当找到那个对象后,整个游戏就只使用缓存的版本</p>
<h3 id="16、纹理导入的设置"><a href="#16、纹理导入的设置" class="headerlink" title="16、纹理导入的设置"></a>16、纹理导入的设置</h3><p>目的: 尽可能减少文件大小并保证视觉效果,即在磁盘、空间以及视觉效果之间取得平衡</p>
<ul>
<li>根据平台的不同,将纹理大小上限设为该平台的最小值</li>
<li>确定纹理大小是 2 的幂次方,因为某些压缩格式可能无法支持非 2 的幂次方的压缩,具体可见<a href="https://docs.unity3d.com/2017.4/Documentation/Manual/class-TextureImporterOverride.html">纹理压缩</a>,同时尽可能将多张纹理合并为大图。</li>
<li>对于背景纹理或者其他不透明的纹理,可以将其 Alpha Channel 移除</li>
<li>如果不需要从代码访问纹理的底层数据,可以将 Read/Write Enabled 选项取消</li>
<li>如果较低的 16bit 的颜色格式就已经足够,则不需要使用 32bit 格式</li>
<li>对于相机的 Z 值改变不会有任何变化的纹理(例如 UI 等),可以将 Mipmaps 关闭</li>
</ul>
<h3 id="17、mesh-网格的导入设置"><a href="#17、mesh-网格的导入设置" class="headerlink" title="17、mesh 网格的导入设置"></a>17、mesh 网格的导入设置</h3><p>若 3D 资源导入不当,可能会造成文件占用过大、执行期内存使用过高等后果,在保证视觉效果的情况下尽可能提高压缩程度</p>
<ul>
<li>Read/Write Enabled 选项如果被开启,Unity 将会存储两份 Mesh,导致执行期内存用量变为两倍</li>
<li>如果该 3D 资源没有使用动画,可以将 Rigs 关闭,例如场景中的房子等</li>
<li>对于法线向量和切线向量,如果该模型的材质(具体可以是材质所用的 Shader)没有使用它们,也可以将其关闭。</li>
</ul>
<h3 id="18、图形部分"><a href="#18、图形部分" class="headerlink" title="18、图形部分"></a>18、图形部分</h3><ul>
<li>降低 DrawCall(大坑)</li>
<li>尽量减少不必要的阴影,一个带有 MeshRender 组件的物体会自动开启阴影效果,如果有无该阴影对场景影响不大,可以将其关闭</li>
</ul>
<h3 id="19、某些组件的-RayCaster-选项"><a href="#19、某些组件的-RayCaster-选项" class="headerlink" title="19、某些组件的 RayCaster 选项"></a>19、某些组件的 RayCaster 选项</h3><p>该组件是用来处理输入事件的,例如触控或者鼠标点击到游戏对象,有时对于那些应该不能互动(也就是点了没反应)也附带了 RayCaster 组件。因此,当每次鼠标点击或者触控时,系统就需要遍历所有可能接受输入事件的 UI 元素,这就会有许多“点击位置或触摸位置是否落在矩形当中的”的检查,来判断点击或触摸该对象是否应该做出反应。在 UI 相当复杂的情况下,这个运算的成本就会很高。所以应该确保只有那些具备可互动功能的组件开启 RayCaster,以减少 CPU 运行时间和不必要的评估。</p>
<h3 id="20、全屏-UI"><a href="#20、全屏-UI" class="headerlink" title="20、全屏 UI"></a>20、全屏 UI</h3><p>有时对主画面进行展示时,会对其他 UI 元素或者集合对象进行遮蔽,此时虽然我们并没有看到场景中的 3D 对象,但是 CPU 和 GPU 还是会有运行成本,为了减少 CPU 和 GPU 的运行消耗,可以做出如下优化:</p>
<ul>
<li>将渲染 3D 场景的摄像机关掉。对于被完全遮住的部分,直接关闭渲染该部分的摄像机。建议关闭 canvas 组件而不是游戏对象本身,这样在下次重新出现时,能减少运行处理时间。</li>
<li>隐藏被遮挡掉的其他 UI</li>
<li>尽可能降低帧率,如果当前 UI 是静态的,或者动画帧率较低,就没必要再把帧率维持在 60fps</li>
</ul>
]]></content>
<categories>
<category>Unity</category>
</categories>
<tags>
<tag>Unity</tag>
</tags>
</entry>
<entry>
<title>计算机网络爬虫</title>
<url>/2022/07/27/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%AE%9E%E9%AA%8C/</url>
<content><![CDATA[<h3 id="一、基本信息"><a href="#一、基本信息" class="headerlink" title="一、基本信息"></a>一、基本信息</h3><p>(略)</p>
<h3 id="二、实验目的"><a href="#二、实验目的" class="headerlink" title="二、实验目的"></a>二、实验目的</h3><p>要求学生掌握Socket编程技术及令牌总线协议工作过程</p>
<script type="math/tex; mode=display">
</script><span id="more"></span>
<h3 id="三、基础实验内容"><a href="#三、基础实验内容" class="headerlink" title="三、基础实验内容"></a>三、基础实验内容</h3><h4 id="Ⅰ、traceroute"><a href="#Ⅰ、traceroute" class="headerlink" title="Ⅰ、traceroute"></a>Ⅰ、traceroute</h4><p><strong>1、实验目的:</strong>熟悉traceroute的使用</p>
<p><strong>2、实验内容:</strong> 用traceroute测量到163网站(www.163.com)和到微软公司(www.microsoft.com)网站的路径。分析测量结果。</p>
<p><strong>3、实验流程及结果</strong></p>
<p>在Ubuntu上下载安装traceroute</p>
<p><img src="https://s2.loli.net/2022/07/26/lIFAdX1gBsMZjVw.png" alt=""></p>
<p>用traceroute测量到163网站(www.163.com)的路径</p>
<p><img src="https://s2.loli.net/2022/07/26/Swt5TgJpsduilEn.png" alt="ICMP 163"></p>
<p>用traceroute测量到微软网站(www.microsoft.com)的路径</p>
<p><img src="https://s2.loli.net/2022/07/26/8MCBeayGq62RVzj.png" alt="ICMP microsoft"></p>
<p>上述输出信息显示了跟踪到的路由地址信息。记录从序号 1 开始,每个记录就是一跳,而每一跳表示经过的一个网关。记录给出了每个网关对应的IP地址。其中,为 <em>*</em> 的记录表示可能被防火墙拦截的 ICMP 的返回信息。</p>
<p>在linux上使用traceroute,默认使用udp协议,除了第一跳,剩下的都是<em> </em> *。是因为虚拟机 nat 路由器,默认丢弃port>32767的包。在命令中加入 -l 强制使用ICMP得到上图结果。</p>
<h4 id="Ⅱ、wireshark"><a href="#Ⅱ、wireshark" class="headerlink" title="Ⅱ、wireshark"></a>Ⅱ、wireshark</h4><p><strong>1、实验目的:</strong>熟悉wireshark的使用</p>
<p><strong>2、实验内容:</strong>下载安装wireshark软件,设置捕获条件,用wireshark捕获数据包,对以太网帧和IP数据包进行分析。</p>
<p><strong>3、实验流程及结果</strong></p>
<p>Wireshark 是一个免费开源的网络数据包分析软件。它的功能是截取网络数据包,并尽可能显示出最为详细的网络数据包数据。</p>
<p>下载安装Wireshark并启动</p>
<p><img src="https://s2.loli.net/2022/07/26/gVl5RE8uwCtqTfM.png" alt=""></p>
<p>查询网卡</p>
<p><img src="https://s2.loli.net/2022/07/26/1wGeMWDXuI27TSl.png" alt=""></p>
<p>打开火绒浏览器开始发包测试</p>
<p><img src="https://s2.loli.net/2022/07/26/YKhqtJekOu3IUV7.png" alt=""></p>
<p>TCP找目的地址,捕获数据包,对以太网帧和IP数据包进行分析。</p>
<p><img src="https://s2.loli.net/2022/07/26/HgNysLPCYbJ7Awa.png" alt=""></p>
<p>将 <em>TCP 80 端口</em> 作为过滤条件进行筛选分析</p>
<p><img src="https://s2.loli.net/2022/07/26/tdFr4m3YbPiUoAL.png" alt=""></p>
<h3 id="四、工程性实验内容"><a href="#四、工程性实验内容" class="headerlink" title="四、工程性实验内容"></a>四、工程性实验内容</h3><h4 id="Ⅰ、Robots协议"><a href="#Ⅰ、Robots协议" class="headerlink" title="Ⅰ、Robots协议"></a>Ⅰ、Robots协议</h4><p><strong>具体内容:</strong>查看淘宝网的Robots协议,了解抓取网站数据需要遵循的规则</p>
<p>Robots协议的全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以爬取,哪些页面不可以爬取。这个协议是互联网中的道德规范,虽没写入法律,但应遵守。在网站末尾加<code>/robots.txt</code>可以进行查看。</p>
<p><img src="https://s2.loli.net/2022/07/26/D8pZfVWqHM67LNu.png" alt="image-20220427171824767"></p>
<p>Robots协议的语法:#注释,*代表所有,/代表根目录。</p>
<p>无robots协议的网站,信息可以爬取。</p>
<p>自动或人工识别robots.txt,再进行内容爬取,Robots协议是建议但非约束性,网络爬虫可以不遵守,但存在法律风险。访问量很小的可以遵循,访问量大的建议遵循。非商业的建议遵循,涉及商业利益的必须遵循,爬取全网信息必须要遵循。如果是类似人类行为的爬取信息,可不遵循。</p>
<h4 id="Ⅱ、-搜索关键词查询确定其关键词的查询接口"><a href="#Ⅱ、-搜索关键词查询确定其关键词的查询接口" class="headerlink" title="Ⅱ、 搜索关键词查询确定其关键词的查询接口"></a>Ⅱ、 搜索关键词查询确定其关键词的查询接口</h4><p>以豆瓣图书为例,进入豆瓣读书网站</p>
<p><img src="https://s2.loli.net/2022/07/26/78cWoRNs1kzgBpZ.png" style="zoom:67%;"></p>
<p>随便点击一本书,观察其网址</p>
<p><img src="https://s2.loli.net/2022/07/26/iALYlmPfGcBs1I2.png" style="zoom:67%;"></p>
<p>观察到红框部分即为该书在豆瓣数据库中的代号</p>
<p>进入短评部分</p>
<p><img src="https://s2.loli.net/2022/07/26/QMTEzFGlNgv2xLY.png" style="zoom:67%;"></p>
<p>观察到前一个红框部分为该书的代码,后一个红框部分为这一页短评的起始标号,豆瓣书籍网页版一页共有20条短评,所以 start = num (num从0开始并且为20的整倍数)</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">url = <span class="string">"https://book.douban.com/subject/{}/comments/?start={}&limit=20&status=P&sort=new_score"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># booknum和index分别为书籍代码和start起始下标</span></span><br><span class="line">u = url.<span class="built_in">format</span>(<span class="built_in">str</span>(booknum),<span class="built_in">str</span>(index))</span><br></pre></td></tr></table></figure>
<p>所以之后对豆瓣图书评论的查询靠替换关键字完成</p>
<h4 id="Ⅲ、爬取豆瓣图书评论"><a href="#Ⅲ、爬取豆瓣图书评论" class="headerlink" title="Ⅲ、爬取豆瓣图书评论"></a>Ⅲ、爬取豆瓣图书评论</h4><h5 id="具体内容"><a href="#具体内容" class="headerlink" title="具体内容"></a>具体内容</h5><p>抓取豆瓣读书中对某本书的前50条短评内容并计算星级评定分数的平均值(保留两位小数)</p>
<h5 id="实验步骤"><a href="#实验步骤" class="headerlink" title="实验步骤"></a>实验步骤</h5><p>TCP协议:传输控制协议(Transmission Control Protocol,缩写为TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。</p>
<p>主要适用的场景:</p>
<p>适合于对传输质量要求较高,以及传输大量数据的通信。</p>
<p>在需要可靠数据传输的场合,通常使用TCP协议。</p>
<p>HTTP/HTTPS等即网络服务都采用TCP协议。</p>
<p>TCP通信需要经过创建连接、数据传送、终止连接三个步骤。</p>
<ul>
<li><p>初始化 url ,并通过 get() 函数逐网页爬取网页源代码</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">url = <span class="string">"https://book.douban.com/subject/{}/comments/?start={}&limit=20&status=P&sort=new_score"</span></span><br><span class="line"><span class="keyword">for</span> index <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">60</span>, <span class="number">20</span>):</span><br><span class="line"> <span class="comment"># booknum 为豆瓣书籍编号,本实验选用的是35593780</span></span><br><span class="line"> u = url.<span class="built_in">format</span>(<span class="built_in">str</span>(booknum),<span class="built_in">str</span>(index))</span><br><span class="line"> r = get(u)</span><br></pre></td></tr></table></figure>
</li>
<li><p>创建 socket 实例,处理 https 的库</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">get</span>(<span class="params">url</span>):</span><br><span class="line"> host = <span class="string">'book.douban.com'</span></span><br><span class="line"> path = url[<span class="number">23</span>:]</span><br><span class="line"> s = ssl.wrap_socket(socket.socket())</span><br></pre></td></tr></table></figure>
</li>
<li><p>初始化端口,与主机建立起连接</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">port = <span class="number">443</span> <span class="comment"># 端口 https 默认端口443</span></span><br><span class="line">s.connect((host, port)) <span class="comment"># 建立起连接,连接主机</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>设置报文格式</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 报文格式: GET+空格+com后面的部分+空格+HTTP/1.1(或1.0)+\r\n+Host:+域名+\r\n\r\n</span></span><br><span class="line">request = <span class="string">'GET {} HTTP/1.1\r\nHost: {}\r\n\r\n'</span>.<span class="built_in">format</span>(path, host) <span class="comment"># 构建请求</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>发送请求并接受数据</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">s.send(request.encode(<span class="string">'utf-8'</span>)) <span class="comment"># 发送请求 转码,将str 转成bytes类型</span></span><br><span class="line">response = <span class="string">b''</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="comment"># 接受数据</span></span><br><span class="line"> r = s.recv(<span class="number">1024</span>)</span><br><span class="line"> response += r</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(r) < <span class="number">1024</span>:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line">response = response.decode(<span class="string">'utf-8'</span>) <span class="comment"># 解码</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>对数据进行查找分析</p>
<p>此处主要使用 str 类型所带的 find() 函数</p>
<p>Python find() 方法检测<strong>字符串</strong>中是否包含<strong>子字符串</strong> str</p>
<p>如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,如果包含子字符串返回开始的索引值,否则返回-1。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># html 为字符串</span></span><br><span class="line"><span class="comment"># userstart 为每个用户所在源代码的开始位置</span></span><br><span class="line"><span class="comment"># userend 每个用户所在源代码的结束位置</span></span><br><span class="line"><span class="comment"># start_str为所查询字符串的前部分</span></span><br><span class="line"><span class="comment"># end_str为所查询字符串的后部分</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">getdata</span>(<span class="params">html, userstart, userend, start_str, end_str</span>):</span><br><span class="line"> start = html.find(start_str, userstart, userend)</span><br><span class="line"> end = html.find(end_str, start)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span> <span class="keyword">if</span> start == -<span class="number">1</span> <span class="keyword">else</span> html[start + <span class="built_in">len</span>(start_str):end]</span><br></pre></td></tr></table></figure>
<p>对于星级的平均分计算,考虑到有些用户没有打分,计算平均分时需将其删除。对于上面的数据查询函数 getdata() ,如果未能查询到该用户所打的分数,则在star[] 数组中写入0,若查询到所打的分数,则写入该分数。</p>
</li>
<li><p>将结果进行整理输出</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># douban.txt 文件和当前py文件位于同一目录之下</span></span><br><span class="line">output = <span class="built_in">open</span>(<span class="string">"douban.txt"</span>,<span class="string">'w'</span>,encoding=<span class="string">'UTF—8'</span>)</span><br></pre></td></tr></table></figure>
</li>
</ul>
<p>将50条评论逐行输入至 douban.txt 文件中,在第51行输入平均分数,第52行输入书名。</p>
<h5 id="可视化界面"><a href="#可视化界面" class="headerlink" title="可视化界面"></a>可视化界面</h5><p>该可视化界面采用 Unity3D 制作,版本为 2021.2.7f1c1,所用语言为 C#</p>
<p><strong>【展示】</strong></p>
<p><img src="https://s2.loli.net/2022/07/26/4tcVLn1Huael2k5.png" alt="image-20220427223747102"></p>
<p>[1] 处按钮为显示前页评论</p>
<p>[2] 处按钮为显示后页评论</p>
<p>[3] 处为书名显示区域</p>
<p>[4] 处为所求得的平均分数(保留两位小数)</p>
<p>[5] [6] [7] [8] 处为评论内容显示区域,可上下拖拽查看全部内容</p>
<p>【<strong>代码及主要制作思路</strong>】</p>
<p>创建一个string类型的数组,用以存储评论、书名、分数等数据</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="built_in">string</span>[] txt</span><br></pre></td></tr></table></figure>
<p>使用<strong>绝对路径</strong>读取上述 douban.txt 文件</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> System.IO;</span><br><span class="line">txt = File.ReadAllLines(<span class="string">"C:\\Users\\EKKO\\Desktop\\ComputerNetwork\\CNLab\\douban.txt"</span>);</span><br></pre></td></tr></table></figure>
<p>此时 txt 数组共有52个元素,存储50条评论、一个书名、一个平均分数</p>
<p>新建 TEXT 类型数组 comments 并与UI上的组件进行绑定,将书名和分数直接显示。</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">comments[<span class="number">0</span>] = transform.GetChild(<span class="number">7</span>).GetComponent<Text>();</span><br><span class="line">comments[<span class="number">0</span>].text = <span class="string">"平均分数:"</span>+txt[<span class="number">50</span>];</span><br><span class="line">comments[<span class="number">1</span>] = transform.GetChild(<span class="number">1</span>).GetChild(<span class="number">0</span>).GetComponent<Text>();</span><br><span class="line">comments[<span class="number">2</span>] = transform.GetChild(<span class="number">2</span>).GetChild(<span class="number">0</span>).GetComponent<Text>();</span><br><span class="line">comments[<span class="number">3</span>] = transform.GetChild(<span class="number">3</span>).GetChild(<span class="number">0</span>).GetComponent<Text>();</span><br><span class="line">comments[<span class="number">4</span>] = transform.GetChild(<span class="number">4</span>).GetChild(<span class="number">0</span>).GetComponent<Text>();</span><br><span class="line">bookname = transform.GetChild(<span class="number">9</span>).GetComponent<Text>();</span><br><span class="line">bookname.text = <span class="string">"《"</span>+txt[<span class="number">51</span>] +<span class="string">"》"</span>; </span><br></pre></td></tr></table></figure>
<p>调用按钮点击相应事件,为 ‘前页’ ‘后页’ 按钮绑定相关操作</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line">upbtn.onClick.AddListener(setindexup);</span><br><span class="line">downbtn.onClick.AddListener(setindexdown);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">setindexup</span>()</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>;i <<span class="number">5</span>;i++)</span><br><span class="line"> <span class="keyword">if</span>(index[i] < <span class="number">49</span>)</span><br><span class="line"> index[i] += <span class="number">4</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">setindexdown</span>()</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>;i <<span class="number">5</span>;i++)</span><br><span class="line"> <span class="keyword">if</span>(index[i] > <span class="number">4</span>)</span><br><span class="line"> index[i] -= <span class="number">4</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>最后在 Update() 函数中逐帧进行赋值操作,达到动态刷新的效果</p>
<figure class="highlight c#"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">for</span>(<span class="built_in">int</span> i = <span class="number">1</span>;i < <span class="number">5</span> ;i++)</span><br><span class="line"> <span class="keyword">if</span>( index[i]<span class="number">-1</span> > <span class="number">49</span> )</span><br><span class="line"> comments[i].text = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> comments[i].text = txt[index[i] - <span class="number">1</span>];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="Ⅳ、抓取道指成分股数据"><a href="#Ⅳ、抓取道指成分股数据" class="headerlink" title="Ⅳ、抓取道指成分股数据"></a>Ⅳ、抓取道指成分股数据</h4><h5 id="具体内容-1"><a href="#具体内容-1" class="headerlink" title="具体内容"></a>具体内容</h5><p>在<a href="https://money.cnn.com/data/dow30/">https://money.cnn.com/data/dow30/</a> 上抓取道指成分股数据并解析其中30家公司的代码、名称和最近一次成交价,将结果放到一个列表中输出。</p>
<h5 id="实验步骤-1"><a href="#实验步骤-1" class="headerlink" title="实验步骤"></a>实验步骤</h5><p>采用 request 和 re 两个包来解决</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> re</span><br></pre></td></tr></table></figure>
<p>确定查询网址路径</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">url = <span class="string">'https://money.cnn.com/data/dow30/'</span></span><br></pre></td></tr></table></figure>
<p>由于网址发生变化,所以实际进入的网址为</p>
<p><img src="https://s2.loli.net/2022/07/26/mH5pUMjov8Kdg9b.png" style="zoom:67%;"></p>
<p>通过r=request.get(url)构造一个向服务器请求资源的url对象。</p>
<p>这个对象是Request库内部生成的。</p>
<p>这时候的r返回的是一个包含服务器资源的Response对象。包含从服务器返回的所有的相关资源。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">r = requests.get(url)</span><br></pre></td></tr></table></figure>
<p>根据网页源代码,构建查匹配对应数据所需的正则表达式</p>
<p><img src="https://s2.loli.net/2022/07/26/D2TMdGf8sIoKmqO.png" alt="image-20220428173724771"></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">pattern = re.<span class="built_in">compile</span>(</span><br><span class="line"> <span class="string">'wsod_symbol">(.*?)</a> <span title="(.*?)">(.*?)</span></td><td class="wsod_aRight"><span stream="last_(.*?)<'</span>)</span><br></pre></td></tr></table></figure>
<p>进行匹配查询</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># data 为一个含有4个元素的元组,包含所需数据</span></span><br><span class="line">data = re.findall(pattern, r.text)</span><br></pre></td></tr></table></figure>
<p> 数据处理:对数据进行筛选比对,选取所需要的 【公司的代码】、【名称】、【最近一次成交价】</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> data:</span><br><span class="line"> <span class="comment"># print("i:",i)</span></span><br><span class="line"> m = (i[<span class="number">0</span>], i[<span class="number">1</span>], i[<span class="number">3</span>][-<span class="number">6</span>:])</span><br><span class="line"> <span class="comment"># 将其添加到列表中</span></span><br><span class="line"> dowdata.append(m)</span><br></pre></td></tr></table></figure>
<p>输出示例:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> dowdata:</span><br><span class="line"> <span class="built_in">print</span>(i)</span><br></pre></td></tr></table></figure>
<p><img src="https://s2.loli.net/2022/07/26/fi17MO3lLc2pzSd.png" alt="image-20220428174108983"></p>
<p>该结果对应时间:2022.4.28 -17:41</p>
]]></content>
<categories>
<category>Unity</category>
</categories>
<tags>
<tag>Unity</tag>
<tag>网络爬虫</tag>
</tags>
</entry>
<entry>
<title>基于Unity和MySQL的数据库课设</title>
<url>/2022/07/27/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AF%BE%E7%A8%8B%E5%AE%9E%E9%AA%8C/</url>
<content><![CDATA[<p>(前排提示:为了凑字数导致内容较水)</p>
<h4 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h4><p>本实验采用的数据库为MySQL数据库,其具有体积小、速度快等优点。本实验的GUI部分采用Unity3D引擎制作,通过编写C#脚本挂载到Unity引擎中,实现了Unity与MySQL之间的连接。在完成基础实验一至五的前提上,依照要求实现了一个小型书籍信息管理系统 ,具有增删改查等基本功能,同时还可以根据一定要求进行排序,并且创建了两个图表对数据信息进行汇总,便于后续分析。</p>
<h4 id="关键词"><a href="#关键词" class="headerlink" title="关键词"></a>关键词</h4><p>MySQL、Unity、、C#、书籍信息管理</p>
<span id="more"></span>
<h2 id="一、实验要求"><a href="#一、实验要求" class="headerlink" title="一、实验要求"></a><strong>一、实验要求</strong></h2><ul>
<li>理解 SQL 定义功能</li>
<li>熟练掌握 SQL 操纵功能</li>
<li>了解 SQL 数据控制功能。</li>
<li>熟练掌握 Oracle、SQL Server、MySQL、DB2、Sybase 或 PostgreSQL 等对数据库的管理和操作,可以采用华为 OpenGauss 数据库。熟练掌握 Visual C++(MFC)、C#、 Qt、Java、PHP 或 Python 等访问数据库的方法,编写学生通讯录或学生选课(不建议)或其他类似的一个小型管理信息系统。</li>