-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1618 lines (1612 loc) · 391 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>提审Google Play遇到的三大问题</title>
<url>/2018/08/31/Cocos2d-X/20180831_001/</url>
<content><![CDATA[<p>    最近在搞Google Play相关的工作,中间遇到很多问题,也学习到了很多。总结下来,发布一个Google系列。首先先说一说首次提审的时候,遇到的三大问题吧。<br><br>    在后台提交之后,没多久我收到了一封来自Google团队的邮件。具体提出了三个问题。</p>
<ul>
<li>Libpng library</li>
<li>OpenSSL</li>
<li>SSL Error Handler</li>
</ul>
<p><img src="20180831_001/20180831223855.png" alt="images"></p>
<p>    前两个问题都是因为我们游戏使用的引擎版本太旧导致的,毕竟是Cocos2d2.2.6的版本,所以在网上搜了很多的so,都是有问题的。这里贴一个地址,可以前往<a href="https://github.com/cocos2d/cocos2d-x-3rd-party-libs-bin/tree/v2">下载</a>。使用<b>cocos2d-x-3rd-party-libs-bin-2\cocos2dx\platform\third_party\android\prebuilt</b>下的libcurl、zlib和libpng替换引擎<b>cocos2dx\platform\third_party\android\prebuilt</b>下的文件夹,Android.mk文件自行检查修改。<br><br>    替换之后可以使用命令行查询OpenSSL,查看输出内容中的版本是否符合Google要求。(我这里是1.0.2g,所以符合要求)<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">$ unzip -p YourApp.apk | strings | grep "OpenSSL"</span><br></pre></td></tr></table></figure></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">IDEA part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">CAMELLIA part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">EDSA part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">ECDSA part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">ECDH part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">RAND part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">CONF part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">CONF_def part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">TXT_DB part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">+SHA part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">RIPE-MD160 part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">3RC4 part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">:Blowfish part of OpenSSL 1.0.2g 1 Mar 2016</span><br><span class="line">\CAST part of OpenSSL 1.0.2g 1 Mar 2016</span><br></pre></td></tr></table></figure>
<p>    浪费我时间最久的就是SSL Error Handler问题了。点开邮件中的连接,看到Google给出的解释是,我的app中使用的 WebViewClient.onReceivedSslError方法有安全隐患。在app中使用过该方法的小伙伴可以尝试这样修改一下。<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceivedSslError</span><span class="params">(WebView view, <span class="keyword">final</span> SslErrorHandler handler, SslError error)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> AlertDialog.Builder builder = <span class="keyword">new</span> AlertDialog.Builder(<span class="keyword">this</span>);</span><br><span class="line"> builder.setMessage(R.string.notification_error_ssl_cert_invalid);</span><br><span class="line"> builder.setPositiveButton(<span class="string">"continue"</span>, <span class="keyword">new</span> DialogInterface.OnClickListener() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(DialogInterface dialog, <span class="keyword">int</span> which)</span> </span>{</span><br><span class="line"> handler.proceed();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> builder.setNegativeButton(<span class="string">"cancel"</span>, <span class="keyword">new</span> DialogInterface.OnClickListener() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(DialogInterface dialog, <span class="keyword">int</span> which)</span> </span>{</span><br><span class="line"> handler.cancel();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">final</span> AlertDialog dialog = builder.create();</span><br><span class="line"> dialog.show();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    本以为找到改一下就可以结束了。但是…我查了一下代码,我并没有使用过这个方法!!!我询问了自家SDK的开发工程师,得知SDK中确实使用过此方法,改正之后又发给我了一个新的jar。替换之后,再次打包提审,再次被拒。<br><br>    懵了~后来在网上看到有人说umeng分享SDK中的jar有问题,使用如下命令:<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">$ find . -name '*.jar' -exec zipgrep -i onreceivedsslerror {} \;</span><br></pre></td></tr></table></figure><br>    得出如下输出:<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">com/umeng/socialize/view/q.class:Binary file (standard input) matches</span><br><span class="line">com/umeng/socialize/view/j.class:Binary file (standard input) matches</span><br></pre></td></tr></table></figure><br>    感人~反正是上Google,也用不到umeng分享了。就直接把分享相关的模块干掉了。以防万一,我把所有用到的jar都查询了一遍,确认无误后。再次提审,通过。美滋滋~</p>
<p>参考文献:<br><br><a href="https://blog.csdn.net/amazing_alex/article/details/71410670">https://blog.csdn.net/amazing_alex/article/details/71410670</a></p>
<p><a href="https://blog.csdn.net/kingoneyun/article/details/54581764?locationNum=6&fps=1">https://blog.csdn.net/kingoneyun/article/details/54581764?locationNum=6&fps=1</a></p>
]]></content>
<tags>
<tag>Cocos2d</tag>
</tags>
</entry>
<entry>
<title>Cocos2d-x2.2.6版本安卓工程移植AS</title>
<url>/2018/08/31/Cocos2d-X/20180831_002/</url>
<content><![CDATA[<p>    首先,引擎这么老的版本,为什么要搞Android Studio移植呢?呵~还不是因为要发Google Play惹的祸<br><br>    本来天真的以为还像往常一样,创建一个插件工程,抽离主工程,就可以完事大吉了。后来发现自家SDK的res文件夹下的xml文件无法解析,仔细问了一下SDK开发工程师,他居然告诉我,因为集成了Google服务相关内容,所以必须用Android Studio做开发。这是什么屁规定,不过仔细一想,毕竟都是人家Google自家的东西,就是喂给你个屁,你也要吃下去,并且大喊一声,真香。<br><br><img src="20180831_002/20180831231020.gif" alt="images"></p>
<p>    言归正传,先用eclipse导出gradle,这上网百度就能找得到。</p>
<ol>
<li>根目录下的<b>local.properties</b>文件中,修改好ndk.dir和sdk.dir<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">## This file must *NOT* be checked into Version Control Systems,</span><br><span class="line"># as it contains information specific to your local configuration.</span><br><span class="line">#</span><br><span class="line"># Location of the SDK. This is only used by Gradle.</span><br><span class="line"># For customization when using a Version Control System, please read the</span><br><span class="line"># header note.</span><br><span class="line">#Thu Aug 16 18:48:03 CST 2018</span><br><span class="line">ndk.dir=H\:\\tools\\android-ndk-r9d</span><br><span class="line">sdk.dir=C\:\\Users\\2069\\AppData\\Local\\Android\\Sdk</span><br><span class="line"></span><br></pre></td></tr></table></figure></li>
<li>根目录下的<b>gradle.properties</b>(没有的话自行创建)文件中添加如下代码<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="meta">org.gradle.daemon</span>=<span class="string">true</span></span><br><span class="line"><span class="meta">org.gradle.parallel</span>=<span class="string">true</span></span><br><span class="line"><span class="comment">#org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8</span></span><br><span class="line"><span class="comment">#这里设置java虚拟机内存</span></span><br><span class="line"><span class="meta">org.gradle.jvmargs</span>=<span class="string">-Xms8192m -Xmx8192m</span></span><br><span class="line"><span class="comment">#这里打开NDK编译</span></span><br><span class="line"><span class="meta">android.useDeprecatedNDK</span> = <span class="string">true</span></span><br></pre></td></tr></table></figure></li>
<li>根目录下的<b>build.gradle</b>文件中,做如下修改<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Top-level build file where you can add configuration options common to all sub-projects/modules.</span></span><br><span class="line"><span class="keyword">buildscript</span> {</span><br><span class="line"> <span class="keyword">repositories</span> {</span><br><span class="line"> <span class="comment">//这里使用的是国内阿里云提供的库,不会使用科学上网的玩家可以选择使用这个地址来下载,不过可能更新比Google有些延迟,而且有些库可能会找不到。</span></span><br><span class="line"> <span class="comment">//maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/' }</span></span><br><span class="line"> <span class="comment">//maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//那么这里使用的就是Google提供的库,需要科学上网才可以访问下载得到。</span></span><br><span class="line"> maven{ url <span class="string">'https://dl.google.com/dl/android/maven2/'</span>}</span><br><span class="line"> jcenter()</span><br><span class="line"> google()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">dependencies</span> {</span><br><span class="line"> <span class="comment">//这里我选用和SDK相同的gradle的Android插件版本,每个插件版本都对应着必需的Gradle版本。这里附上官网传送门:https://developer.android.google.cn/studio/releases/gradle-plugin#updating-plugin</span></span><br><span class="line"> <span class="keyword">classpath</span> <span class="string">'com.android.tools.build:gradle:3.1.4'</span></span><br><span class="line"> <span class="comment">//这里可能是因为使用的Google的统计服务,所以应SDK文档的要求,添加了此配置。</span></span><br><span class="line"> <span class="keyword">classpath</span> <span class="string">'com.google.gms:google-services:3.1.0'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">allprojects</span> {</span><br><span class="line"> <span class="keyword">repositories</span> {</span><br><span class="line"> <span class="comment">//这里同上</span></span><br><span class="line"> <span class="comment">//maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }</span></span><br><span class="line"> <span class="comment">//maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//这里同上</span></span><br><span class="line"> maven{ url <span class="string">'https://dl.google.com/dl/android/maven2/'</span>}</span><br><span class="line"> jcenter()</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//这里根据SDK Demo粘贴</span></span><br><span class="line"><span class="keyword">task</span> clean(type: <span class="keyword">Delete</span>) {</span><br><span class="line"> <span class="keyword">delete</span> rootProject.buildDir</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
<li><p>根目录下的<b>gradle\wrapper\gradle-wrapper.properties</b>修改如下</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#Tue Aug 14 21:16:23 CST 2018</span><br><span class="line">distributionBase=GRADLE_USER_HOME</span><br><span class="line">distributionPath=wrapper/dists</span><br><span class="line">zipStoreBase=GRADLE_USER_HOME</span><br><span class="line">zipStorePath=wrapper/dists</span><br><span class="line">#这里同样是根据SDK的配置来修改的,具体使用哪个版本,需要根据前边提到的gradle的Android插件版本以及官网地址查询具体应使用的版本。</span><br><span class="line">distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip</span><br></pre></td></tr></table></figure>
<p>Plugin version | Required Gradle version<br>—-|—-<br>1.0.0 - 1.1.3 | 2.2.1 - 2.3<br>1.2.0 - 1.3.1 | 2.2.1 - 2.9<br>1.5.0 | 2.2.1 - 2.13<br>2.0.0 - 2.1.2 | 2.10 - 2.13<br>2.1.3 - 2.2.3 | 2.14.1+<br>2.3.0+ | 3.3+<br>3.0.0+ | 4.1+<br>3.1.0+ | 4.4+<br>摘自官网(侵删):<a href="https://developer.android.google.cn/studio/releases/gradle-plugin#updating-plugin">https://developer.android.google.cn/studio/releases/gradle-plugin#updating-plugin</a></p>
</li>
<li><p>主工程目录下的<b>local.properties</b>、<b>gradle.properties</b>(没有的话自行创建)文件我也做了相关配置,和根目录下相同。这里说一下什么是主工程目录,什么是跟目录,我的工程是这样的,再接入SDK之前,我会把SDK部分单独抽出来,作为一个插件工程,然后游戏部分的相关内容是一个主工程,在eclipse中主工程依赖这个插件工程就OK了。那跟目录顾名思义就是包含Cocos2d相关、SDK相关和游戏资源等的最外层目录,当你使用eclipse导出gradle的时候,你的根目录下会产生一个gradle文件夹。</p>
</li>
<li>主工程目录下的<b>build.gradle</b>导入需要引用的相关jar,<b>compile</b>关键字好像过时了,所以换用了<b>implementation</b>。具体修改及注释如下<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line">apply plugin: <span class="string">'android'</span></span><br><span class="line">apply plugin: <span class="string">'com.google.gms.google-services'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">dependencies</span> {</span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">fileTree</span>(<span class="keyword">include</span>: <span class="string">'*.jar'</span>, dir: <span class="string">'libs'</span>)</span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">project</span>(<span class="string">':plugin:protocols:proj.android'</span>)</span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">project</span>(<span class="string">':plugin:plugins:umeng:proj.android'</span>)</span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">project</span>(<span class="string">':plugin:plugins:ZQGameSEA:proj.android'</span>)</span><br><span class="line"> <span class="comment">//compile project(':plugin:plugins:umeng:proj.android')</span></span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/UMSocial/proj.android/sdk/umeng_social_sdk.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/UMSocial/proj.android/sdk/httpmime-4.1.3.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/UMSocial/proj.android/sdk/SocialSDK_QQZone_1.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/UMSocial/proj.android/sdk/SocialSDK_QQZone_2.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/UMSocial/proj.android/sdk/SocialSDK_QQZone_3.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/UMSocial/proj.android/sdk/SocialSDK_Sina.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../plugin/plugins/umeng/proj.android/libs/umeng_sdk.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/xg/proj.android/jg_filter_sdk_1.1.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/xg/proj.android/wup-1.0.0.E-SNAPSHOT.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/xg/proj.android/Xg_sdk_v2.41_20150915_1121.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/bugly/proj.android/bugly_1.2.9_release.jar'</span>)</span><br><span class="line"> implementation files(<span class="string">'../../../external/sdk/bugly/proj.android/bugly_cocos2dx_1.2.9_release.jar'</span>)</span><br><span class="line"> <span class="comment">// implementation files('../../../external/sdk/baiduLocation/proj.android/BaiduLBS_Android.jar')</span></span><br><span class="line"> <span class="comment">// implementation files('libs/android-support-v4.jar')</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">android {</span><br><span class="line"> <span class="comment">//编译工具版本号不能大于下边的构建版本号</span></span><br><span class="line"> compileSdkVersion <span class="number">21</span></span><br><span class="line"> buildToolsVersion <span class="string">'28.0.2'</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//这里给Java堆分配最大内存8g</span></span><br><span class="line"> dexOptions {</span><br><span class="line"> incremental <span class="keyword">true</span></span><br><span class="line"> javaMaxHeapSize <span class="string">"8g"</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> defaultConfig {</span><br><span class="line"> <span class="comment">//包名</span></span><br><span class="line"> applicationId <span class="string">"com.game.ultramansol_sea"</span></span><br><span class="line"> <span class="comment">//支持最小的Android SDK API版本</span></span><br><span class="line"> minSdkVersion <span class="number">17</span></span><br><span class="line"> <span class="comment">//目标版本</span></span><br><span class="line"> targetSdkVersion <span class="number">21</span></span><br><span class="line"> <span class="comment">//这里和版本号相关的内容同以前在eclipse中的清单文件相同</span></span><br><span class="line"> versionCode <span class="number">1</span></span><br><span class="line"> versionName <span class="string">"1.2.22"</span></span><br><span class="line"> <span class="comment">//添加NDK相关配置,moduleName为jni文件夹下的Android.mk的LOCAL_MODULE_FILENAME参数,abiFilters支持的架构。</span></span><br><span class="line"> ndk {</span><br><span class="line"> moduleName <span class="string">"libultraman"</span></span><br><span class="line"> ldLibs <span class="string">"log"</span>, <span class="string">"z"</span>, <span class="string">"m"</span></span><br><span class="line"> abiFilters <span class="string">"armeabi-v7a"</span>, <span class="string">"armeabi"</span></span><br><span class="line"> }</span><br><span class="line"> multiDexEnabled <span class="keyword">true</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// externalNativeBuild {</span></span><br><span class="line"><span class="comment">// ndkBuild {</span></span><br><span class="line"><span class="comment">// path file("jni/Android.mk")</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><br><span class="line"> <span class="keyword">sourceSets</span> {</span><br><span class="line"> main {</span><br><span class="line"> manifest.srcFile <span class="string">'AndroidManifest.xml'</span></span><br><span class="line"> java.srcDirs = [<span class="string">'src'</span>]</span><br><span class="line"> resources.srcDirs = [<span class="string">'src'</span>]</span><br><span class="line"> aidl.srcDirs = [<span class="string">'src'</span>]</span><br><span class="line"> renderscript.srcDirs = [<span class="string">'src'</span>]</span><br><span class="line"> res.srcDirs = [<span class="string">'res'</span>]</span><br><span class="line"> assets.srcDirs = [<span class="string">'assets'</span>]</span><br><span class="line"> jni.srcDirs = []<span class="comment">//设置禁止gradle生成Android.mk</span></span><br><span class="line"> jniLibs.srcDirs = [<span class="string">'libs'</span>]<span class="comment">//设置目标的so存放,也就是组装到apk中的so路径</span></span><br><span class="line"> jniLibs.srcDir <span class="string">'../../../;../../../cocos2dx/platform/third_party/android/prebuilt;../../../projects/ultraman/Classes'</span></span><br><span class="line"><span class="comment">// jni.srcDirs 'jni/hellolua'</span></span><br><span class="line"> <span class="comment">//NDK_PROJECT_PATH =</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Move the tests to tests/java, tests/res, etc...</span></span><br><span class="line"> androidTest.setRoot(<span class="string">'tests'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Move the build types to build-types/<type></span></span><br><span class="line"> <span class="comment">// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...</span></span><br><span class="line"> <span class="comment">// This moves them out of them default location under src/<type>/... which would</span></span><br><span class="line"> <span class="comment">// conflict with src/ being used by the main source set.</span></span><br><span class="line"> <span class="comment">// Adding new build types or product flavors should be accompanied</span></span><br><span class="line"> <span class="comment">// by a similar customization.</span></span><br><span class="line"> debug.setRoot(<span class="string">'build-types/debug'</span>)</span><br><span class="line"> release.setRoot(<span class="string">'build-types/release'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// tasks.withType(JavaCompile) {</span></span><br><span class="line"><span class="comment">// compileTask -> compileTask.dependsOn 'ndkBuild','copyThirdso', 'copyJniLibs'</span></span><br><span class="line"><span class="comment">// }</span></span><br><span class="line"><span class="comment">// task ndkBuild(type: Exec) {</span></span><br><span class="line"><span class="comment">// def ndkBuildingDir = project.android.ndkDirectory//project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder().absolutePath</span></span><br><span class="line"><span class="comment">// println ndkBuildingDir</span></span><br><span class="line"><span class="comment">// println 111</span></span><br><span class="line"><span class="comment">// commandLine "$ndkBuildingDir/ndk-build.cmd", '-C', file('jni').absolutePath,</span></span><br><span class="line"><span class="comment">// '-j', Runtime.runtime.availableProcessors(),</span></span><br><span class="line"><span class="comment">// "NDK_OUT=$buildDir/intermediates/ndk/obj",</span></span><br><span class="line"><span class="comment">// "NDK_APP_DST_DIR=$buildDir/intermediates/ndk/libs/\$(TARGET_ARCH_ABI)"</span></span><br><span class="line"><span class="comment">// } //设置新的so的生成目录</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// task copyJniLibs(type: Copy) {</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// from fileTree(dir: file(buildDir.absolutePath + '/intermediates/ndk/libs'), include: '**/*.so')</span></span><br><span class="line"><span class="comment">// into file('libs')</span></span><br><span class="line"><span class="comment">// } //将新生成的so拷贝到jniLibs目录</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// task copyThirdso(type: Copy) {</span></span><br><span class="line"><span class="comment">// from file('jni/prebuilt')</span></span><br><span class="line"><span class="comment">// into file('libs')</span></span><br><span class="line"><span class="comment">// } //将第三方的so拷贝到jniLibs目录</span></span><br><span class="line"></span><br><span class="line"> buildTypes {</span><br><span class="line"> release {</span><br><span class="line"> minifyEnabled <span class="keyword">false</span></span><br><span class="line"> proguardFiles getDefaultProguardFile(<span class="string">'proguard-android.txt'</span>), <span class="string">'proguard-rules.txt'</span></span><br><span class="line"> }</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></li>
<li>设置AS的内存大小,点击<b>Help -> Edit Custom VM Options</b>,弹出studio64.exe.vmoptions文件,在其中插入如下代码,AS右下角的allocated heap size会变成8G。如果没有请尝试重启AS。<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># custom Android Studio VM options, see https://developer.android.com/studio/intro/studio-config.html</span><br><span class="line">-Xms8192m</span><br><span class="line">-Xmx8192m</span><br><span class="line">-XX:MaxPermSize=8192m</span><br><span class="line">-XX:ReservedCodeCacheSize=200m</span><br><span class="line">-XX:+UseCompressedOops</span><br><span class="line">-XX:ReservedCodeCacheSize=240m</span><br><span class="line">-XX:+UseConcMarkSweepGC</span><br><span class="line">-XX:SoftRefLRUPolicyMSPerMB=50</span><br><span class="line">-Dsun.io.useCanonCaches=false</span><br><span class="line">-Djava.net.preferIPv4Stack=true</span><br><span class="line">-Djna.nosys=true</span><br><span class="line">-Djna.boot.library.path=</span><br><span class="line"></span><br><span class="line">-da</span><br><span class="line"></span><br></pre></td></tr></table></figure></li>
<li>在目录列表选择<b>Project</b>视图,找到主工程,右键jni文件夹(可能别的文件夹也行),选择<b>Link C++ Project With Gradle</b>,<b>Build System</b>选择<b>ndk-build</b>选项,在<b>Project Path</b>中设置“主工程/jni/Android.mk”路径,选择<b>OK</b>。<br><br><img src="20180831_002/20180831231729.png" alt="images"></li>
<li>设置NDK编译工具,<b>File -> Settings -> Tools -> External Tools</b>,点击“+”。<b>Name</b>自己取名,<b>Program</b>为ndk-build.cmd的路径,<b>Arguments</b>为编译时需要的参数“NDK_MODULE_PATH=(自己的NDK_MODULE_PATH)”,<b>Working directory</b>为主工程路径,点击<b>OK</b>。<br><br><img src="20180831_002/20180831232144.png" alt="images"></li>
<li><b>Run/Debug Configurations</b>,在下边点击“<b>+</b>”,添加<b>Run External tool</b>,选择刚刚添加的“ndk”工具。如果不打算烧在真机上,想直接打包的话。在打包之前,请记得<b>右键->External Tools->ndk</b>,进行NDK编译。</li>
</ol>
<p>    到此为止,基本结束了配置,可能还会有些小问题,自行百度或者Google应该就可以解决了。还有就是插件工程中以前有引用jar的,别忘记在对应工程下的<b>build.gradle</b>文件中引用。还有就是清单文件的坑,AS中检查可能比较严格,所有插件工程的<b>minSdkVersion</b>和<b>targetSdkVersion</b>必须一致,不然会报错。还有,如果出现多个分包APK并且找不到编译生成的so错误时,可以尝试把<b>Instant Run</b>关掉。</p>
<p>NDK编译相关的官网地址:<a href="https://developer.android.google.cn/studio/projects/add-native-code#ndkCompile">https://developer.android.google.cn/studio/projects/add-native-code#ndkCompile</a></p>
]]></content>
<tags>
<tag>Cocos2d</tag>
</tags>
</entry>
<entry>
<title>关于Google Play商店中obb文件的加载</title>
<url>/2018/08/31/Cocos2d-X/20180831_003/</url>
<content><![CDATA[<p>    最近搞Google Play的这件事情,真的是焦头烂额。项目太老了。。。根本不支持AS工程,也就别想支持obb文件解析了。<br><br>    实质上obb文件就是一个ZIP文件。有两种方式可以生成:<br><br>    一种是Google官方提供的Jobb工具来生成obb文件。工具可以在 Android\sdk\tools\bin文件夹下找到。命令行用法和参数如下:<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">jobb -d [所有资源的路径] -o [生成的obb名称(请遵循上述命名规则)] -k [打包密码] -pn [包名] -pv [versionCode(跟obb名称的versionCode一致)]</span><br></pre></td></tr></table></figure><br>    也可以使用该工具对obb文件进行解压:<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">jobb -d [输出路径] -o [obb文件名] -k [打包所用的密码]</span><br></pre></td></tr></table></figure><br>    另一种是使用压缩工具,将资源从assets路径下开始压缩,命名方式为<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">main.[versionCode].[packageName].obb</span><br></pre></td></tr></table></figure><br>    还有一种是使用build.gradle中添加脚本,有兴趣使用此方法的可以自行<a href="www.baidu.com">百度</a>或<a href="www.google.com">Google</a>。因为我使用的是第二种方法。<br><br>    在使用之前,需要下载一个Google官方提供的库,可以打开Android Studio(<b>File</b> -> <b>Settings</b> -> <b>Appearance & Behavior</b> -> <b>System Settings</b> -> <b>Android SDK</b> -> <b>SDK Tools</b>),选择<b>Google Play APK Expansion library</b>和<b>Google Play Licensing Library</b>,<b>Apply</b>下载。但这里不建议勾选<b>Google Play APK Expansion library</b>,原因后边详述。(可以在这里<a href="https://github.com/google/play-apk-expansion">下载</a>)<br><br><img src="20180831_003/20180831234902.png" alt="images"><br><br>    下载完成之后,在<b>C:\Users\xxx\AppData\Local\Android\Sdk\extras\google\market_apk_expansion</b>或者apkx_library中找到zip_file,作为module引用到工程。(apkx_library在使用上面连接下载的目录中)<br><br>    我们先修改obb文件读取的逻辑。首先修改主activity类,也就是Cocos2dxActivity.java的派生类。<br><!--因为Google官方似乎并没有做维护更新,已经年久失修了--></p>
<hr>
<h4 id="MainActivity-java"><a href="#MainActivity-java" class="headerlink" title="MainActivity.java"></a>MainActivity.java</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String FATE_OBB_PATH = <span class="string">""</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getObbFileName</span><span class="params">()</span> </span>{</span><br><span class="line"> PackageInfo info = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> info = <span class="keyword">super</span>.getPackageManager().getPackageInfo(<span class="keyword">super</span>.getPackageName(), <span class="number">0</span>);</span><br><span class="line"> String fileName = <span class="string">"main."</span> + info.versionCode + <span class="string">"."</span> + getPackageName() + <span class="string">".obb"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> fileName;</span><br><span class="line"> } <span class="keyword">catch</span> (PackageManager.NameNotFoundException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="string">""</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getVirtualObbFileFullpath</span><span class="params">()</span></span>{</span><br><span class="line"> File sdcardDir = Environment.getExternalStorageDirectory();</span><br><span class="line"> String _path = getObbDir().getPath() + <span class="string">"/"</span> + getObbFileName();</span><br><span class="line"> Log.e(<span class="string">"===_path==="</span>, _path);</span><br><span class="line"> <span class="keyword">return</span> _path;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span></span>{</span><br><span class="line"> Log.w(<span class="string">"uuuuuuuuuu"</span>,<span class="string">"onCreate"</span>);</span><br><span class="line"></span><br><span class="line"> FATE_OBB_PATH = getVirtualObbFileFullpath();<span class="comment">//这句需要放在super.onCreate上面</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<hr>
<h4 id="Cocos2dxHelper-java"><a href="#Cocos2dxHelper-java" class="headerlink" title="Cocos2dxHelper.java"></a>Cocos2dxHelper.java</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ZipResourceFile obbzip = <span class="keyword">null</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="keyword">void</span> <span class="title">init</span><span class="params">(<span class="keyword">final</span> Context pContext, <span class="keyword">final</span> Cocos2dxHelperListener pCocos2dxHelperListener)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// begin--------------------添加代码----------------------------</span></span><br><span class="line"> <span class="comment">//检查obb文件是否存在</span></span><br><span class="line"> <span class="keyword">if</span>(fileIsExists(ultraman.FATE_OBB_PATH)){</span><br><span class="line"> <span class="comment">//存在添加obb路径到cocos中 注意 nativeSetObbPath 方法是需要新添加的 下方会介绍</span></span><br><span class="line"> Cocos2dxHelper.nativeSetObbPath(ultraman.FATE_OBB_PATH);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// end--------------------添加代码----------------------------</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">final</span> ApplicationInfo applicationInfo = pContext.getApplicationInfo();</span><br><span class="line"> </span><br><span class="line"> Cocos2dxHelper.sContext = pContext;</span><br><span class="line"> Cocos2dxHelper.sCocos2dxHelperListener = pCocos2dxHelperListener;</span><br><span class="line"></span><br><span class="line"> Cocos2dxHelper.sPackageName = applicationInfo.packageName;</span><br><span class="line"> Cocos2dxHelper.sFileDirectory = pContext.getFilesDir().getAbsolutePath();</span><br><span class="line"> Cocos2dxHelper.nativeSetApkPath(applicationInfo.sourceDir);</span><br><span class="line"></span><br><span class="line"> Cocos2dxHelper.sCocos2dxAccelerometer = <span class="keyword">new</span> Cocos2dxAccelerometer(pContext);</span><br><span class="line"> Cocos2dxHelper.sCocos2dMusic = <span class="keyword">new</span> Cocos2dxMusic(pContext);</span><br><span class="line"> <span class="keyword">int</span> simultaneousStreams = Cocos2dxSound.MAX_SIMULTANEOUS_STREAMS_DEFAULT;</span><br><span class="line"> <span class="keyword">if</span> (Cocos2dxHelper.getDeviceModel().indexOf(<span class="string">"GT-I9100"</span>) != -<span class="number">1</span>) {</span><br><span class="line"> simultaneousStreams = Cocos2dxSound.MAX_SIMULTANEOUS_STREAMS_I9100;</span><br><span class="line"> }</span><br><span class="line"> Cocos2dxHelper.sCocos2dSound = <span class="keyword">new</span> Cocos2dxSound(pContext, simultaneousStreams);</span><br><span class="line"> Cocos2dxHelper.sAssetManager = pContext.getAssets();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//设置压缩包</span></span><br><span class="line"> PackageInfo info = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> info = pContext.getPackageManager().getPackageInfo(pContext.getPackageName(), <span class="number">0</span>);</span><br><span class="line"> Cocos2dxHelper.obbzip = APKExpansionSupport.getAPKExpansionZipFile(pContext,info.versionCode,<span class="number">0</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (PackageManager.NameNotFoundException e1) {</span><br><span class="line"> e1.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e1) {</span><br><span class="line"> e1.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// end--------------------添加代码----------------------------</span></span><br><span class="line"></span><br><span class="line"> Cocos2dxBitmap.setContext(pContext);</span><br><span class="line"> Cocos2dxETCLoader.setContext(pContext);</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//检查obb文件是否存在</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">fileIsExists</span><span class="params">(String strFile)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">try</span></span><br><span class="line"> {</span><br><span class="line"> File f=<span class="keyword">new</span> File(strFile);</span><br><span class="line"> <span class="keyword">if</span>(!f.exists())</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">catch</span> (Exception e)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//nativeSetObbPath 设置obb路径方法</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">nativeSetObbPath</span><span class="params">(<span class="keyword">final</span> String pObbPath)</span></span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<hr>
<h4 id="Cocos2dxMusic-java-和-Cocos2dxSound-java"><a href="#Cocos2dxMusic-java-和-Cocos2dxSound-java" class="headerlink" title="Cocos2dxMusic.java 和 Cocos2dxSound.java"></a>Cocos2dxMusic.java 和 Cocos2dxSound.java</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//Cocos2dxMusic.java</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> MediaPlayer <span class="title">createMediaplayer</span><span class="params">(<span class="keyword">final</span> String pPath)</span> </span>{</span><br><span class="line"> MediaPlayer mediaPlayer = <span class="keyword">new</span> MediaPlayer();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (pPath.startsWith(<span class="string">"/"</span>)) {</span><br><span class="line"> <span class="keyword">final</span> FileInputStream fis = <span class="keyword">new</span> FileInputStream(pPath);</span><br><span class="line"> mediaPlayer.setDataSource(fis.getFD());</span><br><span class="line"> fis.close();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//-------------------modify begin-----------------</span></span><br><span class="line"> <span class="keyword">final</span> AssetFileDescriptor assetFileDescritor = Cocos2dxHelper.obbzip.getAssetFileDescriptor(<span class="string">"assets/"</span>+pPath);</span><br><span class="line"> <span class="keyword">if</span>(assetFileDescritor == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">final</span> AssetFileDescriptor assetFileDescritor1 = <span class="keyword">this</span>.mContext.getAssets().openFd(pPath);</span><br><span class="line"> mediaPlayer.setDataSource(assetFileDescritor1.getFileDescriptor(), assetFileDescritor1.getStartOffset(), assetFileDescritor1.getLength());</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(), assetFileDescritor.getStartOffset(), assetFileDescritor.getLength());</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//-------------------modify end-----------------</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> mediaPlayer.prepare();</span><br><span class="line"></span><br><span class="line"> mediaPlayer.setVolume(<span class="keyword">this</span>.mLeftVolume, <span class="keyword">this</span>.mRightVolume);</span><br><span class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Exception e) {</span><br><span class="line"> mediaPlayer = <span class="keyword">null</span>;</span><br><span class="line"> Log.e(Cocos2dxMusic.TAG, <span class="string">"error: "</span> + e.getMessage(), e);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> mediaPlayer;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//Cocos2dxSound.java</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">createSoundIDFromAsset</span><span class="params">(<span class="keyword">final</span> String pPath)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> soundID = Cocos2dxSound.INVALID_SOUND_ID;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (pPath.startsWith(<span class="string">"/"</span>)) {</span><br><span class="line"> soundID = <span class="keyword">this</span>.mSoundPool.load(pPath, <span class="number">0</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//-------------------modify begin-----------------</span></span><br><span class="line"> <span class="keyword">final</span> AssetFileDescriptor assetFileDescritor = Cocos2dxHelper.obbzip.getAssetFileDescriptor(<span class="string">"assets/"</span>+pPath);</span><br><span class="line"> <span class="keyword">if</span>(assetFileDescritor == <span class="keyword">null</span>) {</span><br><span class="line"> soundID = <span class="keyword">this</span>.mSoundPool.load(<span class="keyword">this</span>.mContext.getAssets().openFd(pPath), <span class="number">0</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> soundID = <span class="keyword">this</span>.mSoundPool.load(assetFileDescritor, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//-------------------modify end-----------------</span></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (<span class="keyword">final</span> Exception e) {</span><br><span class="line"> soundID = Cocos2dxSound.INVALID_SOUND_ID;</span><br><span class="line"> Log.e(Cocos2dxSound.TAG, <span class="string">"error: "</span> + e.getMessage(), e);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// mSoundPool.load returns 0 if something goes wrong, for example a file does not exist</span></span><br><span class="line"> <span class="keyword">if</span> (soundID == <span class="number">0</span>) {</span><br><span class="line"> soundID = Cocos2dxSound.INVALID_SOUND_ID;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> soundID;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<hr>
<h4 id="Java-org-cocos2dx-lib-Cocos2dxHelper-cpp"><a href="#Java-org-cocos2dx-lib-Cocos2dxHelper-cpp" class="headerlink" title="Java_org_cocos2dx_lib_Cocos2dxHelper.cpp"></a>Java_org_cocos2dx_lib_Cocos2dxHelper.cpp</h4><hr>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line">string g_apkPath;</span><br><span class="line"><span class="comment">//添加obb path</span></span><br><span class="line">string g_obbPath;</span><br><span class="line"><span class="comment">//添加设置obbpath 方法</span></span><br><span class="line"> <span class="function">JNIEXPORT <span class="keyword">void</span> JNICALL <span class="title">Java_org_cocos2dx_lib_Cocos2dxHelper_nativeSetObbPath</span><span class="params">(JNIEnv* env, jobject thiz, jstring obbPath)</span> </span>{</span><br><span class="line"> g_obbPath = JniHelper::<span class="built_in">jstring2string</span>(obbPath);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//添加获取obbpath 方法</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">char</span> * <span class="title">getObbPath</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> g_obbPath.<span class="built_in">c_str</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<hr>
<h4 id="CCFileUtilsAndroid-cpp"><a href="#CCFileUtilsAndroid-cpp" class="headerlink" title="CCFileUtilsAndroid.cpp"></a>CCFileUtilsAndroid.cpp</h4><hr>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">//CCFileUtilsAndroid.h头文件中需要加入 extern const char * getObbPath();方法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//在 s_pZipFile 下添加一个 obb zip包解析</span></span><br><span class="line"><span class="keyword">static</span> ZipFile *s_pZipFileobb = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="function">CCFileUtils* <span class="title">CCFileUtils::sharedFileUtils</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (s_sharedFileUtils == <span class="literal">NULL</span>)</span><br><span class="line"> {</span><br><span class="line"> s_sharedFileUtils = <span class="keyword">new</span> <span class="built_in">CCFileUtilsAndroid</span>();</span><br><span class="line"> s_sharedFileUtils-><span class="built_in">init</span>();</span><br><span class="line"> std::string resourcePath = <span class="built_in">getApkPath</span>();</span><br><span class="line"> s_pZipFile = <span class="keyword">new</span> <span class="built_in">ZipFile</span>(resourcePath, <span class="string">"assets/"</span>);</span><br><span class="line"> <span class="comment">// ---------- modify begin -----------</span></span><br><span class="line"> <span class="comment">//获取obb路径</span></span><br><span class="line"> std::string resourcePath_Obb = <span class="built_in">getObbPath</span>();</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::sharedFileUtils resourcePath_Obb: %s"</span>, resourcePath_Obb.<span class="built_in">c_str</span>());</span><br><span class="line"> <span class="comment">// 创建obbzip</span></span><br><span class="line"> s_pZipFileobb = <span class="keyword">new</span> <span class="built_in">ZipFile</span>(resourcePath_Obb,<span class="string">"assets/"</span>);</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::sharedFileUtils s_pZipFileobb: %p"</span>, s_pZipFileobb);</span><br><span class="line"> <span class="comment">// ---------- modify end -----------</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> s_sharedFileUtils;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">CCFileUtilsAndroid::~<span class="built_in">CCFileUtilsAndroid</span>()</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">CC_SAFE_DELETE</span>(s_pZipFile);</span><br><span class="line"> <span class="comment">//销毁</span></span><br><span class="line"> <span class="built_in">CC_SAFE_DELETE</span>(s_pZipFileobb);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CCFileUtilsAndroid::isFileExist</span><span class="params">(<span class="keyword">const</span> std::string& strFilePath)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="number">0</span> == strFilePath.<span class="built_in">length</span>())</span><br><span class="line"> {</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="keyword">bool</span> bFound = <span class="literal">false</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Check whether file exists in apk.</span></span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::isFileExist strFilePath: %s"</span>, strFilePath.<span class="built_in">c_str</span>());</span><br><span class="line"> <span class="keyword">if</span> (strFilePath[<span class="number">0</span>] != <span class="string">'/'</span>)</span><br><span class="line"> {</span><br><span class="line"> std::string strPath = strFilePath;</span><br><span class="line"> <span class="keyword">if</span> (strPath.<span class="built_in">find</span>(m_strDefaultResRootPath) != <span class="number">0</span>)</span><br><span class="line"> {<span class="comment">// Didn't find "assets/" at the beginning of the path, adding it.</span></span><br><span class="line"> strPath.<span class="built_in">insert</span>(<span class="number">0</span>, m_strDefaultResRootPath);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (s_pZipFile-><span class="built_in">fileExists</span>(strPath))</span><br><span class="line"> {</span><br><span class="line"> bFound = <span class="literal">true</span>;</span><br><span class="line"> } </span><br><span class="line"> <span class="comment">// ---------- modify begin -----------</span></span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::isFileExist bFound: %d"</span>, bFound);</span><br><span class="line"> <span class="keyword">if</span>(!bFound){</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (s_pZipFileobb-><span class="built_in">fileExists</span>(strPath))</span><br><span class="line"> {</span><br><span class="line"> </span><br><span class="line"> bFound = <span class="literal">true</span>;</span><br><span class="line"> } </span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::isFileExist bFound1: %d"</span>, bFound);</span><br><span class="line"> <span class="comment">// ---------- modify end -----------</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"> FILE *fp = <span class="built_in">fopen</span>(strFilePath.<span class="built_in">c_str</span>(), <span class="string">"r"</span>);</span><br><span class="line"> <span class="keyword">if</span>(fp)</span><br><span class="line"> {</span><br><span class="line"> bFound = <span class="literal">true</span>;</span><br><span class="line"> <span class="built_in">fclose</span>(fp);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> bFound;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">char</span>* <span class="title">CCFileUtilsAndroid::doGetFileData</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span>* pszFileName, <span class="keyword">const</span> <span class="keyword">char</span>* pszMode, <span class="keyword">unsigned</span> <span class="keyword">long</span> * pSize, <span class="keyword">bool</span> forAsync)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">char</span> * pData = <span class="number">0</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> ((! pszFileName) || (! pszMode) || <span class="number">0</span> == <span class="built_in">strlen</span>(pszFileName))</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> string fullPath = <span class="built_in">fullPathForFilename</span>(pszFileName);</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"liuwen test CCFileUtilsAndroid::doGetFileData fullPath: %s"</span>, fullPath.<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (fullPath[<span class="number">0</span>] != <span class="string">'/'</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::doGetFileData forAsync: %d"</span>, forAsync);</span><br><span class="line"> <span class="keyword">if</span> (forAsync)</span><br><span class="line"> {</span><br><span class="line"> pData = s_pZipFile-><span class="built_in">getFileData</span>(fullPath.<span class="built_in">c_str</span>(), pSize, s_pZipFile->_dataThread);</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::doGetFileData pData: %p"</span>, pData);</span><br><span class="line"> <span class="comment">// ---------- modify begin -----------</span></span><br><span class="line"> <span class="keyword">if</span> (! pData)</span><br><span class="line"> {</span><br><span class="line"> pData = s_pZipFileobb-><span class="built_in">getFileData</span>(fullPath.<span class="built_in">c_str</span>(), pSize, s_pZipFile->_dataThread);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//---------- modify end -----------</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"> pData = s_pZipFile-><span class="built_in">getFileData</span>(fullPath.<span class="built_in">c_str</span>(), pSize);</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"LJM test CCFileUtilsAndroid::doGetFileData else pData: %p"</span>, pData);</span><br><span class="line"> <span class="comment">// ---------- modify begin -----------</span></span><br><span class="line"> <span class="keyword">if</span> (! pData)</span><br><span class="line"> {</span><br><span class="line"> pData = s_pZipFileobb-><span class="built_in">getFileData</span>(fullPath.<span class="built_in">c_str</span>(), pSize);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//---------- modify end -----------</span></span><br><span class="line"> }</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="keyword">do</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// read rrom other path than user set it</span></span><br><span class="line"> <span class="comment">//CCLOG("liuwen test fopen pszMode: %s", pszMode);</span></span><br><span class="line"> FILE *fp = <span class="built_in">fopen</span>(fullPath.<span class="built_in">c_str</span>(), pszMode);</span><br><span class="line"> <span class="built_in">CC_BREAK_IF</span>(!fp);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> size;</span><br><span class="line"> <span class="built_in">fseek</span>(fp,<span class="number">0</span>,SEEK_END);</span><br><span class="line"> size = <span class="built_in">ftell</span>(fp);</span><br><span class="line"> <span class="built_in">fseek</span>(fp,<span class="number">0</span>,SEEK_SET);</span><br><span class="line"> pData = <span class="keyword">new</span> <span class="keyword">unsigned</span> <span class="keyword">char</span>[size];</span><br><span class="line"> size = <span class="built_in">fread</span>(pData,<span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">unsigned</span> <span class="keyword">char</span>), size,fp);</span><br><span class="line"> <span class="built_in">fclose</span>(fp);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//CCLOG("liuwen test fopen size: %d", size);</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (pSize)</span><br><span class="line"> {</span><br><span class="line"> *pSize = size;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">while</span> (<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (! pData)</span><br><span class="line"> {</span><br><span class="line"> std::string msg = <span class="string">"Get data from file("</span>;</span><br><span class="line"> msg.<span class="built_in">append</span>(pszFileName).<span class="built_in">append</span>(<span class="string">") failed!"</span>);</span><br><span class="line"> <span class="built_in">CCLOG</span>(<span class="string">"%s"</span>, msg.<span class="built_in">c_str</span>());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//CCLOG("liuwen test fopen pData not null ");</span></span><br><span class="line"> <span class="keyword">return</span> pData;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>    至此,读取相关的内容已经修改完毕。下面进行自测:</p>
<ol>
<li>如果手机已经获取root权限,可以下载一个RE文件管理器,把打包好的obb文件放在,<b>/storage/sdcard0/Android/obb/[包名]</b>路径下。</li>
<li>手机没有获取root权限的情况下,可以使用adb命令。adb命令具体怎么配置怎么使用,请<a href="www.baidu.com">这里</a>。<br></li>
</ol>
<p>    具体命令行:<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">$ adb push [obb文件路径] /storage/emulated/0/Android/obb/[包名]</span><br></pre></td></tr></table></figure></p>
<p>参考文献:<br><br><a href="https://blog.csdn.net/androidworkor/article/details/70226726">https://blog.csdn.net/androidworkor/article/details/70226726</a></p>
<p><a href="https://www.jianshu.com/p/de3c53f69925">https://www.jianshu.com/p/de3c53f69925</a></p>
]]></content>
<tags>
<tag>Cocos2d</tag>
</tags>
</entry>
<entry>
<title>关于Google Play商店中obb文件意外情况下无法下载的处理</title>
<url>/2018/09/01/Cocos2d-X/20180901_001/</url>
<content><![CDATA[<p>    上面讲了如何加载obb文件,相信大家应该也大致知道obb是什么了。<br><br>    在Google Play后台上传apk的时候,apk大小有一个限制,不能够超过100M(应用我不太清楚,但最起码游戏是这样的),那么对于游戏来说,这可能不是个好消息,因为游戏随随便便就可以超过100M,那么我们需要把资源抽出来,单独打包成obb文件,在后台上传apk的时候,同时上传obb文件。<br><br>    审核通过之后,用户在Google Play中可以下载到我们的app,商店中显示的实际大小应该是apk+obb的大小,也就是说实际上在帮用户下载apk的同时,Google Play也一起下载了obb。但是Google的官方文档注明,不能保证百分之百成功下载,所以需要开发者在启动app前,判断一下本地是否有相应的obb,如果没有,那么需要重新下载。<br><br>    那么如何在启动时,判断是否需要下载obb呢。首先要提到之前说的<b>Google Play APK Expansion library</b>和<b>Google Play Licensing Library</b>。上篇文章中描述了如何下载这两个库,但是不推荐使用Android SDK Manager下载到的<b>Google Play APK Expansion library</b>库。因为官方应该已经很久没维护过了,所以有些类已经被废弃掉,不适用新版本的Android SDK。(传送门:<a href="https://github.com/google/play-apk-expansion">下载</a>)下载之后,我们需要在工程中引用<b>apkx_library</b>、<b>zip_file</b>和<b>market_licensing/library</b>三个module。<a href="https://jammelee.github.io/2018/03/31/Cocos2d-X/20180831_003/">《关于Google Play商店中obb文件的加载》</a>中已经提到了zip_file,这里不再赘述。<br><br>    <b style="color:red">注意:</b><b>apkx_library</b>依赖<b>market_licensing/library</b>,所以需要在build.gradle中设置依赖。导入module大的时候不要忘记在setting.gradle中include。<br><br>    引用后需要修改一些文件:</p>
<hr>
<h4 id="com-google-android-vending-licensing-LicenseChecker类的checkAccess"><a href="#com-google-android-vending-licensing-LicenseChecker类的checkAccess" class="headerlink" title="com.google.android.vending.licensing.LicenseChecker类的checkAccess()"></a>com.google.android.vending.licensing.LicenseChecker类的checkAccess()</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">checkAccess</span><span class="params">(LicenseCheckerCallback callback)</span> </span>{</span><br><span class="line"> <span class="comment">// If we have a valid recent LICENSED response, we can skip asking</span></span><br><span class="line"> <span class="comment">// Market.</span></span><br><span class="line"> <span class="keyword">if</span> (mPolicy.allowAccess()) {</span><br><span class="line"> Log.i(TAG, <span class="string">"Using cached license response"</span>);</span><br><span class="line"> callback.allow(Policy.LICENSED);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> LicenseValidator validator = <span class="keyword">new</span> LicenseValidator(mPolicy, <span class="keyword">new</span> NullDeviceLimiter(),</span><br><span class="line"> callback, generateNonce(), mPackageName, mVersionCode);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (mService == <span class="keyword">null</span>) {</span><br><span class="line"> Log.i(TAG, <span class="string">"Binding to licensing service."</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//------------------modify begin-----------------------</span></span><br><span class="line"> <span class="comment">//delete</span></span><br><span class="line"> <span class="comment">//这段在代码在Android5.0上会抛出IllegalArgumentException</span></span><br><span class="line"><span class="comment">// boolean bindResult = mContext</span></span><br><span class="line"><span class="comment">// .bindService(</span></span><br><span class="line"><span class="comment">// new Intent(</span></span><br><span class="line"><span class="comment">// new String(</span></span><br><span class="line"><span class="comment">// Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),</span></span><br><span class="line"><span class="comment">// this, // ServiceConnection.</span></span><br><span class="line"><span class="comment">// Context.BIND_AUTO_CREATE);</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//add</span></span><br><span class="line"> Intent serviceIntent = <span class="keyword">new</span> Intent(<span class="keyword">new</span> String(Base64.decode(<span class="string">"Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="</span>)));</span><br><span class="line"> serviceIntent.setPackage(<span class="string">"com.android.vending"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span> bindResult = mContext</span><br><span class="line"> .bindService(</span><br><span class="line"> serviceIntent,</span><br><span class="line"> <span class="keyword">this</span>, <span class="comment">// ServiceConnection.</span></span><br><span class="line"> Context.BIND_AUTO_CREATE);</span><br><span class="line"> <span class="comment">//------------------modify begin-----------------------</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (bindResult) {</span><br><span class="line"> mPendingChecks.offer(validator);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Log.e(TAG, <span class="string">"Could not bind to service."</span>);</span><br><span class="line"> handleServiceConnectionError(validator);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (SecurityException e) {</span><br><span class="line"> callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION);</span><br><span class="line"> } <span class="keyword">catch</span> (Base64DecoderException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> mPendingChecks.offer(validator);</span><br><span class="line"> runChecks();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<hr>
<h4 id="com-google-android-vending-expansion-downloader-DownloaderClientMarshaller-Stub类的connect"><a href="#com-google-android-vending-expansion-downloader-DownloaderClientMarshaller-Stub类的connect" class="headerlink" title="com.google.android.vending.expansion.downloader.DownloaderClientMarshaller.Stub类的connect()"></a>com.google.android.vending.expansion.downloader.DownloaderClientMarshaller.Stub类的connect()</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">connect</span><span class="params">(Context c)</span> </span>{</span><br><span class="line"> mContext = c;</span><br><span class="line"> Intent bindIntent = <span class="keyword">new</span> Intent(c, mDownloaderServiceClass);</span><br><span class="line"> bindIntent.putExtra(PARAM_MESSENGER, mMessenger);</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * modify</span></span><br><span class="line"><span class="comment"> * Context.BIND_DEBUG_UNBIND</span></span><br><span class="line"><span class="comment"> * Context.BIND_AUTO_CREATE</span></span><br><span class="line"><span class="comment"> * 这里会导致绑定的服务在某些情况下无法启动,服务不启动,IDownloaderClient接口的onServiceConnected()方法就不会执行,mRemoteService为null,从而导致NullPointerException。</span></span><br><span class="line"><span class="comment"> * 虽然在使用mRemoteService前增加对其是否为null的判断可以避免crash,但是下载过程仍然无法监控,无法得到下载的结果。需要将这段代码替换成如下代码。也就是将BIND_DEBUG_UNBIND替换成BIND_AUTO_CREATE。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> ( !c.bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE) ) {</span><br><span class="line"> <span class="keyword">if</span> ( Constants.LOGVV ) {</span><br><span class="line"> Log.d(Constants.TAG, <span class="string">"Service Unbound"</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> mBound = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>    修改完成后,如果要自己开发下载界面的话,按照apkx_library文件夹中的AndroidManifest.xml添加即可。本文这里使用的是apkx_sample文件夹中提供的demo,AndroidManifest.xml文件也按照demo添加即可。<br><br>    把src下的java文件和res拷贝到主工程中(如有冲突,自行合并),在主工程的build.gradle中引用这几个support库。<br><figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">dependencies</span> {</span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">fileTree</span>(<span class="keyword">include</span>: <span class="string">'*.jar'</span>, dir: <span class="string">'libs'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">project</span>(<span class="string">':plugin:plugins:GooglePlayObbService:downloader_obb'</span>)</span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">project</span>(<span class="string">':plugin:plugins:GooglePlayObbService:market_licensing'</span>)</span><br><span class="line"> <span class="keyword">compile</span> <span class="keyword">project</span>(<span class="string">':plugin:plugins:GooglePlayObbService:zip_file'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment">//-------------add-------------</span></span><br><span class="line"> <span class="keyword">compile</span> <span class="string">'com.android.support:appcompat-v7:26.0.0'</span></span><br><span class="line"> <span class="keyword">compile</span> <span class="string">'com.android.support:design:26.0.0'</span></span><br><span class="line"> <span class="keyword">compile</span> <span class="string">'com.android.support:support-compat:25.2.0'</span></span><br><span class="line"> <span class="comment">//-------------add-------------</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    在主工程的AndroidManifest.xml文件中添加如下代码:<br><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.INTERNET"</span> /></span></span><br><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.WAKE_LOCK"</span> /></span></span><br><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.ACCESS_NETWORK_STATE"</span> /></span></span><br><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.WRITE_EXTERNAL_STORAGE"</span> /></span></span><br><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.ACCESS_WIFI_STATE"</span>/></span></span><br><span class="line"><span class="comment"><!--Google Play OBB Download Begin--></span></span><br><span class="line"><span class="tag"><<span class="name">activity</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:name</span>=<span class="string">"[文件所在包名].SampleDownloaderActivity"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:label</span>=<span class="string">"@string/app_name"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:screenOrientation</span>=<span class="string">"portrait"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:theme</span>=<span class="string">"@style/Base.Theme.AppCompat"</span> /></span></span><br><span class="line"><span class="tag"><<span class="name">activity</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:label</span>=<span class="string">"@string/app_name"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:name</span>=<span class="string">"[文件所在包名].SampleVideoPlayerActivity"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:theme</span>=<span class="string">"@style/VideoFullScreen"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:screenOrientation</span>=<span class="string">"portrait"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:configChanges</span>=<span class="string">"orientation|keyboard|keyboardHidden"</span> ></span></span><br><span class="line"><span class="tag"></<span class="name">activity</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!--</span></span><br><span class="line"><span class="comment"> In order to start the service, it must be uniquely registered with</span></span><br><span class="line"><span class="comment"> the package manager here.</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"><span class="tag"><<span class="name">service</span> <span class="attr">android:name</span>=<span class="string">"[文件所在包名].SampleDownloaderService"</span> /></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!--</span></span><br><span class="line"><span class="comment"> In order for the alarm manager to contact the downloader script, the receiver</span></span><br><span class="line"><span class="comment"> must be uniquely registered with the package manager here.</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"><span class="tag"><<span class="name">receiver</span> <span class="attr">android:name</span>=<span class="string">"[文件所在包名].SampleAlarmReceiver"</span> /></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!--</span></span><br><span class="line"><span class="comment">Providers must have a unique authority per application.</span></span><br><span class="line"><span class="comment">Change the android:authorities line to something unique to your</span></span><br><span class="line"><span class="comment">application (such as its package name) if you wish to use the</span></span><br><span class="line"><span class="comment">provider.</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">The format will be content://com.example.google.play.apkx/[zipfilepath].</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"><span class="tag"><<span class="name">provider</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:authorities</span>=<span class="string">"[文件所在包名]"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:exported</span>=<span class="string">"false"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:multiprocess</span>=<span class="string">"true"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:name</span>=<span class="string">"[文件所在包名].SampleZipFileProvider"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta-data</span> <span class="attr">android:name</span>=<span class="string">"mainVersion"</span> <span class="attr">android:value</span>=<span class="string">"3"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">provider</span>></span></span><br><span class="line"><span class="comment"><!--Google Play OBB Download End--></span></span><br></pre></td></tr></table></figure></p>
<p>    还有几处java文件的修改:</p>
<hr>
<h4 id="MainActivity-java"><a href="#MainActivity-java" class="headerlink" title="MainActivity.java"></a>MainActivity.java</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span></span>{</span><br><span class="line"> Log.w(<span class="string">"uuuuuuuuuu"</span>,<span class="string">"onCreate"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// FATE_OBB_PATH是存储obb路径的变量,getVirtualObbFileFullpath()在obb文件读取的文章中讲过</span></span><br><span class="line"> FATE_OBB_PATH = getVirtualObbFileFullpath();<span class="comment">//这句需要放在super.onCreate上面</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//------------add-------------</span></span><br><span class="line"> <span class="comment">//如果obb文件不存在,未能在Google Play中正确下载或是用户删除,则跳转至SampleDownloaderActivity开始下载</span></span><br><span class="line"> File file = <span class="keyword">new</span> File(FATE_OBB_PATH);</span><br><span class="line"> <span class="keyword">if</span> (!file.exists()){</span><br><span class="line"> Intent intent = <span class="keyword">new</span> Intent(<span class="keyword">this</span>, SampleDownloaderActivity.class);</span><br><span class="line"> <span class="keyword">this</span>.startActivity(intent);</span><br><span class="line"> <span class="keyword">this</span>.finish();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//------------add-------------</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<hr>
<h4 id="SampleDownloaderActivity-java"><a href="#SampleDownloaderActivity-java" class="headerlink" title="SampleDownloaderActivity.java"></a>SampleDownloaderActivity.java</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//修改为自己需要的参数</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> XAPKFile[] xAPKS = {</span><br><span class="line"> <span class="keyword">new</span> XAPKFile(</span><br><span class="line"> <span class="keyword">true</span>, <span class="comment">//isMain:是否为主要分包</span></span><br><span class="line"> <span class="number">6</span>, <span class="comment">//fileVersion:其实就是versionCode</span></span><br><span class="line"> <span class="number">182118395L</span> <span class="comment">//fileSize:分包文件字节大小,不知道怎么看的可以在cmd中使用dir命令查看</span></span><br><span class="line"> )</span><br><span class="line"> <span class="comment">// main file only</span></span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>    在void validateXAPKZipFiles()中,validationTask对象调用execute方法时,我把它加入了UiThread。(虽然不知道有没有用,但是在别的帖子中看到有人说这个问题。于是就加上了。)<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">this</span>.runOnUiThread(<span class="keyword">new</span> Runnable() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> validationTask.execute(<span class="keyword">new</span> Object());</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure><br>    加入onResume方法。<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Connect the stub to our service on resume.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onResume</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != mDownloaderClientStub) {</span><br><span class="line"> mDownloaderClientStub.connect(<span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">super</span>.onResume();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    startMovie是在验证文件结束后,界面上有一个确定按钮,点击事件中会调用这个方法。应该是demo中的骚操作,但我们这里不需要,所以我改成了以下内容,重新跳转回游戏activity。<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">startMovie</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// launch the movie player activity</span></span><br><span class="line"><span class="comment">// Uri mediaUri = Uri.withAppendedPath(SampleZipFileProvider.ASSET_URI,</span></span><br><span class="line"><span class="comment">// "big_buck_bunny_720p_surround.m4v");</span></span><br><span class="line"><span class="comment">// Intent intent = new Intent();</span></span><br><span class="line"><span class="comment">// intent.setData(mediaUri);</span></span><br><span class="line"><span class="comment">// intent.putExtra(Intent.EXTRA_TITLE, mediaUri.getLastPathSegment());</span></span><br><span class="line"><span class="comment">// intent.setClass(this, SampleVideoPlayerActivity.class);</span></span><br><span class="line"><span class="comment">// startActivity(intent);</span></span><br><span class="line"></span><br><span class="line"> Intent intent = <span class="keyword">new</span> Intent(<span class="keyword">this</span>, MainActivity.class);</span><br><span class="line"> startActivity(intent);</span><br><span class="line"> finish();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<hr>
<h4 id="SampleDownloaderService-java"><a href="#SampleDownloaderService-java" class="headerlink" title="SampleDownloaderService.java"></a>SampleDownloaderService.java</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//替换为自己的KEY,在Google后台查询自己的RSA公共密钥</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String BASE64_PUBLIC_KEY = <span class="string">"REPLACE THIS WITH YOUR PUBLIC KEY"</span></span><br></pre></td></tr></table></figure>
<hr>
<h4 id="SampleZipFileProvider-java"><a href="#SampleZipFileProvider-java" class="headerlink" title="SampleZipFileProvider.java"></a>SampleZipFileProvider.java</h4><hr>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// must match what is declared in the Zip content provider in</span></span><br><span class="line"><span class="comment">// the AndroidManifest.xml file</span></span><br><span class="line"><span class="comment">// 这里填写你在AndroidManifest.xml文件中注册此Provider时,填写的android:authorities</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String AUTHORITY = <span class="string">"com.xxx.xxx.xxx"</span>;</span><br></pre></td></tr></table></figure>
<p>    在依赖这些module的时候,可能会遇到这样一个无法import的问题。<br><br><img src="20180901_001/20180901001731.png" alt="images"><br><br>    请尝试在build.gradle中做如下修改。<br></p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line">android {</span><br><span class="line"> compileSdkVersion <span class="number">26</span></span><br><span class="line"> buildToolsVersion <span class="string">'28.0.2'</span></span><br><span class="line"> useLibrary <span class="string">'org.apache.http.legacy'</span> <span class="comment">//加上这一句</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>    到此,修改基本完成。在测试之前,请先确认不添加下载逻辑的情况下,使用本地挂载方式,把obb放到<b>/storage/emulated/0/Android/obb/[包名]</b>路径下是否能够正常读取。<br><br>    在测试下载逻辑之前,请先将手机安装Google Play全家桶。并将已添加为测试账号的Google Play账号在手机上登陆(因为只有测试账号才能看到内测版App),并进入Google Play中下载已提审通过的内测版App。之后如有修改,安装相同签名以及包名的apk即可直接调试,不用再次提交审核。</p>
<p>参考文献:<br><br><a href="https://blog.csdn.net/androidworkor/article/details/70226726">https://blog.csdn.net/androidworkor/article/details/70226726</a></p>
<p><a href="https://www.jianshu.com/p/de3c53f69925">https://www.jianshu.com/p/de3c53f69925</a></p>
]]></content>
<tags>
<tag>Cocos2d</tag>
</tags>
</entry>
<entry>
<title>Cocos2d-X2.2.6版本添加图片后缀名</title>
<url>/2018/03/31/Cocos2d-X/%E5%90%8E%E7%BC%80%E5%90%8D/</url>
<content><![CDATA[<!--    年前遇到一件特别扯淡的事情,因为项目初期的热更新模式没有设计好。采用的是解压方式,下载的热更新压缩包解压之后就留在手机内存卡里了,导致资源直接漏在外边,结果有些安卓手机的相册会把游戏资源图片检索出来,因为图片是加密过的,所以检索到相册中并不能正常显示,结果被一个傻X手机厂商发现了(因为在该手机厂商的应用商店上架了,具体不点名了)。因为游戏中每个功能模块需要的图片资源都是单独放在一个目录下的,而且游戏上线了两年了,模块比较多,导致文件夹也很多,结果相册是按照文件夹检索的,相册里多出了好几个文件夹,渠道技术客服说会把他们的相册搞崩溃(手机是有多垃圾,多了两个文件夹,相册就会崩溃),其实不然,我专门找了他家的手机试了一下,一点问题都没有。我前去询问,结果他们就改口了,说了一堆反正就是必须改,没商量。后来我问把文件夹隐藏掉可以么,并且还找了一个TX的游戏给他们示例,结果他说那款游戏也有问题,还没来得及下架呢,呵呵了。我就知道是故意找茬了,毕竟年底了么,为了KPI。那我改就是了。于是我把所有图片的PNG后缀名全部改了。啰嗦了这么多,切入正题。-->
<p>    本以为Cocos是把所有文件转换成二进制数据加载进内存的,所以代码中应该不去管后缀名了,然后我把图片更换png扩展名为metek,加密脚本中添加了新扩展名。CCFileUtils中也添加了新扩展名,用于解密流程中。一切准备就绪,发现安卓设备还是不能识别新扩展名。跟了一下代码,发现CCTextureeCache中computeImageFormatType和addImage方法中,通过eImageFormat变量的赋值为图片类型扩展名的枚举值。然后我在png的if条件中加入了新的扩展名后,发现还是无法解析出图片。于是,我打印了图片文件的头,发现标识头不是PNG格式,这时我开始怀疑是解密流程有问题。但是,因为单独加密的文件替换到iOS包中,是可以解析出图片的。这点可以证明加密流程是没有问题的。应该是android和ios走的读取文件并解密的接口不对。最终发现android走的是CCFileUitlsAndroid文件中的接口,后来在该文件中发现了这个数组。</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">char</span> decodeSuffix[][<span class="number">50</span>] = { <span class="string">".lua"</span>, <span class="string">".png"</span> };</span><br></pre></td></tr></table></figure>
<p>    于是,在数组中添加新扩展名后,解密流程就可以识别并解析metek文件了。</p>
]]></content>
<tags>
<tag>Cocos2d</tag>
</tags>
</entry>
<entry>
<title>Shading</title>
<url>/2021/05/16/ComputerGraphics/Shading/</url>
<content><![CDATA[<h3 id="Illumination-amp-Shading"><a href="#Illumination-amp-Shading" class="headerlink" title="Illumination & Shading"></a>Illumination & Shading</h3><h4 id="Shading-Definition"><a href="#Shading-Definition" class="headerlink" title="Shading: Definition"></a>Shading: Definition</h4><ul>
<li><p>In Merriam-Webster Dictionary</p>
<p> shad·ing, [ˈʃeɪdɪŋ], noun </p>
<p> The darkening or coloring of an illustration or diagram with parallel lines or a block of color.</p>
</li>
<li><p>In this course</p>
<p> The process of applying a material to an object.</p>
</li>
</ul>
<h4 id="A-Simple-Shading-Model-Blinn-Phong-Reflectance-Model"><a href="#A-Simple-Shading-Model-Blinn-Phong-Reflectance-Model" class="headerlink" title="A Simple Shading Model (Blinn-Phong Reflectance Model)"></a>A Simple Shading Model (Blinn-Phong Reflectance Model)</h4><ul>
<li>Specular highlights(镜面高光)</li>
<li>Diffuse reflection(漫反射)</li>
<li>Ambient lighting(环境光)</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608205432.png" alt="img"></p>
<!--![img](\Shading Note\20200608205432.png)-->
<h5 id="Shading-is-Local"><a href="#Shading-is-Local" class="headerlink" title="Shading is Local"></a>Shading is Local</h5><p>Compute light reflected toward camera at a specific shading point.</p>
<p>Inputs: </p>
<ul>
<li>Viewer direction, $ v $</li>
<li>Surface normal, $ n $</li>
<li>Light direction, $ l $ (for each of many lights) </li>
<li>Surface parameters(color, shininess, …)</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608205843.png" alt=""></p>
<!--![img](Shading Note\20200608205843.png)-->
<p>No shadows will be generated! (shading ≠ shadow) </p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608210044.png" alt=""></p>
<!-- ![img](Shading Note\20200608210044.png) -->
<h5 id="Diffuse-Reflection"><a href="#Diffuse-Reflection" class="headerlink" title="Diffuse Reflection"></a>Diffuse Reflection</h5><ul>
<li>But how much light (energy) is received?<ul>
<li>Lambert’s cosine law</li>
</ul>
</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608210753.png" alt="img"></p>
<ol>
<li>立方体的顶面接收一定量的光</li>
<li>旋转60度的立方体顶面只截取了一半的光</li>
<li>通常,每个单位面积的光与<script type="math/tex">\cos(\theta) = l · n</script>成正比</li>
</ol>
<h5 id="Light-Falloff"><a href="#Light-Falloff" class="headerlink" title="Light Falloff"></a>Light Falloff</h5><p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608212142.png" alt="img"></p>
<p>半径为1的球面上,光的强度为I。球的面积公式为<script type="math/tex">4\pi R^{2}</script>, 所以图中最远处的球面上,光强度为<script type="math/tex">I/R^{2}</script>。</p>
<h5 id="Lambertian-Diffuse-Shading"><a href="#Lambertian-Diffuse-Shading" class="headerlink" title="Lambertian (Diffuse) Shading"></a>Lambertian (Diffuse) Shading</h5><p>Shading independent of view direction</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608212954.png" alt="img"></p>
<ul>
<li>$k_{d}$: 漫反射项的系数 (vector类型,分别代表RGB,0-1)<ul>
<li>如果等于1,就代表该点完全不吸收能量,进来多少反射多少</li>
<li>如果等于0,就代表该点吸收了所有能量,没有能量反射出去</li>
</ul>
</li>
<li>$I/r^{2}$:到达shading point的能量</li>
<li>$max(0, n · l)$:有多少能量会被接收,因为n和l为单位向量,所以余弦值就是两个向量点乘,如果余弦值为负数,则无物理意义。</li>
</ul>
<p>漫反射打到一个点上,反射光应该是各个方向均匀分布,所以从哪个角度观察,结果都是一模一样的,所以公式和向量v没有任何关系。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608225954.png" alt="img"></p>
<h5 id="Specular-Term-Blinn-Phong"><a href="#Specular-Term-Blinn-Phong" class="headerlink" title="Specular Term (Blinn-Phong)"></a>Specular Term (Blinn-Phong)</h5><p>镜面反射项,Blinn-Phong模型(高光)</p>
<p>镜面反射强度取决于观察方向</p>
<ul>
<li>接近镜面反射方向</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608220955.png" alt="img"></p>
<p>$v$距离镜面反射方向越近,半程向量越接近法向量</p>
<ul>
<li>用单位向量的点积测量接近程度</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608221458.png" alt="img"></p>
<ul>
<li>$ k_{s} $:镜面反射系数,通常认为是一个白色</li>
<li>$(I/r^{2})$:到达shading point的能量</li>
<li>$max(0, n·h)^{p}$:使用点乘计算半程向量和法向量的夹角余弦值,从而判断两个向量的接近程度<ul>
<li>n:法向量</li>
<li>h:半程向量,使用平行四边形法则,向量v加上向量l求出半程向量,除以向量的模得出半程向量的单位向量h</li>
<li>p:指数,因为余弦值用来判断两个向量的接近程度,容忍度太高,会导致高光面积过大,所以需要加一个指数。正常再Blinn-Phong模型中大概会用到100-200。(如下图)</li>
</ul>
</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608223149.png" alt="img"></p>
<p>这里因为Blinn-Phong模型是一个经验模型,所以不考虑shading poing有多少能量被吸收。</p>
<p>使用镜面反射向量r和观察向量v做点乘也可以判断这两个向量的远近程度,从而判断高光。此模型被称为Phong模型,Blinn-Phong模型是Phong模型的一个改进,因为半程向量的计算量低于反射向量的计算量。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608223317.png" alt="img"></p>
<h5 id="Ambient-Term"><a href="#Ambient-Term" class="headerlink" title="Ambient Term"></a>Ambient Term</h5><p>环境光</p>
<p>做一个大胆的假设,任何一个点接收到来自环境的光永远都是相同的,强度叫做$ I_{a} $。任何点都有自己的一个颜色,$k_{a}$环境光的系数。把$k_{a}$和$I_{a}$结合在一起,我们就可以近似的得出一个环境光。可以保证没有地方是完全黑色的。</p>
<h5 id="Blinn-Phong-Reflection-Model"><a href="#Blinn-Phong-Reflection-Model" class="headerlink" title="Blinn-Phong Reflection Model"></a>Blinn-Phong Reflection Model</h5><p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200608225152.png" alt="img"></p>
<script type="math/tex; mode=display">
\begin{equation}
\begin{split}
L &= L_{a} + L_{d} + L_{s} \\
&= k_{a}I_{a} + k_{d}(I/r^{2})max(0, n·l) + k_{s}(I/r^{2})max(0, n·h)^{p}\\
\end{split}
\end{equation}</script><h4 id="Shading-Frequencies"><a href="#Shading-Frequencies" class="headerlink" title="Shading Frequencies"></a>Shading Frequencies</h4><h5 id="Shading-Frequency-Face-Vertex-or-Pixel"><a href="#Shading-Frequency-Face-Vertex-or-Pixel" class="headerlink" title="Shading Frequency: Face, Vertex or Pixel"></a>Shading Frequency: Face, Vertex or Pixel</h5><p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609111554.png" alt="img"></p>
<p>着色频率取决于几何面或者顶点出现的频率,当面出现的频率已经很高的情况下,就不需要使用复杂的逐像素着色。</p>
<h5 id="Defining-Per-Vertex-Normal-Vectors"><a href="#Defining-Per-Vertex-Normal-Vectors" class="headerlink" title="Defining Per-Vertex Normal Vectors"></a>Defining Per-Vertex Normal Vectors</h5><p>最好的方法是从基础的集合获取顶点的法线。例如下图(上),当我们知道我们所要渲染的几何是个球体,那么顶点表示的就是球面上的点,可以通过求该顶点所在球面的法线来获取顶点法线。但是现实情况中,并不能够总是渲染一个已知的规则几何。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609112815.png" alt="img"></p>
<p>所以我们需要通过三角面的法线来推断顶点法线,其中最简单的方案就是,求顶点所关联的所有面法线之和的平均,然后再做归一化操作,得出该顶点的法线。</p>
<script type="math/tex; mode=display">
N_{v} = \frac{\sum_i N_{i}}{||\sum_i N_{i}||}</script><h5 id="Defining-Per-Pixel-Normal-Vectors"><a href="#Defining-Per-Pixel-Normal-Vectors" class="headerlink" title="Defining Per-Pixel Normal Vectors"></a>Defining Per-Pixel Normal Vectors</h5><p>Barycentric interpolation (introducing soon) of vertex normals</p>
<p>顶点法线的重心插值</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609114951.png" alt="img"></p>
<h4 id="Graphics-Real-time-Rendering-Pipeline"><a href="#Graphics-Real-time-Rendering-Pipeline" class="headerlink" title="Graphics (Real-time Rendering) Pipeline"></a>Graphics (Real-time Rendering) Pipeline</h4><p>图形管线(实时渲染管线)流程</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609184456.png" alt="img"></p>
<p>顶点处理,做一些顶点变换</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609184555.png" alt="img"></p>
<p>顶点处理和三角形处理后,进行光栅化三角形</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609184952.png" alt="img"></p>
<p>片段(像素)处理,深度测试(有些教材把深度测试归为光栅化阶段)</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609185037.png" alt="img"></p>
<p>着色阶段有两个</p>
<ul>
<li>顶点着色,高洛德着色(Gouraud Shading)</li>
<li>片段着色,Phong着色(Phong Shading)。</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609185228.png" alt="img"></p>
<p>纹理映射</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609185602.png" alt="img"></p>
<h5 id="Shader-Programs"><a href="#Shader-Programs" class="headerlink" title="Shader Programs"></a>Shader Programs</h5><ul>
<li>Vertex shader</li>
<li>Fragment shader</li>
</ul>
<p>这里拿一个GLSL的片段着色程序来做实例:</p>
<ul>
<li>着色方法在每个片段上执行一次</li>
<li>在当前片段的屏幕采样点输出表面颜色</li>
<li>该着色器执行纹理查找以获取表面的材质颜色,然后执行漫射照明计算</li>
</ul>
<figure class="highlight glsl"><table><tr><td class="code"><pre><span class="line"><span class="keyword">uniform</span> <span class="type">sampler2D</span> myTexture; <span class="comment">// program parameter</span></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">vec3</span> lightDir; <span class="comment">// program parameter</span></span><br><span class="line"><span class="keyword">varying</span> <span class="type">vec2</span> uv; <span class="comment">// per fragment value (interp. by rasterizer)</span></span><br><span class="line"><span class="keyword">varying</span> <span class="type">vec3</span> norm; <span class="comment">// per fragment value (interp. by rasterizer)</span></span><br><span class="line"><span class="type">void</span> diffuseShader()</span><br><span class="line">{</span><br><span class="line"> <span class="type">vec3</span> kd;</span><br><span class="line"> kd = texture2d(myTexture, uv); <span class="comment">// material color from texture</span></span><br><span class="line"> kd *= <span class="built_in">clamp</span>(<span class="built_in">dot</span>(–lightDir, norm), <span class="number">0.0</span>, <span class="number">1.0</span>); <span class="comment">// Lambertian shading model</span></span><br><span class="line"> <span class="built_in">gl_FragColor</span> = <span class="type">vec4</span>(kd, <span class="number">1.0</span>); <span class="comment">// output fragment color</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h5 id="Goal-Highly-Complex-3D-Scenes-in-Realtime"><a href="#Goal-Highly-Complex-3D-Scenes-in-Realtime" class="headerlink" title="Goal: Highly Complex 3D Scenes in Realtime"></a>Goal: Highly Complex 3D Scenes in Realtime</h5><ul>
<li>100’s of thousands to millions of triangles in a scene </li>
<li>Complex vertex and fragment shader computations</li>
<li>High resolution (2-4 megapixel + supersampling) </li>
<li>30-60 frames per second (even higher for VR)</li>
</ul>
<h4 id="Texture-Mapping"><a href="#Texture-Mapping" class="headerlink" title="Texture Mapping"></a>Texture Mapping</h4><h5 id="Surfaces-are-2D"><a href="#Surfaces-are-2D" class="headerlink" title="Surfaces are 2D"></a>Surfaces are 2D</h5><p>任何一个三维物体它的表面都是二维的。</p>
<p>任意3D表面上的点都对应2D图片上的一个位置,这个2D图片就称为纹理(Texture)。把这个图片通过拉伸、压缩等方式蒙在3D物体的表面,这个过程就叫做纹理映射(Texture Mapping)。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609193337.png" alt="img"></p>
<p>每个三角形将一块纹理图片“拷贝”到表面。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609194915.png" alt="img"></p>
<p>每一个三角形的顶点上都分配有一个纹理坐标(Texture Coordinate,(u,v))</p>
<ul>
<li>$ u $:代表横向坐标,越红代表值越大</li>
<li>$ v $:代表纵向坐标,越绿代表值越大</li>
<li>坐标值在区间$ [0,1] $上,方便计算任何分辨率大小的纹理的坐标</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609195433.png" alt="img"></p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609195505.png" alt="img"></p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609195633.png" alt="img"></p>
<p>可视化的纹理坐标</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609195729.png" alt="img"></p>
<p>纹理是可以复用的,即同一个纹理可以被多次使用。这种纹理在图形学中被统称为无缝纹理贴图(Tileable Texture)。这种纹理的设计需要各种各样的算法,其中一种算法叫做Wang Tiled。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200609195905.png" alt="img"></p>
<h5 id="Interpolation-Across-Triangles-Barycentric-Coordinates"><a href="#Interpolation-Across-Triangles-Barycentric-Coordinates" class="headerlink" title="Interpolation Across Triangles:Barycentric Coordinates"></a>Interpolation Across Triangles:Barycentric Coordinates</h5><p>三角形的插值:重心坐标</p>
<p>我们为什么要引入插值?</p>
<ul>
<li>三角形顶点的值都是额定的</li>
<li>平滑过渡</li>
</ul>
<p>我们使用插值都做了些什么?</p>
<ul>
<li>纹理坐标插值</li>
<li>颜色插值</li>
<li>法线插值</li>
</ul>
<p>重心坐标是定义在三角形上的,在三角形ABC构成的平面内,任何一点(x, y)都可以表示成A、B、C这三个顶点坐标的线性组合。</p>
<script type="math/tex; mode=display">
(x,y)=\alpha A+\beta B+\gamma C \\
\alpha+\beta+\gamma=1</script><p>当系数$\alpha$、$\beta$、$\gamma$都非负时,该点在三角形内;否则在三角形外。如果三个系数之和不为1,那么该点不在三角形构成的平面内。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611194235.png" alt="img"></p>
<p>那么三角形A点的重心坐标是多少呢,显而易见:</p>
<script type="math/tex; mode=display">
\begin{equation}
\begin{split}
(\alpha, \beta, \gamma) &= (1, 0, 0) \\
(x, y) &= \alpha A+\beta B+\gamma C \\
&= A
\end{split}
\end{equation}</script><p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611194644.png" alt="img"></p>
<p>在几何定义下,我们可以通过面积之比来计算三个系数$ \alpha $、$ \beta $、$ \gamma $。</p>
<script type="math/tex; mode=display">
\begin{equation}
\begin{split}
\alpha &= \frac{A_{A}}{A_{A}+A_{B}+A_{C}} \\
\beta &= \frac{A_{B}}{A_{A}+A_{B}+A_{C}} \\
\gamma &= \frac{A_{C}}{A_{A}+A_{B}+A_{C}} \\
\end{split}
\end{equation}</script><p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611195210.png" alt="img"></p>
<p>在重心坐标系下,三角形的重心是多少呢?</p>
<p>根据重心的性质,重心和三角形的三个顶点连接起来,会将三角形等分为三份,即三个等面积的三角形。那么根据上边的公式可以得出:</p>
<script type="math/tex; mode=display">
\begin{equation}
\begin{split}
(\alpha, \beta, \gamma) &= (\frac{1}{3}, \frac{1}{3}, \frac{1}{3}) \\
(x, y) &= \frac{1}{3} A+\frac{1}{3} B+\frac{1}{3} C \\
\end{split}
\end{equation}</script><p>同时,三角形的面积可以通过向量的叉乘计算:</p>
<script type="math/tex; mode=display">
\begin{equation}
\begin{split}
\alpha &= \frac{-(x-x_{B})(y_{C}-y_{B})+(y-y_{B})(x_{C}-x_{B})}{-(x_{A}-x_{B})(y_{C}-y_{B})+(y_{A}-y_{B})(x_{C}-x_{B})} \\
\beta &= \frac{-(x-x_{C})(y_{A}-y_{C})+(y-y_{C})(x_{A}-x_{C})}{-(x_{B}-x_{C})(y_{A}-y_{C})+(y_{B}-y_{C})(x_{A}-x_{C})} \\
\gamma &= 1-\alpha-\beta \\
\end{split}
\end{equation}</script><p>我们需要插值的属性,也同样可以通过重心坐标线性的组合出来。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611201227.png" alt="img"></p>
<p>但是,重心坐标在投影变化下,并不是不变的。所以光栅化中的深度测试,需要先做逆变换,然后对三维空间中的坐标做插值。</p>
<h5 id="Applying-Textures"><a href="#Applying-Textures" class="headerlink" title="Applying Textures"></a>Applying Textures</h5><p>如何进行纹理映射?</p>
<p>先对三角形中的采样点进行纹理坐标插值,得到$(u,v)$坐标,然后在纹理上查询$(u,v)$值,得出这个点上的颜色。我们可以认为这个颜色就是漫反射系数$k_{d}$,然后经过Phong Shading的计算,将这个颜色值设置到物体上,即相当于将图贴到了物体上,且带有漫反射、高光、环境光等效果。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611203539.png" alt="img"></p>
<h5 id="Texture-Magnification"><a href="#Texture-Magnification" class="headerlink" title="Texture Magnification"></a>Texture Magnification</h5><h6 id="纹理的放大,即纹理的分辨率不足"><a href="#纹理的放大,即纹理的分辨率不足" class="headerlink" title="纹理的放大,即纹理的分辨率不足"></a>纹理的放大,即纹理的分辨率不足</h6><p>A pixel on a texture — a texel (纹理元素、纹素)</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611205422.png" alt="img"></p>
<ul>
<li>Bilinear Interpolation(双线性插值)</li>
</ul>
<p>如下图,我们想要在红点处采样纹理值,其中黑点代表纹理采样点。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611205548.png" alt="img"></p>
<p>取四个临近的采样点</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611210145.png" alt="img"></p>
<p>令两个采样点之间的距离为1,以四个点的左下角为0点,然后可以通过水平距离$s$和垂直距离$t$,找到红点的位置。</p>
<p>通过线性插值(Linear interpolation (1D))操作,即</p>
<script type="math/tex; mode=display">
lerp(x,v_{0},v_{1})=v_{0}+x(v_{1}-v_{0})</script><p>当x=0时,插值等于$v_{0}$;当x=1时,插值等于$v_{1}$。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611210957.png" alt="img"></p>
<p>那么,我们对下边这条边,即左下角和右下角这两个点,使用$s$做插值;同理,上边这条边,我们也可以使用$s$对左上角和右上角两点进行插值。即,</p>
<script type="math/tex; mode=display">
u_{0}=lerp(s,u_{00},y_{10}) \\
u_{1}=lerp(s,u_{01},y_{11})</script><p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611211030.png" alt="img"></p>
<p>最后,我们再使用$t$做一次竖直方向上的插值。即,</p>
<script type="math/tex; mode=display">
f(x,y)=lerp(t,u_{0},u_{1})</script><p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611211743.png" alt="img"></p>
<p>双线性插值通常以合理的成本给出相当不错的结果。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200611211859.png" alt="img"></p>
<p>但是,双线性插值(Bilinear Interpolation)也存在一些问题,对于一些更高级的方法,双线性插值的质量还是差一些。</p>
<h6 id="纹理过大,也就是纹理的分辨率过于大"><a href="#纹理过大,也就是纹理的分辨率过于大" class="headerlink" title="纹理过大,也就是纹理的分辨率过于大"></a>纹理过大,也就是纹理的分辨率过于大</h6><p>纹理过大,采样后会出现什么问题呢?</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200612212033.png" alt="img"></p>
<p>没错,近处会出现锯齿,远处会出现摩尔纹。</p>
<p>为什么会出现这种走样的情况?</p>
<p>因为近处一个像素所覆盖的纹理上的区域相对较小,但是在远处一个像素就覆盖了一片纹理区域。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200612213031.png" alt="img"></p>
<p>那么我们依旧可以采用超采样的方法,得出一个不错的结果,但是使用超采样会有一个问题,那就是开销问题。增加采样点,必然会使整个算法变得特别慢。当然,我们也不希望一个算法会变得超级慢。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200612213558.png" alt="img"></p>
<p>如果不使用超采样方法的话,我们仅需要得到该像素所对应的范围内的平均值即可。范围查询不只是我们用的这一种应用——平均查询,还有很多类型的范围查询,比如说查询范围内的最大值、最小值等。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200612221107.png" alt="img"></p>
<p>不同像素在纹理上的覆盖对应不同大小。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200612221257.png" alt="img"></p>
<h5 id="Mipmap"><a href="#Mipmap" class="headerlink" title="Mipmap"></a>Mipmap</h5><p>允许(快速,近似,正方形)范围查询</p>
<p>“ Mip”来自拉丁语“multum in parvo”,意思是有很多不同的小的东西。</p>
<p>其实Mipmap就是从一张图生成一系列图,假如说第一张图叫做Level 0,那么我们可以生成更多更高层的纹理,使得每一层$i$,都是第$i-1$层的长和宽缩小一半,直到最后剩下一个点。一共$log_{2}n$层,$n$为分辨率。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613161356.png" alt="img"></p>
<p>对应到Mipmap中,我们生成的是如下图这样的东西,第0层为原始的图像,上边总比下边小一半。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613161446.png" alt="img"></p>
<p>所以在计算机视觉界,大家不把它称作Mipmap,而是称作Image pyramid(图像金字塔)。</p>
<p>Mipmap所造成的额外存储仅是原来的$\frac{1}{3}$,使用级数求和可以计算得出。</p>
<script type="math/tex; mode=display">
\sum_{n=1}^{\infty}{1+\frac{1}{4^n}}</script><p>还有一种理解方式,可以把每一层的存储都乘以3(不影响最终结果),并把第0层的三份分别放在左下角、左上角、右上角,然后把第一层的三份分别放在空置的右下角方块中,并且依旧按照左下角、左上角、右上角,那么右下角再次空出,以此类推把剩余层填入剩余的空间。则会发现,最外层除了三份0层,剩下的层都放在了右下角,所以得出$\frac{1}{3}$。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613165212.png" alt="img"></p>
<h6 id="计算Mipmap层"><a href="#计算Mipmap层" class="headerlink" title="计算Mipmap层"></a>计算Mipmap层</h6><p>任何像素都会有一个对应的映射在纹理上的区域</p>
<p>如下图,要计算左下方的红点所占据的像素的覆盖面积,我们可以取这个像素的中心点和它相邻像素的中心点,分别投影到纹理上去。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613165710.png" alt="img"></p>
<p>求出纹理上,该点相对于上方相邻点和右方相邻点的长度,就可以近似计算出这个像素在纹理上所占区域的边长。这个边长就作为以该像素中心点为重心,所占正方形区域的边长。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613170321.png" alt="img"></p>
<p>接下来,我们应该如何根据预计算好的Mipmap,去查询这个边长为$L$的正方形区域内的平均值?</p>
<p>我们可以假设,如果这个正方形的区域大小是1x1,就是一个像素,那我们可以在没有做过Mipmap的最原始的纹理上找对应的像素。</p>
<p>如果这个正方形的大小是4x4,那我们可以得出这个区域一定会在第2层上变成1个像素。为什么呢?我们可以思考一下,这个区域的大小是4x4,是指在最原始的图片上的大小是4x4。那么经过第一层Mipmap后,这个区域会变成2x2,然后经过第二层Mipmap之后,这个区域会变成1x1。</p>
<p>这个$L*L$大小的区域,一定会在$log_{2}L$层上对应到1个像素。也就是说我们只要计算出这个区域在第几层变成了一个像素,那么我们就可以去查找那个像素,即立刻得出这个区域的平均值是多少。</p>
<p>如果说对于每个像素,我们都计算它投影到纹理上对应的区域,然后根据这个区域的大小,计算出应该在第几层Mipmap上去找这个像素的平均值,那我们就可以做一个可视化。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613172503.png" alt="img"></p>
<p>虽然在上图中,我们发现这些颜色有些渐变,但是会发现一个问题,那就是这些颜色的变化并不连续。因为我们只查询了第0层、第1层、第2层等等的整数层,如果我们想要查询第1.5层、第1.8层,应该怎么办呢?</p>
<p>那当然是使用插值</p>
<p>假设我们想查询第1.8层,那么应该先查询第一层,再查询第二层。在这两层内部,分别使用双线性插值,把所在的这两层上的查询先做出来;然后把这两层插值得出的值,再做一次插值,即相当于在层与层之间做了一次插值。一共做了三次插值,我们把它称作<strong>三线性插值(Trilinear Interpolation)</strong>。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613174601.png" alt="img"></p>
<p>再来对比一下原图、超采样、Mipmap。</p>
<p>原图:</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613174846.png" alt="img"></p>
<p>使用512x超采样:</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613174856.png" alt="img"></p>
<p>Mipmap:</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613174913.png" alt="img"></p>
<p>我们会发现Mipmap似乎并不太对,远处全部模糊了起来。</p>
<p>原因在于,Mipmap只能查询到正方形区域内的插值。我们可以使用<strong>各向异性过滤(Anisotropic Filtering )</strong>。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613180516.png" alt="img"></p>
<p>Mipmap所做的事情就是在原图上一直把长宽缩小一般,那么对应到下图中其实Mipmap就是做了对角线上的计算。但是有一些图需要作不同长宽比的预计算,这个就是Mipmap中所没有的。</p>
<p><strong>各向异性过滤</strong></p>
<p>各向异性是指,在不同的方向上表现各不相同。我们原本认为在正方形的水平和垂直方向上,表现完全相同,就叫做各向同性。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613180827.png" alt="img"></p>
<p>我们可以看到上图中,每一行都做了水平方向上的压缩,每一列都是垂直方向上的压缩。它相比于Mipmap多了一些水平方向和垂直方向上不均匀的压缩。</p>
<p>通过这样的一种方式的预计算,就可以查询到任何一个被压扁了的图的位置,即可以查询原图的一个矩形区域,而不用被限制在一个正方形的区域内。</p>
<p>因为屏幕上的任何一个像素映射在纹理上,不一定都是一个规律的形状,很可能出现斜着的、极细的形状,如下图。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613182020.png" alt="img"></p>
<p>例如上(右)图中,左上方斜长条区域,如果我们把它近似成一个正方形区域,就相当于求了一个更大区域的平均,所以会造成<strong>Overblur(过于模糊)</strong>。</p>
<p>如果我们引入各向异性过滤,例如上(右)图中,右上方的长条区域就可以得到一个近乎完美的解决,因为各向异性过滤允许我们对这种长条形的区域做一个快速的查询。</p>
<p>我们可以通过上边带有卫星的那张图片分析出,各向异性过滤所生成的图,总共的开销是原本的3倍(原图占左上角的1/4)。各向异性过滤生成的图有一个名字,叫做<strong>Ripmap</strong>。而Mipmap所需的开销比原本多1/3,这里可以供我们参考一下。</p>
<p>如果大家经常打游戏的话,会接触到一个各向异性过滤相关的概念,就是多少x,这里的x其实指的就是计算多少层,比如说2x指的就是方向上压缩了一次,就是上边带有卫星的那张图片中的左上角四个卫星的区域,以此类推,4x就是在2x的基础上又压缩了一次,就是上边带有卫星的那张图片中的左上角九个卫星的区域,即左上角$((log_{2}{n})+1)^2$个卫星,n代表nx。</p>
<p>我们会发现,n的增加,最后的结果会逐渐收敛至原图的三倍,也就是说各向异性过滤的存储量和用户开nx的关系不大。应用各向异性过滤,只要显存足够,其实和计算力基本没有关系,所以在游戏中将各向异性过滤开至最高也几乎不会对游戏有性能的影响。</p>
<p>但是各向异性过滤仍然不能解决斜着的区域,要解决这个问题,可以使用一些另外的方法,比如EWA filtering。</p>
<p><strong>EWA filtering</strong></p>
<ul>
<li>Use multiple lookups</li>
<li>Weighted average</li>
<li>Mipmap hierarchy still helps</li>
<li>Can handle irregular footprints</li>
</ul>
<p>任意一个不规则的形状都可以被拆分成很多圆形,以覆盖这个不规则形状。例如,有一个椭圆形(如下图),可以把它拆分成三个不同的圆形,然后每次去查询一个圆形,然后多次查询,自然就可以去覆盖一个不规则的形状。但是代价就是牺牲时间,因为查询时需要耗时的。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20200613183416.png" alt="img"></p>
]]></content>
<categories>
<category>Computer Graphics</category>
</categories>
<tags>
<tag>Games101</tag>
</tags>
</entry>
<entry>
<title>投影变换的推导</title>
<url>/2020/05/31/ComputerGraphics/%E6%8A%95%E5%BD%B1%E5%8F%98%E6%8D%A2%E7%9A%84%E6%8E%A8%E5%AF%BC/</url>
<content><![CDATA[<p>这里我们说到的投影分为两种:</p>
<ul>
<li>正交投影(Orthographic projection (O))</li>
<li>透视投影(Perspective projection (P))</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/20191118233041.png" alt="img"></p>
<h2 id="正交投影-Orthographic-projecton"><a href="#正交投影-Orthographic-projecton" class="headerlink" title="正交投影(Orthographic projecton)"></a>正交投影(Orthographic projecton)</h2><p>先来说一说最简单的正交投影,我们需要做的是:</p>
<ul>
<li>定义一个<script type="math/tex">[l, r] * [b, t] * [f, n]</script>的立方体</li>
<li>然后将定义的立方体转换成“标准(canonical)”立方体<script type="math/tex">[-1, 1]^{3}</script></li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/20200531015713.png" alt="img"></p>
<p>如上图,我们想要把最左边的长方体变成最右边的立方体,需要两步:</p>
<ul>
<li>平移(Translate)</li>
<li>缩放(Scale)</li>
</ul>
<h3 id="平移-Translate"><a href="#平移-Translate" class="headerlink" title="平移(Translate)"></a>平移(Translate)</h3><p>首先我们要做的操作就是平移,先把长方体的中心点移至原点(origin)。因为我们已经知道l,r,b,t,f,n六个参数,所以我们可以计算出当前长方体的中心点,即:</p>
<script type="math/tex; mode=display">
(x, y, z) = (\frac{(r + l)}{2}, \frac{(t + b)}{2}, \frac{(n + f)}{2})</script><p>那么平移变换矩阵就应该是一个单位矩阵加上平移部分,要想将长方体的中心点移至原点(origin),只需要把所有点都减去长方体的中心点即可。</p>
<script type="math/tex; mode=display">
M_{Translate} =
\begin{pmatrix}
1 & 0 & 0 & -\frac{(r + l)}{2} \\
0 & 1 & 0 & -\frac{(t + b)}{2} \\
0 & 0 & 1 & -\frac{(n + f)}{2} \\
0 & 0 & 0 & 1 \\
\end{pmatrix}</script><h3 id="缩放-Scale"><a href="#缩放-Scale" class="headerlink" title="缩放(Scale)"></a>缩放(Scale)</h3><p>有了平移变换,接下来要做的就是缩放了,将每个点都等比缩放至<script type="math/tex">[-1, 1]^{3}</script>立方体上。那么<script type="math/tex">[l, r]</script>和<script type="math/tex">[-1, 1]</script>的比例为<script type="math/tex">\frac{2}{r - l}</script>,<script type="math/tex">[b, t]</script>和<script type="math/tex">[-1, 1]</script>的比例为<script type="math/tex">\frac{2}{t - b}</script>,<script type="math/tex">[f, n]</script>和<script type="math/tex">[-1, 1]</script>的比例为<script type="math/tex">\frac{2}{n - f}</script>(因为相机照像<script type="math/tex">-z</script>方向,所以<script type="math/tex">n</script>比<script type="math/tex">f</script>大)。</p>
<script type="math/tex; mode=display">
M_{Scale} =
\begin{pmatrix}
\frac{2}{r - l} & 0 & 0 & 0 \\
0 & \frac{2}{t - b} & 0 & 0 \\
0 & 0 & \frac{2}{n - f} & 0 \\
0 & 0 & 0 & 1 \\
\end{pmatrix}</script><h3 id="两种变换相乘"><a href="#两种变换相乘" class="headerlink" title="两种变换相乘"></a>两种变换相乘</h3><p>将平移和缩放融合在一起,即两矩阵相乘:</p>
<script type="math/tex; mode=display">
\begin{equation}\begin{split}
M_{Ortho} &= M_{Scale} * M_{Translate} \\
&=
\begin{pmatrix}
\frac{2}{r - l} & 0 & 0 & 0 \\
0 & \frac{2}{t - b} & 0 & 0 \\
0 & 0 & \frac{2}{n - f} & 0 \\
0 & 0 & 0 & 1 \\
\end{pmatrix}
*
\begin{pmatrix}
1 & 0 & 0 & -\frac{(r + l)}{2} \\
0 & 1 & 0 & -\frac{(t + b)}{2} \\
0 & 0 & 1 & -\frac{(n + f)}{2} \\
0 & 0 & 0 & 1 \\
\end{pmatrix} \\
&=
\begin{pmatrix}
\frac{2}{r - l} & 0 & 0 & -\frac{(r + l)}{r - l} \\
0 & \frac{2}{t - b} & 0 & -\frac{(t + b)}{t - b} \\
0 & 0 & \frac{2}{n - f} & -\frac{(n + f)}{n - f} \\
0 & 0 & 0 & 1 \\
\end{pmatrix} \\
\end{split}\end{equation}</script><h2 id="透视投影-Perspective-projection"><a href="#透视投影-Perspective-projection" class="headerlink" title="透视投影(Perspective projection)"></a>透视投影(Perspective projection)</h2><p>透视投影相对于正交投影就比较困难了,不过我们可以换一种思路</p>
<ul>
<li>首先,把视锥体(view frustum)挤扁成一个立方体(cuboid)(<script type="math/tex">M_{Persp->Ortho}</script>)</li>
<li>然后,我们通过正交投影,即可把远平面投影至近平面</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/20200531025421.png" alt="img"></p>
<h3 id="把视锥体-view-frustum-挤扁成一个立方体-cuboid"><a href="#把视锥体-view-frustum-挤扁成一个立方体-cuboid" class="headerlink" title="把视锥体(view frustum)挤扁成一个立方体(cuboid)"></a>把视锥体(view frustum)挤扁成一个立方体(cuboid)</h3><p>在挤压的过程中,我们需要规定几件事情:</p>
<ul>
<li>近平面上的任意点都不会改变</li>
<li>远平面上的<script type="math/tex">z</script>值不变,永远都是<script type="math/tex">f</script></li>
<li>远平面上的中心点不变</li>
</ul>
<p>下面我们来寻找一下近平面和远平面之间的点的关系,从视锥体的右侧观察,如下图:</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF/20200531025750.png" alt="img"></p>
<p>通过相似三角形的性质,可以得出<script type="math/tex">y^{'} = \frac{n}{z}y</script>,同理,<script type="math/tex">x^{'} = \frac{n}{z}x</script>。</p>
<p>因为<script type="math/tex">z</script>暂时未知,那么我们可以得出,点<script type="math/tex">\begin{pmatrix} x & y & z & 1\end{pmatrix}^{T}</script>经过变换后,可以得到点<script type="math/tex">\begin{pmatrix} \frac{nx}{z} & \frac{ny}{z} & unknow & 1\end{pmatrix}^{T}</script>。</p>
<p>通过齐次坐标,我们可以将点<script type="math/tex">\begin{pmatrix} \frac{nx}{z} & \frac{ny}{z} & unknow & 1\end{pmatrix}^{T}</script>转换为点<script type="math/tex">\begin{pmatrix} nx & ny & unknow & z\end{pmatrix}^{T}</script>。</p>
<p>所以,</p>
<script type="math/tex; mode=display">
M_{Persp->Ortho} · \begin{pmatrix} x \\ y \\ z \\ 1\end{pmatrix} =
\begin{pmatrix} nx \\ ny \\ unknow \\ z\end{pmatrix}</script><p>根据上述等式,我们已经可以反推出:</p>
<script type="math/tex; mode=display">
M_{Persp->Ortho} =
\begin{pmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0 \\
? & ? & ? & ? \\
0 & 0 & 1 & 0 \\
\end{pmatrix}</script><p>那么未知的第三行应该如何推算呢?我们可以思考两个特殊条件:</p>
<ul>
<li>近平面上任意点在做变换后,不会改变</li>
<li>远平面上任意点的z值在做变换后,不会改变</li>
</ul>
<h4 id="近平面上任意点变换后不会改变"><a href="#近平面上任意点变换后不会改变" class="headerlink" title="近平面上任意点变换后不会改变"></a>近平面上任意点变换后不会改变</h4><p>取近平面上点<script type="math/tex">(x, y, n, 1)^{T}</script>,通过齐次坐标,可以转换为点<script type="math/tex">(nx, ny, n^{2}, n)^{T}</script>。则:</p>
<script type="math/tex; mode=display">
\begin{equation}\begin{split}
M_{Persp->Ortho} · \begin{pmatrix}x \\ y \\ n \\ 1\\ \end{pmatrix} &=
\begin{pmatrix}nx \\ ny \\ n^{2} \\ n\\ \end{pmatrix} \\
\begin{pmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0 \\
? & ? & ? & ? \\
0 & 0 & 1 & 0 \\
\end{pmatrix}
·
\begin{pmatrix}x \\ y \\ n \\ 1\\ \end{pmatrix} &=
\begin{pmatrix}nx \\ ny \\ n^{2} \\ n\\ \end{pmatrix}\\
\end{split}\end{equation}</script><p>我们单独解出变换矩阵的第三行,即:</p>
<script type="math/tex; mode=display">
\begin{pmatrix}
? & ? & ? & ? \\
\end{pmatrix}
·
\begin{pmatrix}x \\ y \\ n \\ 1\\ \end{pmatrix} =
n^{2}</script><p>因为等式结果等于<script type="math/tex">n^{2}</script>,与<script type="math/tex">x</script>和<script type="math/tex">y</script>值并没有关系,所以得出:</p>
<script type="math/tex; mode=display">
\begin{pmatrix}
0 & 0 & A & B \\
\end{pmatrix}
·
\begin{pmatrix}x \\ y \\ n \\ 1\\ \end{pmatrix} =
n^{2}</script><p>可得出(1)式,</p>
<script type="math/tex; mode=display">
An + B = n^{2}</script><h4 id="远平面上任意点的z值变换后不会改变"><a href="#远平面上任意点的z值变换后不会改变" class="headerlink" title="远平面上任意点的z值变换后不会改变"></a>远平面上任意点的z值变换后不会改变</h4><p>因为开始时,我们规定远平面中心点在变换后,仍然不改变,所以我们取远平面的中心点<script type="math/tex">(0, 0, f, 1)^{T}</script>,通过齐次坐标,我们可以将点转换为<script type="math/tex">(0, 0, f^{2}, f)^{T}</script>。</p>
<p>则,</p>
<script type="math/tex; mode=display">
\begin{pmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0 \\
0 & 0 & A & B \\
0 & 0 & 1 & 0 \\
\end{pmatrix}
·
\begin{pmatrix}0 \\ 0 \\ f \\ 1\\ \end{pmatrix} =
\begin{pmatrix}0 \\ 0 \\ f^{2} \\ f\\ \end{pmatrix}\\</script><p>因为等式结果等于<script type="math/tex">f^{2}</script>,与<script type="math/tex">x</script>和<script type="math/tex">y</script>值并没有关系,所以得出:</p>
<script type="math/tex; mode=display">
\begin{pmatrix}
0 & 0 & A & B \\
\end{pmatrix}
·
\begin{pmatrix}0 \\ 0 \\ f \\ 1\\ \end{pmatrix} =
f^{2}</script><p>可得出(2)式,</p>
<script type="math/tex; mode=display">
Af + B = f^{2}</script><p>由(1)式和(2)式解得:</p>
<script type="math/tex; mode=display">
A = n + f \\
B = -nf</script><p>所以,得出:</p>
<script type="math/tex; mode=display">
M_{Persp->Ortho} =
\begin{pmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0 \\
0 & 0 & n + f & -nf \\
0 & 0 & 1 & 0 \\
\end{pmatrix}</script><h3 id="通过正交投影把远平面投影至近平面"><a href="#通过正交投影把远平面投影至近平面" class="headerlink" title="通过正交投影把远平面投影至近平面"></a>通过正交投影把远平面投影至近平面</h3><p>正交投影的变换矩阵在上边我们已经求出,且<script type="math/tex">M_{Persp->Ortho}</script>也已求出,所以我们可以通过矩阵乘法求得<script type="math/tex">M_{Persp}</script>。</p>
<p>即,</p>
<script type="math/tex; mode=display">
\begin{equation}\begin{split}
M_{Persp} &= M_{Ortho} · M_{Persp->Ortho} \\
&=
\begin{pmatrix}
\frac{2}{r - l} & 0 & 0 & -\frac{(r + l)}{r - l} \\
0 & \frac{2}{t - b} & 0 & -\frac{(t + b)}{t - b} \\
0 & 0 & \frac{2}{n - f} & -\frac{(n + f)}{n - f} \\
0 & 0 & 0 & 1 \\
\end{pmatrix}
·
\begin{pmatrix}
n & 0 & 0 & 0\\
0 & n & 0 & 0 \\
0 & 0 & n + f & -nf \\
0 & 0 & 1 & 0 \\
\end{pmatrix} \\
&=
\begin{pmatrix}
\frac{2n}{r - l} & 0 & -\frac{r + l}{r - l} & 0 \\
0 & \frac{2}{t - b} & -\frac{(t + b)}{t - b} & 0 \\
0 & 0 & \frac{n + f}{n - f} & -\frac{2nf}{n - f} \\
0 & 0 & 1 & 0 \\
\end{pmatrix} \\
\end{split}\end{equation}</script>]]></content>
<categories>
<category>Computer Graphics</category>
</categories>
<tags>
<tag>Games101</tag>
</tags>
</entry>
<entry>
<title>Unity的渲染路径(上)</title>
<url>/2021/07/28/ComputerGraphics/%E6%B8%B2%E6%9F%93%E8%B7%AF%E5%BE%84/</url>
<content><![CDATA[<p>在Unity里,<strong>渲染路径(Rendering Path)</strong>决定了光照是如何应用到Unity Shader中的。因此,如果要和光源打交道,我们需要为每个Pass指定它使用的渲染路径,只有这样才能让Unity知道我们想要使用哪种渲染路径,从而把光源和处理后的光照信息都放在一些内置变量中,供我们访问。也就是说,只有为Shader正确地选择和设置了需要的渲染路径,该Shader的光照计算才能被正确执行。</p>
<p>大多数情况下,一个项目只使用一种渲染路径,因此我们可以为整个项目设置渲染时的渲染路径。通过<code>Edit</code>-><code>Project Settings</code>-><code>Graphics</code>-><code>Tier Settings</code>-><code>Rendering Path</code>中选择项目所需的渲染路径。默认情况下,该选项为前向渲染路径。如图:</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20210726194345.png" alt="img"></p>
<p>但有时我们希望可以使用多个渲染路径,例如相机A渲染的物体使用前向渲染路径,而相机B渲染的物体使用延迟渲染路径。这时,我们可以通过在每个相机的渲染路径设置中设置该相机使用的渲染路径,来覆盖Graphics Settings中的设置。如图:</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20210726194633.png" alt="img"></p>
<p>上面的设置中,如果选择了Use Graphics Settings,那么这个相机就会使用Graphics Settings中的设置,否则就会覆盖掉Graphics Settings中的设置。需要注意的是,如果当前的显卡并不支持所选的渲染路径,Unity会自动使用更低一级的渲染路径。例如,如果一个GPU不支持延迟渲染,那么Unity就会使用前向渲染。</p>
<p>完成了上面的设置后,我们就可以在每个Pass中使用标签来指定该Pass使用的渲染路径。这是通过设置Pass的<strong>LightMode</strong>标签实现的。不同类型的渲染路径可能会包含多种标签设置。</p>
<blockquote>
<p>Pass{<br> Tags{ “LightMode” = “ForwardBase”}</p>
</blockquote>
<p>上面的代码告诉Unity,该Pass使用前向渲染路径中的<strong>ForwardBase</strong>路径。而前向渲染路径还有一种路径叫做<strong>ForwardAdd</strong>。下表给出了Pass的LightMode标签支持的渲染路径设置选项。</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">标签名</th>
<th style="text-align:center">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">Always</td>
<td style="text-align:center">不管使用哪种渲染路径,该Pass总是会被渲染,但不会计算任何光照</td>
</tr>
<tr>
<td style="text-align:center">ForwardBase</td>
<td style="text-align:center">用于<strong>前向渲染</strong>。该Pass会计算环境光、最重要的平行光、逐顶点/SH光源和Lightmaps</td>
</tr>
<tr>
<td style="text-align:center">ForwardAdd</td>
<td style="text-align:center">用于<strong>前向渲染</strong>。该Pass会计算额外的逐像素光源,每个Pass对应一个光源。</td>
</tr>
<tr>
<td style="text-align:center">Deferred</td>
<td style="text-align:center">用于<strong>延迟渲染</strong>。该Pass会渲染G缓冲(G-buffer)。</td>
</tr>
<tr>
<td style="text-align:center">ShadowCaster</td>
<td style="text-align:center">把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中</td>
</tr>
<tr>
<td style="text-align:center">MotionVectors</td>
<td style="text-align:center">用于计算每个物体的运动响亮</td>
</tr>
<tr>
<td style="text-align:center">PrepassBase</td>
<td style="text-align:center">用于<strong>遗留的延迟渲染</strong>。该Pass会渲染法线和高光反射的指数部分。</td>
</tr>
<tr>
<td style="text-align:center">PrepassFinal</td>
<td style="text-align:center">用于<strong>遗留的延迟渲染</strong>。该Pass通过合并纹理、光照和自发光来渲染得到最后的颜色。</td>
</tr>
<tr>
<td style="text-align:center">Vertex</td>
<td style="text-align:center">用于<strong>遗留的顶点照明渲染</strong>。当物体未烘焙光照贴图时。应用所有顶点光源。</td>
</tr>
<tr>
<td style="text-align:center">VertexLMRGBM</td>
<td style="text-align:center">用于<strong>遗留的顶点照明渲染</strong>。当物体烘焙光照贴图时,并且在光照贴图采用RGBM编码的平台(PC和控制台)上。</td>
</tr>
<tr>
<td style="text-align:center">VertexLM</td>
<td style="text-align:center">用于<strong>遗留的顶点照明渲染</strong>。当物体烘焙光照贴图时,并且在光照贴图采用双LDR编码(移动平台)的平台上。</td>
</tr>
<tr>
<td style="text-align:center">Meta</td>
<td style="text-align:center">此Pass不用于常规渲染,仅用于光照贴图烘焙或实时全局光照。更多有关信息,请参阅<a href="https://docs.unity3d.com/Manual/MetaPass.html">光照贴图和Meta Pass标签</a></td>
</tr>
</tbody>
</table>
</div>
<p>如果一个Pass没有指定任何渲染路径会有什么问题么?</p>
<p>通俗讲,指定渲染路径是我们和Unity的底层渲染引擎的一次重要沟通。例如,如果我们为一个Pass设置了前向渲染路径的标签,就相当于告诉Unity,我们准备使用前向渲染了。那么Unity就会把光照属性按照前向渲染的流程准备好。随后我们可以通过Unity内置的光照变量来访问这些属性值。如果我们没有指定任何的渲染路径(实际上,Unity中如果使用了前向渲染,又没有为Pass指定任何前向渲染适合的标签,就会被当成一个和顶点照明渲染路径相同的Pass),那么一些光照变量很可能不会被正确赋值,我们计算出的效果也就很有可能是错误的。</p>
<h2 id="前向渲染路径"><a href="#前向渲染路径" class="headerlink" title="前向渲染路径"></a>前向渲染路径</h2><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。利用深度缓冲区来决定一个片元是否可见,如果可见,那么就刷新颜色缓冲区中的值。用伪代码来描述一下前向渲染的大概过程:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Pass {</span><br><span class="line"> for (each primitive in this model) {</span><br><span class="line"> if (failed in depth test) {</span><br><span class="line"> //如果没有通过深度测试,说明该片元不可见,并抛弃此片元</span><br><span class="line"> discard;</span><br><span class="line"> } else {</span><br><span class="line"> //进行光照计算</span><br><span class="line"> float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);</span><br><span class="line"> //更新帧缓冲</span><br><span class="line"> writeFrameBuffer(fragment, color);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来,得到最终的颜色值。</p>
<p>假设场景中有<strong><em>N</em></strong>个物体,每个物体受<strong><em>M</em></strong>个光源影响,那么渲染整个场景一共需要<strong><em>N·M</em></strong>个Pass。所以,如果有大量的逐像素光照,那么需要执行的Pass数量也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。</p>
<h3 id="Unity中的前向渲染"><a href="#Unity中的前向渲染" class="headerlink" title="Unity中的前向渲染"></a>Unity中的前向渲染</h3><p>实际上,一个Pass不仅仅可以用来计算逐像素光照,也可以用来计算逐顶点等其他光照。这取决于光照计算所处流水线阶段,以及计算时使用的数学模型。当我们渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。</p>
<p>在Unity中,前向渲染有三种处理光照的方式:<strong>逐顶点处理</strong>、<strong>逐像素处理</strong>、<strong>球谐函数(Spherical Harmonics,SH)处理</strong>。而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是<strong>重要的(Important)</strong>。如果我们把一个光照的模式设置为Important,意味着我们要告诉Unity,这个光源很重要,需要把它当成一个逐像素光源来处理。我们可以在光源的Light组件中设置这些属性。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20210725171623.png" alt="img"></p>
<p>在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(如,距离该物体远近、光源强度等)对这些光源进行一个重要程度的排序。其中,一定数量的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理(每个顶点最多计算四次点光源),剩下的光源可以按SH方式处理。Unity使用的判断规则如下:</p>
<ul>
<li>场景中最亮的平行光总是按逐像素处理。</li>
<li>渲染模式被设置为<strong>Not Important</strong>的光源,会按逐顶点或者SH处理。</li>
<li>渲染模式被设置为<strong>Important</strong>的光源,会按逐像素处理。</li>
<li>如果根据以上规则得到的逐像素光源少于<strong>Quality Setting</strong>中逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染,按照亮度递减的方式。</li>
</ul>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20210725173351.png" alt="img"></p>
<p>每个对象的渲染如下:</p>
<ul>
<li>Base Pass应用一个逐像素平行光和所有逐顶点/SH光源。</li>
<li>其他逐像素光源在Additional Pass中渲染,每个光源执行一次Pass。</li>
</ul>
<p>光照计算是在Pass中进行。前面提到过,前向渲染有两种Pass:Base Pass和Additional Pass。通常来说,这两种Pass进行的标签和渲染设置以及常规光照计算如图所示。</p>
<p><img src="https://raw.githubusercontent.com/JammeLee/MyPicBed/master/BlogPic/20210725192818.png" alt=""></p>
<ul>
<li>在渲染设置中,我们除了设置了Pass的标签外,还使用了<strong>#pragma multi_compile_fwdbase</strong>这样的编译指令。</li>
<li>Base Pass旁边的注释给出了Base Pass中支持的一些光照特性。例如在Base Pass中,我们可以访问光照纹理(lightmap)。</li>
<li>Base Pass中渲染的平行光默认是支持阴影的(如果开启了光源的阴影功能),而Additional Pass中渲染的光源在默认情况下是没有阴影效果的,即便我们在它的Light组件中设置了有阴影的<strong>Shadow Type</strong>。但我们可以在Additional Pass中使用<strong>#pragma multi_compile_fwdadd_fullshadows</strong>替代<strong>#pragma multi_compile_fwdadd</strong>编译指令,为点光源和聚光灯开启阴影效果,但这需要Unity在内部使用更多的Shader变体。</li>
<li>环境光和自发光也是在Base Pass中计算的。因为对于一个物体来说,环境光和自发光我们只希望计算一次,如果我们在Additional Pass中计算这两种光照,就会造成叠加多次环境光和自发光的情况,这并不是我们想要的。</li>
<li>在Additional Pass的渲染设置中,我们还开启和设置了混合模式。这是因为,我们希望每个Additional Pass可以与上一次的光照结果在帧缓存中进行叠加,从而得到有多个光照的渲染效果。如果我们没有开启和设置混合模式,那么Additional Pass的渲染结果会覆盖掉之前的渲染结果,看起来就好像物体只受该光源的影响。通常情况下,我们选择的混合模式是Blend One One。</li>
<li>对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass(Base Pass也可以定义多次,例如需要双面渲染等情况)以及一个Additional Pass。一个Base Pass仅会执行一次(定义了多个Base Pass的情况除外),而一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Additional Pass。</li>
</ul>
<p>上图给出的光照计算是<strong>通常情况</strong>下我们在没种Pass中进行的计算。实际上,渲染路径的设置用于告诉Unity该Pass在前向渲染路径中的位置,然后底层的渲染引擎会进行相关计算并填充一些内置变量(如_LightColor0等),如何使用这些内置变量进行计算完全取决于开发者的选择。例如,我们完全可以利用Unity提供的内置变量在Base Pass中之进行逐顶点光照;同样,我们也完全可以在Additional Pass中按逐顶点的方式进行光照计算,不进行任何逐像素光照计算。</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">编译指令</th>
<th style="text-align:center">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">multi_compile_fwdbase</td>
<td style="text-align:center">编译PassType.ForwardBase所需的所有变体。这些变体处理不同的光照贴图类型,以及启用或禁用主平行光的阴影</td>
</tr>
<tr>
<td style="text-align:center">multi_compile_fwdadd</td>
<td style="text-align:center">编译PassType.ForwardAdd的变体。这会编译变体以处理定向光、聚光灯或点光类型及其带有 cookie 纹理的变体</td>
</tr>
<tr>
<td style="text-align:center">multi_compile_fwdadd_fullshadows</td>
<td style="text-align:center">与multi_compile_fwdadd相同,但还包括灯光具有实时阴影的能力</td>
</tr>
<tr>
<td style="text-align:center">multi_compile_fog</td>
<td style="text-align:center">扩展为多个变体,以处理不同的雾类型(off/linear/exp/exp2)</td>
</tr>
</tbody>
</table>
</div>
<p>大多数内置的编译指令会产生许多Shader变体。如果确定项目不需要它们,可以使用<strong>#pragma skip_variants</strong>跳过编译其中的一些变体。例如:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#pragma multi_compile_fwdadd</span><br><span class="line">#pragma skip_variants POINT POINT_COOKIE</span><br></pre></td></tr></table></figure>
<p>该指令跳过所有包含<strong>POINT</strong>或<strong>POINT_COOKIE</strong>的变体。</p>
<h3 id="内置的光照变量和函数"><a href="#内置的光照变量和函数" class="headerlink" title="内置的光照变量和函数"></a>内置的光照变量和函数</h3><p>根据我们使用的渲染路径(即Pass标签中Light Mode的值),Unity会把不同的光照变量传递给Shader。</p>
<p>对于前向渲染(即Light Mode为ForwardBase或ForwardAdd)来说,下表给出了我们可以在Shader中访问到的光照变量:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">变量名</th>
<th style="text-align:center">类型</th>
<th style="text-align:center">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">_LightColor0(在UnityLightingCommon.cginc中声明)</td>
<td style="text-align:center">fixed4</td>
<td style="text-align:center">该Pass处理的逐像素光源的颜色</td>
</tr>
<tr>
<td style="text-align:center">_WorldSpaceLightPos0</td>
<td style="text-align:center">float4</td>
<td style="text-align:center">_WorldSpaceLightPos0.xyz是该Pass处理的逐像素光源的位置。如果该光源是平行光,那么_WorldSpaceLightPos0.w是0(因为平行光是向量,不需要矩阵的偏移部分),其他光源类型w值为1</td>
</tr>
<tr>
<td style="text-align:center">unity_WorldToLight(在AutoLight.cginc中声明)</td>
<td style="text-align:center">float4x4</td>
<td style="text-align:center">从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减(attenuation)纹理</td>
</tr>
<tr>
<td style="text-align:center">unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0</td>
<td style="text-align:center">float4</td>
<td style="text-align:center">仅用于ForwardBase Pass。前4个非重要的点光源在世界空间中的位置</td>
</tr>
<tr>
<td style="text-align:center">unity_4LightAtten0</td>
<td style="text-align:center">float4</td>
<td style="text-align:center">仅用于ForwardBase Pass。前四个非重要点光源的衰减因子</td>
</tr>
<tr>
<td style="text-align:center">unity_LightColor</td>
<td style="text-align:center">half4[4]</td>
<td style="text-align:center">仅用于ForwardBase Pass。前四个非重要点光源的颜色。</td>
</tr>
<tr>
<td style="text-align:center">unity_WorldToShadow</td>
<td style="text-align:center">float4x4[4]</td>
<td style="text-align:center">世界空间到阴影空间(World-to-shadow)的矩阵。一个用于聚光灯的矩阵,最多四个用于平行光级联。</td>
</tr>
</tbody>
</table>
</div>
<p>仅可以在前向渲染中使用的内置光照函数:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">函数名</th>
<th style="text-align:center">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">float3 WorldSpaceLightDir(float4 v)</td>
<td style="text-align:center">给定模型空间的顶点位置,返回世界空间中从该点到光源的光照方向。未被归一化。</td>
</tr>
<tr>
<td style="text-align:center">float3 ObjSpaceLightDir(float4 v)</td>
<td style="text-align:center">给定模型空间的顶点位置,返回模型空间中从该点到光源的光照方向。未被归一化。</td>
</tr>
<tr>
<td style="text-align:center">float3 Shade4PointLights(…)</td>
<td style="text-align:center">计算四个点光源的光照,它的参数是已经打包进矢量的光照数据。通常就是上表中的内置变量,如unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0、unity_LightColor、unity_4LightAtten0等。前向渲染通常会使用此函数来计算逐顶点光照。</td>
</tr>
</tbody>
</table>
</div>
<p>参考文献:</p>
<ul>
<li>《Unity Shader入门经验》</li>
<li>Unity官方手册</li>
</ul>
]]></content>
<categories>
<category>Computer Graphics</category>
</categories>
<tags>
<tag>Unity Shader</tag>
</tags>
</entry>
<entry>
<title>重建二叉树</title>
<url>/2019/03/16/Interview/20190316_001/</url>
<content><![CDATA[<h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><p><br></p>
<hr>
<p>    输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。</p>
<hr>
<p><br></p>
<ul>
<li>前序遍历的顺序为:根左右</li>
<li>中序遍历的顺序为:左根右</li>
</ul>
<h3 id="主要思路:递归"><a href="#主要思路:递归" class="headerlink" title="主要思路:递归"></a>主要思路:递归</h3><ol>
<li>根据前序遍历的第一个节点确定根节点</li>
<li>在中序遍历中找到根节点的位置</li>
<li>根左边的就是左子树,右边的就是右子树</li>
<li>构建根和左右子树</li>
<li>递归的进行1、2、3和4</li>
</ol>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Definition for binary tree</span></span><br><span class="line"><span class="comment"> * struct TreeNode {</span></span><br><span class="line"><span class="comment"> * int val;</span></span><br><span class="line"><span class="comment"> * TreeNode *left;</span></span><br><span class="line"><span class="comment"> * TreeNode *right;</span></span><br><span class="line"><span class="comment"> * TreeNode(int x) : val(x), left(NULL), right(NULL) {}</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="class"><span class="keyword">class</span> <span class="title">Solution</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">TreeNode* <span class="title">reConstructBinaryTree</span><span class="params">(vector<<span class="keyword">int</span>> pre,vector<<span class="keyword">int</span>> vin)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(pre.<span class="built_in">size</span>() != vin.<span class="built_in">size</span>())</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"length not equls"</span> << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(pre.<span class="built_in">size</span>() == <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"pre length is 0"</span> << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> length = pre.<span class="built_in">size</span>();</span><br><span class="line"> cout << <span class="string">"pre length is "</span> << length << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//前序遍历的第一个结点是root结点</span></span><br><span class="line"> <span class="keyword">int</span> value = pre[<span class="number">0</span>];</span><br><span class="line"> TreeNode *root = <span class="keyword">new</span> <span class="built_in">TreeNode</span>(value);</span><br><span class="line"> cout << <span class="string">"this root is "</span> << root->val << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//然后再中序遍历中找到根的位置 左边的就是左子树 右边的就是右子树</span></span><br><span class="line"> <span class="keyword">int</span> rootIdx = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span>(;rootIdx < length; rootIdx ++)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span>(vin[rootIdx] == value)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> cout << <span class="string">"the root at "</span> << rootIdx << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(rootIdx >= length)</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"can't find root (value = "</span> << value << <span class="string">") in vin"</span> << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</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">int</span> leftLength = rootIdx;</span><br><span class="line"> <span class="keyword">int</span> rightLength = length - rootIdx - <span class="number">1</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="function">vector<<span class="keyword">int</span>> <span class="title">preLeft</span><span class="params">(leftLength)</span>, <span class="title">inLeft</span><span class="params">(leftLength)</span></span>;</span><br><span class="line"> <span class="function">vector<<span class="keyword">int</span>> <span class="title">preRight</span><span class="params">(rightLength)</span>, <span class="title">inRight</span><span class="params">(rightLength)</span></span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i < length; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span>(i < rootIdx)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//左子树</span></span><br><span class="line"> <span class="comment">//前序遍历的第一个节点是根结点,因此是i+1</span></span><br><span class="line"> preLeft[i] = pre[i + <span class="number">1</span>];</span><br><span class="line"> inLeft[i] = vin[i];</span><br><span class="line"> cout << preLeft[i] << inLeft[i] << <span class="string">" "</span>;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(i > rootIdx)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//右子树</span></span><br><span class="line"> preRight[i - rootIdx - <span class="number">1</span>] = pre[i];</span><br><span class="line"> inRight[i - rootIdx - <span class="number">1</span>] = vin[i];</span><br><span class="line"> cout << preRight[i] << inRight[i] << <span class="string">" "</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> cout << <span class="string">"left tree is"</span> << endl;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i < leftLength; i++)</span><br><span class="line"> {</span><br><span class="line"> cout << preLeft[i] << <span class="string">" "</span> << inLeft[i] << endl;</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> cout << <span class="string">"right tree is"</span> << endl;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i < rightLength; i++)</span><br><span class="line"> {</span><br><span class="line"> cout << preRight[i] << <span class="string">" "</span> << inRight[i] << endl;</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> root->left = <span class="built_in">reConstructBinaryTree</span>(preLeft, inLeft);</span><br><span class="line"> root->right = <span class="built_in">reConstructBinaryTree</span>(preRight, inRight);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> root;</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>]]></content>
<tags>
<tag>Interview</tag>
</tags>
</entry>
<entry>
<title>用两个栈实现队列</title>
<url>/2019/03/17/Interview/20190317_001/</url>
<content><![CDATA[<h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><p><br></p>
<hr>
<p>    用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。</p>
<hr>
<p><br></p>
<h3 id="主要思路:"><a href="#主要思路:" class="headerlink" title="主要思路:"></a>主要思路:</h3><h4 id="最初思路"><a href="#最初思路" class="headerlink" title="最初思路"></a>最初思路</h4><p>    因为栈和队列最明显的区别就是,栈是先进后出,而队列是先进先出。所以为了满足这个要求,我最初的思路是用stack1来做入队操作,用stack2来做出队操作。</p>
<ul>
<li>入队,将元素压入stack1</li>
<li>出队,将stack1倒入stack2,弹出栈顶元素,然后再重新倒入stack1</li>
</ul>
<h4 id="优化思路"><a href="#优化思路" class="headerlink" title="优化思路"></a>优化思路</h4><p>    最初思路完全只是为了解决这个问题的思路,丝毫没有考虑优化方面。那么仔细思考一下,发现是不是有可以优化的地方呢。首先,最初思路有个小细节可以优化一下,那就是在出队时,stack1的栈底元素可以不用压入stack2(只倒入stack1.size( )-1个元素),可直接弹出作为出队元素返回,这样就可以少做一次压栈操作。</p>
<ul>
<li>如果是入队操作,判断stack1是否为空,如果不为空,那么就直接压栈;如果为空,就把stack2倒入stack1中,然后再压栈。</li>
<li>如果是出队操作,判断stack2是否为空,如果不为空,那么就直接出栈;如果为空,就把stack1的元素逐个倒入stack2,把最后一个元素弹出并出队。</li>
</ul>
<h4 id="最后解决方案"><a href="#最后解决方案" class="headerlink" title="最后解决方案"></a>最后解决方案</h4><p>    上边的思路都有一个问题,那就是stack1和stack2之间频繁的倒来倒去,效率就显得不是那么好。所以再换一种更好的思路,我们始终用stack1做入队操作,用stack2做出队操作。</p>
<ul>
<li>入队,将元素压入stack1</li>
<li>出队,先判断stack2是否为空,如果不为空,那么弹出stack2的栈顶元素,如果为空,将stack1元素逐个倒入stack2,把最后一个元素弹出并出队。</li>
</ul>
<p>    这个思路避免了反复“倒”栈,仅在需要时才“倒”一次。</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(<span class="keyword">int</span> node)</span> </span>{</span><br><span class="line"> stack1.<span class="built_in">push</span>(node);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">pop</span><span class="params">()</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> node = <span class="number">-1</span>;</span><br><span class="line"> <span class="comment">//两个栈都为空时,队列为空</span></span><br><span class="line"> <span class="keyword">if</span>(stack1.<span class="built_in">empty</span>() == <span class="literal">true</span> && stack2.<span class="built_in">empty</span>() == <span class="literal">true</span>){</span><br><span class="line"> cout << <span class="string">"queue is null"</span> << endl;</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">if</span>(stack2.<span class="built_in">empty</span>() == <span class="literal">true</span>){</span><br><span class="line"> <span class="comment">//stack2为空 需要把stack1倒入stack2 </span></span><br><span class="line"> <span class="comment">//然后直接把stack1的最后一个元素弹出 可以减少一次压栈操作</span></span><br><span class="line"> <span class="keyword">int</span> size1 = stack1.<span class="built_in">size</span>();</span><br><span class="line"> <span class="keyword">int</span> length = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span>(stack1.<span class="built_in">empty</span>() != <span class="literal">true</span> && length < size1 - <span class="number">1</span>){</span><br><span class="line"> node = stack1.<span class="built_in">top</span>();</span><br><span class="line"> stack1.<span class="built_in">pop</span>();</span><br><span class="line"> stack2.<span class="built_in">push</span>(node);</span><br><span class="line"> cout << <span class="string">"push "</span> << node << <span class="string">" in stack2"</span> << endl;</span><br><span class="line"> length ++;</span><br><span class="line"> }</span><br><span class="line"> node = stack1.<span class="built_in">top</span>();</span><br><span class="line"> stack1.<span class="built_in">pop</span>();</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> node = stack2.<span class="built_in">top</span>();</span><br><span class="line"> stack2.<span class="built_in">pop</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> stack<<span class="keyword">int</span>> stack1;</span><br><span class="line"> stack<<span class="keyword">int</span>> stack2;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>Interview</tag>
</tags>
</entry>
<entry>
<title>旋转数组的最小数字</title>
<url>/2019/03/19/Interview/20190319_001/</url>
<content><![CDATA[<h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><p><br></p>
<hr>
<p>    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。</p>
<hr>
<p><br></p>
<p>    要做这道题,首先要讲一下非减排序数组是什么意思。起初以为只要不是减排序就可以,比如说{1,3,2,5,4}。但实际上并不是这样,找了很多资料都没有详细描述。(这里就要吐槽一下国内的某sdn和辣鸡某度,随便搜一下,全是千篇一律的文章,连标点符号都一字不差。有时候都怀疑,这些人就那么喜欢吃人家嘴里吐出来的东西么。)最后终于凭着自己的猜测和模糊的资料,得知原来非减序数组就是每个元素都大于等于后一个元素。例如,{1,2,3,4,5},{0,1,1,1,1,1}等。</p>
<p>    主要思路:二分查找</p>
<ul>
<li>可以把旋转数组看作是前后两个非减排序的子数组</li>
<li>用两个指针分别指向数组的第一个元素和最后一个元素</li>
<li>求mid=(low + high) / 2</li>
<li>如果mid位于后边数组的话,mid应该小于high,那么最小值应该在mid及自身之前,所以把high提到mid</li>
<li>如果mid位于前边数组的话,mid应该大于low,那么最小值应该在mid及自身之后,所以把low后移到mid</li>
<li>依次循环找到最小值</li>
<li>但是上边讲非减排序数组也可以是{0,1,1,1,1,1},那么它的旋转数组{1,1,1,1,0,1}的low、mid和high是相等的,就出现了问题,此时不知道最小值是再前边数组还是后边数组,所以就只能使用顺序查找来找出最小值了</li>
</ul>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">minNumberInRotateArray</span><span class="params">(vector<<span class="keyword">int</span>> rotateArray)</span> </span>{</span><br><span class="line"> <span class="comment">//可以把旋转数组看作是前后两个非减排序的子数组</span></span><br><span class="line"> <span class="keyword">if</span>(rotateArray.<span class="built_in">size</span>() == <span class="number">0</span>){</span><br><span class="line"> cout << <span class="string">"rotateArray size is 0"</span> << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> mid = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> low = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> high = rotateArray.<span class="built_in">size</span>() - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span>(rotateArray[low] < rotateArray[high]){</span><br><span class="line"> cout << <span class="string">"rotateArray is not rotated"</span> << endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span>(rotateArray[low] >= rotateArray[high]){</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(high - low == <span class="number">1</span>){</span><br><span class="line"> mid = high;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> mid = (low + high) / <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">if</span>(rotateArray[mid] == rotateArray[high] </span><br><span class="line"> && rotateArray[mid] == rotateArray[low]){</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">return</span> <span class="built_in">minOrder</span>(rotateArray, low, high);</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(rotateArray[mid] <= rotateArray[high]){</span><br><span class="line"> <span class="comment">//如果mid位于后边数组的话 mid应该小于high</span></span><br><span class="line"> <span class="comment">//最小值应该在mid及之前 因为mid后边的值一定不小于自身的</span></span><br><span class="line"> <span class="comment">//那么把high提前到mid位置</span></span><br><span class="line"> high = mid;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(rotateArray[mid] >= rotateArray[low]){</span><br><span class="line"> <span class="comment">//如果mid位于前边数组的话 mid应该大于low</span></span><br><span class="line"> <span class="comment">//最小值应该在mid及之后</span></span><br><span class="line"> <span class="comment">//所以把low后移到mid位置</span></span><br><span class="line"> low = mid;</span><br><span class="line"> }</span><br><span class="line"> </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> rotateArray[mid];</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">minOrder</span><span class="params">(vector<<span class="keyword">int</span>> &num, <span class="keyword">int</span> low, <span class="keyword">int</span> high)</span></span>{</span><br><span class="line"> <span class="keyword">int</span> min = num[low];</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i = low + <span class="number">1</span>; i < high; i++){</span><br><span class="line"> <span class="keyword">if</span>(min > num[i]){</span><br><span class="line"> min = num[i];</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> min;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>Interview</tag>
</tags>
</entry>
<entry>
<title>二进制中1的个数</title>
<url>/2019/03/25/Interview/20190325_001/</url>
<content><![CDATA[<h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><p><br></p>
<hr>
<p>    输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。</p>
<hr>
<p><br></p>
<h3 id="通过移位测试每一位"><a href="#通过移位测试每一位" class="headerlink" title="通过移位测试每一位"></a>通过移位测试每一位</h3><p>    首先,很容易能想到的就是通过移位的方法,挨个判断每一位。<br><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">NumberOf1</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(n)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span>(n & <span class="number">1</span> == <span class="number">1</span>)</span><br><span class="line"> {</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> n >>= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><br>    但是这种方法有个致命的缺陷,我们假设通过移位的方式可以获取到其每一位,但并不总是对的。</p>
<h3 id="逻辑右移和算数右移"><a href="#逻辑右移和算数右移" class="headerlink" title="逻辑右移和算数右移"></a>逻辑右移和算数右移</h3><p>    比如说一个有符号位的8位二进制数11001101,逻辑右移就不管符号位,如果移一位就变成01100110。算数右移要管符号位,右移一位变成10100110。</p>
<ul>
<li>逻辑左移=算数左移,右边统一添0</li>
<li>逻辑右移,左边统一添0</li>
<li>算数右移,左边添加的数和符号有关<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">e.g:1010101010,其中[]位是添加的数字</span><br><span class="line">逻辑左移一位:010101010[0]</span><br><span class="line">算数左移一位:010101010[0]</span><br><span class="line">逻辑右移一位:[0]101010101</span><br><span class="line">算数右移一位:[1]101010101</span><br></pre></td></tr></table></figure>
    因此如果输入负数,那么我们的算法简单的判断是不是0来终结,就会进入死循环。</li>
</ul>
<h3 id="避免负数移位的死循环"><a href="#避免负数移位的死循环" class="headerlink" title="避免负数移位的死循环"></a>避免负数移位的死循环</h3><p>    为了负数的时候,避免死循环,我们可以不右移数字n,转而去移动测试位。<br>    那么思考我们的循环结束条件,flag一直左移(乘以2),当超出表示标识范围的时候,我们就可以终止了,但是这样子的话,最高位的符号没有测试,因此要单独测试,同时由于会溢出,我们的flag需要用long来标识。<br><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">NumberOf1</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> flag = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(flag <= INT_MAX)</span><br><span class="line"> {</span><br><span class="line"> cout <<n <<<span class="string">" & "</span> <<flag <<<span class="string">" == "</span><<(n & flag) <<endl;</span><br><span class="line"> <span class="keyword">if</span>((n & flag) != <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> flag <<= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 由于循环终结,我们使用的是flag <= INT_MAX</span></span><br><span class="line"> <span class="comment">// 因此前面的循环只会执行31次</span></span><br><span class="line"> <span class="keyword">if</span>((n & flag) != <span class="number">0</span>) <span class="comment">// 最后测试符号位</span></span><br><span class="line"> {</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure><br>    继续考虑,会发现循环条件还可以精简一下,如果数据发生溢出的话,会被截断,截断以后就是0。<br><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">NumberOf1</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> flag = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(flag != <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> cout <<n <<<span class="string">" & "</span> <<flag <<<span class="string">" == "</span><<(n & flag) <<endl;</span><br><span class="line"> <span class="keyword">if</span>((n & flag) != <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> flag <<= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> cout <<n <<<span class="string">" & "</span> <<flag <<<span class="string">" == "</span><<(n & flag) <<endl;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure><br>    这种方法循环的次数刚好就是二进制的位数,比如32位就循环32次。那么,有没有方法可以做到整数中有多少个1就循环多少次呢。</p>
<h3 id="整数中有几个1就循环几次——lowbit优化"><a href="#整数中有几个1就循环几次——lowbit优化" class="headerlink" title="整数中有几个1就循环几次——lowbit优化"></a>整数中有几个1就循环几次——lowbit优化</h3><p>    我们分析n与n-1两个数的差别</p>
<ul>
<li>如果n!=0,那么其二进制位中至少有一个1</li>
<li>如果n的最低位是1(奇数),那么n-1正好把这个最低位的1变成0,其他位不变</li>
<li>如果n的最低位是0(偶数),那么假设其右起第一个1位于m位,即m位后面全是0,那么n-1的第m位由1变成0,而第m位后面的所有0均变成1,m位之前的所有位保持不变。<br>    因此通过分析发现:<br>    把一个整数n减去1,再和原来的整数做与运算,会把该整数最右边一个1变成0,那么该整数有多少个1,就会进行多少次与运算。</li>
</ul>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">NumberOf1</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span>(n)</span><br><span class="line"> {</span><br><span class="line"> count++;</span><br><span class="line"> n = n & (n - <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h3 id="STL-bitset"><a href="#STL-bitset" class="headerlink" title="STL-bitset"></a>STL-bitset</h3><p>    STL中用bitset来方便地管理一系列的bit位,而不用程序员自己来写代码。<br>    bitset除了可以访问指定下标的bit位以外,还可以把它们作为一个整数来进行某些统计。</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">NumberOf1</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="function">bitset<32> <span class="title">bit</span><span class="params">(n)</span></span>;</span><br><span class="line"> <span class="keyword">return</span> bit.<span class="built_in">count</span>();</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>
]]></content>
<tags>
<tag>Interview</tag>
</tags>
</entry>
<entry>
<title>斐波那契数列</title>
<url>/2019/04/14/Interview/20190414_001/</url>
<content><![CDATA[<h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><p><br></p>
<hr>
<p>    大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。</p>
<hr>
<p><br></p>
<h3 id="通过递归"><a href="#通过递归" class="headerlink" title="通过递归"></a>通过递归</h3><p>    首先,很容易就想到了递推公式<script type="math/tex">f(n)=f(n-1)+f(n-2)</script>。因此我们马上就能想到使用递归方法,代码如下:<br><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">Fibonacci</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">if</span>(n <= <span class="number">1</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> n;</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="keyword">return</span> <span class="built_in">Fibonacci</span>(n - <span class="number">1</span>) + <span class="built_in">Fibonacci</span>(n - <span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><br>    但实际上递归方法的时间复杂度是以n的指数方式增长的。</p>
<h3 id="递归展开-迭代方法"><a href="#递归展开-迭代方法" class="headerlink" title="递归展开-迭代方法"></a>递归展开-迭代方法</h3><p>    因此我们只能用最普通的方法,将递推公式进行展开。<br><figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span></span></span><br><span class="line"><span class="class">{</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">int</span> <span class="title">Fibonacci</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">if</span>(n <= <span class="number">1</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">return</span> n;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">long</span> one = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">long</span> two = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">long</span> res = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">2</span>; i <= n; i++)</span><br><span class="line"> {</span><br><span class="line"> res = one + two;</span><br><span class="line"></span><br><span class="line"> one = two;</span><br><span class="line"> two = res;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><br>    但这还不是最快的方法,下面介绍一种时间复杂度是<script type="math/tex">O(logn)</script>的方法。</p>
<h3 id="O-logn-求Fibonacci数列"><a href="#O-logn-求Fibonacci数列" class="headerlink" title="$O(logn)$求Fibonacci数列"></a>$O(logn)$求Fibonacci数列</h3><p>    在介绍此方法前,先介绍一个数学公式:</p>
<script type="math/tex; mode=display">
\begin{bmatrix}
f(n)&f(n-1)\\
f(n-1)&f(n-2)\\
\end{bmatrix}
=
\begin{bmatrix}
1&1\\
1&0\\
\end{bmatrix}^{n-1}</script><p>    有了此公式,求<script type="math/tex">f(n)</script>就只要求出矩阵<script type="math/tex">\begin{bmatrix}1&1\\1&0\\\end{bmatrix}</script>的n-1次方,因为矩阵的n-1次方的第一行第一列就是f(n)。这个公式使用数学归纳法不难得出。那么问题又来了,从0开始到n-1,实际上还是需要n次运算,并不比上面的方法快。但是我们可以利用同底数幂法则:</p>
<script type="math/tex; mode=display">
a^n = a^{\frac{n} {2}}*a^{\frac{n} {2}}</script><p>或</p>
<script type="math/tex; mode=display">
a^n = a^{\frac{n-1} {2}}*a^{\frac{n-1} {2}}</script><p>    要求n次方,现求出<script type="math/tex">{\frac{n} {2}}</script>次方后,再平方即可。<br>    用这种方式实现时,首先要定义一个2*2的矩阵。并定义好矩阵的乘法以及幂运算。</p>
<p>    接下来再介绍一种推理公式:</p>
<script type="math/tex; mode=display">