-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathSoftRenderForm.cs
1091 lines (866 loc) · 35.3 KB
/
SoftRenderForm.cs
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
using SortRenderWithCSharp;
using SortRenderWithCSharp.TextureMode;
using SortRenderWithCSharp.Lights;
public class SoftRenderForm : Form {
// 帧缓冲,后备缓冲区,用于双缓冲时将该后备缓冲区交换到前置缓冲区
Bitmap screenBuffer;
// 帧缓冲的绘图画面,用来清除帧缓冲
Graphics screenBufferGraphics;
// 用于贴在图元上的2D贴图
Bitmap texture2D;
// 法线贴图
Bitmap normalTexture;
// 前置缓冲区(也就是当前显示的部分)
Graphics g;
// 深度缓冲区
float[,] ZBuffer;
// 是否开启深度写入(默认为否)
bool IsZWritting;
// 是否开启深度测试(默认为否)
bool IsZTest;
// 是否开启计算颜色的函数
bool fragmentShaderOn;
// 是否开启光照渲染(同时也决定了是否对顶点的法线进行计算)
bool LightingOn;
// 平行光光源的方向,这里将默认方向设为从摄像机观察方向指向摄像机
Vector3 MainLightDirection = new Vector3(1, 0, 0);
Color01 lightColor = Color01.White; // 平行光颜色
// 光源集合
List<Light> lights = new List<Light>();
// 平行光
DirectionalLight directionalLight;
// 点光源
PointLight pointLight;
// 聚光灯
SpotLight spotLight;
// 是否开启面剔除功能
bool cullingFaceOn = true;
CullingFaceMode cullingFaceMode = CullingFaceMode.Back;
// 屏幕宽高度(同时也作分辨率使用)
private int screenHeight = 500, screenWidth = 500;
// 摄像机类
private Camera camera;
private long lastUpdateTime;
// 当前绘制的mesh
private Mesh currentDrawMesh;
// 天空盒
private CubeMap skyBox;
private Mesh skyBoxMesh;
private bool drawingSkyBox = false;
/// <summary>
/// 初始化
/// </summary>
public SoftRenderForm() {
Height = screenHeight;
Width = screenWidth;
// 初始化前置缓冲区
g = this.CreateGraphics();
// 初始化屏幕缓冲区
screenBuffer = new Bitmap(screenWidth+1, screenHeight+1);
screenBufferGraphics = Graphics.FromImage(screenBuffer);
// 初始化深度缓冲区
ZBuffer = new float[screenWidth+1,screenHeight+1];
// 初始化所有光源
directionalLight = new DirectionalLight(MainLightDirection, Color01.White);
lights.Add(directionalLight);
//pointLight = new PointLight(Vector3.Zero, 7, new Color01(0, 0, 1f, 1f));
//lights.Add(pointLight);
//spotLight = new SpotLight(15f, 7f, new Vector3(0, 0, 1), new Color01(1, 0, 0, 1), new Vector3(0, 0, -3));
//lights.Add(spotLight);
// 开启深度测试
IsZTest = true;
// 开启深度写入
IsZWritting = true;
// 开启光照渲染
LightingOn = true;
// 开启逐像素计算
fragmentShaderOn = true;
// 初始化摄像机
camera = new Camera();
// 读取贴图
texture2D = new Bitmap("C:\\Users\\Administrator\\Desktop\\29126173.bmp");
// 读取法线贴图
normalTexture = new Bitmap(256, 256);
//normalTexture = new Bitmap("D:\\UnityInstance\\Shader Collection\\Assets\\Textures\\Chapter7\\Brick_Normal.bmp");
// 读取OBJ文件
SpaceShip = OBJLoader.LoadOBJ("../../enemry_spaceship.obj");
cube = OBJLoader.LoadOBJ("../../cube.obj");
//cube = new Cube();
sphere = OBJLoader.LoadOBJ("../../sphere.obj");
skyBoxMesh = cube;
// 初始化天空盒
skyBox = new CubeMap(
// front
new Bitmap("../../frontImage.png"),
// back
new Bitmap("../../backImage.png"),
// left
new Bitmap("../../leftImage.png"),
// right
new Bitmap("../../rightImage.png"),
// top
new Bitmap("../../upImage.png"),
// bottom
new Bitmap("../../downImage.png")
);
StartRender();
}
protected override void OnActivated(EventArgs e) {
base.OnActivated(e);
}
protected override void OnKeyDown(KeyEventArgs e) {
base.OnKeyDown(e);
Input.Instance.OperateKeybordEvent(e);
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
Input.Instance.OperateMouseEvent(e);
}
private void UpdateInput() {
float cameraSpeed = 5.0f;
if (Input.Instance.isKeyDown) {
switch (Input.Instance.keyCode) {
case Keys.Left:
spotLight.position.X -= cameraSpeed * Time.deltaTime;
break;
case Keys.Right:
spotLight.position.X += cameraSpeed * Time.deltaTime;
break;
case Keys.Up:
spotLight.position.Z += cameraSpeed * Time.deltaTime;
break;
case Keys.Down:
spotLight.position.Z -= cameraSpeed * Time.deltaTime;
break;
case Keys.W:
camera.rotation.X += cameraSpeed * Time.deltaTime;
break;
case Keys.S:
camera.rotation.X -= cameraSpeed * Time.deltaTime;
break;
case Keys.A:
camera.rotation.Y -= cameraSpeed * Time.deltaTime;
break;
case Keys.D:
camera.rotation.Y += cameraSpeed * Time.deltaTime;
break;
case Keys.Z:
camera.rotation.Z += cameraSpeed * Time.deltaTime;
break;
case Keys.X:
camera.rotation.Z -= cameraSpeed * Time.deltaTime;
break;
}
}
}
/// <summary>
/// 清除后备缓冲区
/// </summary>
private void ClearScreenBuffer() {
// 清除颜色缓冲区
screenBufferGraphics.Clear(System.Drawing.Color.Black);
// 清除深度缓冲区
Array.Clear(ZBuffer,0,ZBuffer.Length);
}
/// <summary>
/// 在后备缓冲区中,对点(x,y)画上一个color的颜色
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="color"></param>
private void DrawPixel(int x, int y, Color01 color) {
screenBuffer.SetPixel(x, y, color.ToColor());
}
private void DrawPixel(int x, int y, Color color) {
screenBuffer.SetPixel(x, y, color);
}
/// <summary>
/// 显示帧率
/// </summary>
public void ShowFPS() {
Console.Clear();
// dateTime.now.ticks/10000 =
// 从0001 /01/01/00:00开始到现在的毫秒数
long nowMs = DateTime.Now.Ticks / 10000;
// 两次窗口更新之间经历的毫秒数
long deltaTime = nowMs - lastUpdateTime;
Console.WriteLine("delta"+deltaTime);
if(lastUpdateTime!=0)
Time.deltaTime = deltaTime / 1000f;
// 更新lastUpdateTime变量(即最后一次更新时间)
lastUpdateTime = nowMs;
// deltaTime表示ms转s,帧数表示1秒中内能更新几次
float fps = (float)1 / ((float)deltaTime / 1000);
Console.WriteLine(String.Format("帧率: {0:G4} FPS", fps));
if (screenBufferGraphics != null)
screenBufferGraphics.DrawString(String.Format("帧率: {0:G4} FPS", fps), new Font("Verdana", 12), new SolidBrush(Color.Red), new PointF(1.0f, 1.0f));
}
/// <summary>
/// 开始渲染
/// </summary>
private void StartRender() {
// 1000ms / 60表示 共60帧,每帧渲染一次
System.Timers.Timer timer = new System.Timers.Timer(0.0001f);
timer.Elapsed += new System.Timers.ElapsedEventHandler(Render);
timer.AutoReset = true;
timer.Enabled = true;
timer.Start();
}
int angel = 0;
// 每一帧渲染图形的方法
public void Render(object sender, System.Timers.ElapsedEventArgs args) {
lock (screenBuffer) {
// 清空后置缓冲区
ClearScreenBuffer();
// 显示FPS
ShowFPS();
#region 绘制区域
// 先绘制天空盒
cullingFaceOn = false;
drawingSkyBox = true;
DrawSkyBox();
drawingSkyBox = false;
cullingFaceOn = true;
//angel = (angel + 1)%180;
//DrawMesh(cube,SoftRenderDrawMode.Triangles);
//DrawSpaceShip();
//DrawMesh(sphere,Vector3.Zero,Vector3.One,new Vector3(angel,angel,angel),SoftRenderDrawMode.Triangles_FUN);
#endregion
// 更新输入
UpdateInput();
// 重置按钮
Input.Instance.Reset();
// 交换缓冲,将后备缓冲交换到前置缓冲
g.DrawImage(screenBuffer, 0, 0);
}
}
#region 光栅化线段与三角图元
/// <summary>
/// 对应任意一种情况的光栅化线段的方法,
/// 是Bresenham朴素算法的优化版,不出现浮点运算
/// </summary>
/// <param name="x1"></param>
/// <param name="y1"></param>
/// <param name="x2"></param>
/// <param name="y2"></param>
/// <param name="color"></param>
private void DrawLine(Vertex v1,Vertex v2) {
int x1 = (int)v1.pos.X;
int y1 = (int)v1.pos.Y;
int x2 = (int)v2.pos.X;
int y2 = (int)v2.pos.Y;
// 线段起点与终点在x轴上的距离(可能为负)
int dx = x2 - x1;
// 线段起点和终点在y轴上的距离(可能为负)
int dy = y2 - y1;
// 计算从x1到x2的方向是正方向还是负方向
// 1<<1 = 2; 0<<1 = 0
int stepX = ((dx > 0 ? 1 : 0) << 1) - 1;
// 计算从y1到y2的方向是正方向还是负方向
int stepY = ((dy > 0 ? 1 : 0) << 1) - 1;
dx = Math.Abs(dx); dy = Math.Abs(dy);
// 误差
int eps = 0;
if (dx > dy) {
int y = y1;
// 当x轴距离差更大时,将x作为自增变量
for (int x = x1; x != x2; x += stepX) {
float t = (float)(x - x1) / (float)(x2 - x1);
// 当前顶点
Vertex vertex = Vertex.LerpVertexData(v1, v2, t);
vertex.pos.X = x;
vertex.pos.Y = y;
float z = vertex.pos.Z;
// 对当前像素进行深度测试
if (IsZTest)
if (!ZTest(x, y, z)) continue;
// 透视插值矫正
float realZ = 1.0f / z;
vertex.u *= realZ; // 变回原来的u
vertex.v *= realZ; // 变回原来的v
float u = vertex.u;
float v = vertex.v;
// 对顶点颜色进行插值
Color01 color = Color01.LerpColor(v1.color, v2.color, t);
// 对纹理贴图进行采样
Color01 textureColor = Texture.Tex2D(texture2D, u, v);
Color01 finalColor = textureColor * color;
if (drawingSkyBox) {
finalColor = CubeMap.TexCube(skyBox,vertex.modelSpacePos);
DrawPixel(x, y, finalColor);
} else if (fragmentShaderOn) {
// 对所有光源进行着色器计算,然后将所有光源的结果叠加起来
// 即该片元的颜色由所有光源决定
finalColor = Color01.Black;
foreach (var light in lights) {
finalColor += FragmentShader(vertex,light);
}
DrawPixel(x, y, finalColor);
} else
DrawPixel(x, y, finalColor);
// 增量误差
eps += dy;
// 误差大于0.5,那么y++
if ((eps << 1) >= dx) {
y += stepY;
eps -= dx;
}
}
} else {
int x = x1;
// 当y轴距离差更大时,将y作为自增变量
for (int y = y1; y != y2; y += stepY) {
float t = (float)(y - y1) / (float)(y2 - y1);
Color01 color = Color01.LerpColor(v1.color,v2.color,t);
DrawPixel(x, y, color);
// 增量误差
eps += dx;
if ((eps << 1) >= dy) {
x += stepX;
eps -= dy;
}
}
}
}
/// <summary>
/// 光栅化平顶三角形(基于扫描线法)
///
/// 假设平顶三角形三个顶点A(x1,y1),B(x2,y2),C(x3,y3),
/// 那么其中AB是平顶,分别在AC/BC上取xLeft,xRight两个点,
/// 自增y,向下逐步画扫描线,即可光栅化一个平顶三角形
/// </summary>
/// <param name="x1"></param>
/// <param name="y1"></param>
/// <param name="x2"></param>
/// <param name="y2"></param>
/// <param name="x3"></param>
/// <param name="y3"></param>
/// <param name="color"></param>
private void DrawTopFlatTriangle(Vertex v1,Vertex v2,Vertex v3) {
float x1 = v1.pos.X;
float y1 = v1.pos.Y;
float x2 = v2.pos.X;
float y2 = v2.pos.Y;
float x3 = v3.pos.X;
float y3 = v3.pos.Y;
// 令y自增,并在平顶三角形的左右两条边上取两个点
// 画线段
for (float y = y1; y <= y3; y++) {
//if (y3 - y1 == 0 || y3 - y2 == 0) continue;
// 使用直线方程(两点式)获得左端点和右端点
// 需要注意此方法不适用于垂直x轴和y轴的直线
// 左端点
float xLeft = (y - y1) * (x3 - x1) / (y3 - y1) + x1;
// 根据y进行插值
float t = (y - y1) / (y3 - y1);
Vertex vLeft = Vertex.LerpVertexData(v1,v3,t);
vLeft.pos.X = xLeft;
vLeft.pos.Y = y;
// 右端点
float xRight = (y - y2) * (x3 - x2) / (y3 - y2) + x2;
Vertex vRight = Vertex.LerpVertexData(v2,v3,t);
vRight.pos.X = xRight;
vRight.pos.Y = y;
DrawLine(vLeft,vRight);
}
}
/// <summary>
/// 绘制平底三角形
///
/// 设有平底三角形ABC,则A(x1,y1),B(x2,y2),C(x3,y3),
/// 则BC是底,要在AB,AC上找两个端点对三角形进行绘制
/// </summary>
private void DrawBottomFlatTriangle(Vertex v1,Vertex v2,Vertex v3) {
float x1 = v1.pos.X ;
float y1 = v1.pos.Y;
float x2 = v2.pos.X;
float y2 = v2.pos.Y;
float x3 = v3.pos.X;
float y3 = v3.pos.Y;
// 从上到下扫描
for (float y = y1; y <= y2; y++) {
// 处理除0错误
if (y2 - y1 == 0 || y3 - y1 == 0) continue;
// 利用y进行插值
float t = (y - y1) / (y2 - y1); // 插值系数
// 左端点
float xLeft = (y - y1) * (x2 - x1) / (y2 - y1) + x1;
Vertex vLeft = Vertex.LerpVertexData(v1,v2,t);
vLeft.pos.X = xLeft;
vLeft.pos.Y = y;
// 右端点
float xRight = (y - y1) * (x3 - x1) / (y3 - y1) + x1;
Vertex vRight = Vertex.LerpVertexData(v1,v3,t);
vRight.pos.X = xRight;
vRight.pos.Y = y;
DrawLine(vLeft,vRight);
}
}
/// <summary>
/// 绘制普通三角形
///
/// 思路是:
/// 将三个顶点中,
/// 从y值排在中间的顶点开始,画一条平行于x轴的平行线,
/// 这样,就把一个普通三角形分为一个平底三角形,一个平顶三角形了
///
/// 假设普通三角形ABC,有顶点A(x1,y1),B(x2,y2),C(x3,y3),
/// 这里假设顶点B的y值排在AC中间即 y1 小于 y2 小于 y3
///
/// 那么在直线AC上找到一点y值与B相等的点D,画直线BD,就将普通三角形分割成平底和平顶三角形了
/// 分割后,ABD是平底三角形,BDC是平顶三角形
///
/// </summary>
private void DrawTriangle(Vertex v1,Vertex v2,Vertex v3) {
// 对三个顶点的y值升序排序,
if (v3.pos.Y < v1.pos.Y) {
// 交换顶点1/3
Vertex temp = v1; v1 = v3; v3 = temp;
}
if (v2.pos.Y < v1.pos.Y) {
// 交换顶点1/2
Vertex temp = v1; v1 = v2; v2 = temp;
}
// 经过上面的操作,现在(x1,y1)的y值是最小的,现在将(x3,y3)变成最大的
if (v2.pos.Y > v3.pos.Y) {
// 交换顶点2/3
Vertex temp = v2; v2 = v3; v3 = temp;
}
// 现在三个顶点的顺序按照y值升序排列分别是A(x1,y1)<B(x2,y2)<C(x3,y3)
if (v1.pos.Y == v2.pos.Y) {
// 平顶三角形
DrawTopFlatTriangle(v1,v2,v3);
} else if (v2.pos.Y == v3.pos.Y) {
// 平底三角形
DrawBottomFlatTriangle(v1, v2, v3);
} else {
// 普通三角形
// 在AC上找到一点D(使用两点式),其y值与B点相同
float Dy = v2.pos.Y;
float Dx = (Dy - v1.pos.Y) * (v3.pos.X - v1.pos.X) / (v3.pos.Y - v1.pos.Y) + v1.pos.X;
// 根据DY,在AC上对点D进行插值
float t = (Dy - v1.pos.Y) / (v3.pos.Y - v1.pos.Y); // 插值系数
Vertex vD = Vertex.LerpVertexData(v1,v3,t);
vD.pos.X = Dx;
vD.pos.Y = Dy;
// 将B点和D点按照x值升序排列(保证B点在D点左边)
if (Dx < v2.pos.X) { // 如果D点在B点左边,那么交换他们,使B点成为D点,D点成为B点
Vertex temp = vD; vD = v2; v2 = temp;
}
// 绘制平底三角形ABD
DrawBottomFlatTriangle(v1,v2,vD);
// 绘制平顶三角形BDC
DrawTopFlatTriangle(v2, vD, v3);
}
}
#endregion
#region MVP变换
/// <summary>
/// 在顶点进行屏幕映射之前,
/// 判断该顶点在不在摄像机的视椎体内,
/// 这个操作即为CVV剔除or裁剪,这里只考虑剔除操作
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
public bool CVVCutting(Vector3 pos) {
if (pos.X > pos.W || pos.X < -pos.W)
return true;
if (pos.Y > pos.W || pos.Y < -pos.W)
return true;
if (pos.Z > pos.W || pos.Z < -pos.W)
return true;
return false;
}
/// <summary>
/// 将一个处于裁剪空间内的顶点(还未进行齐次除法),
/// 变换到NDC坐标空间下,然后再将该顶点映射到屏幕空间上
/// </summary>
/// <param name="vertex"></param>
public Vertex ScreenMapping(Vertex v) {
Vertex vertex = v.DeepyCopy();
// 进行齐次除法
vertex.pos.X /= vertex.pos.W;
vertex.pos.Y /= vertex.pos.W;
// 进行齐次除法后,此时顶点坐标被转换到NDC坐标下
// 这里的NDC坐标采用OpenGL风格,
// 即其中各分量范围如: 1<= x,y,z <= 1
// 现要将x,y分量映射到屏幕上,需要将这两个分量
// 缩放到[0,1]区间,并乘于各自屏幕分辨率
// 这里可以进行经典的 *0.5+0.5的操作
// 将x坐标映射到屏幕上
vertex.pos.X = (vertex.pos.X * 0.5f + 0.5f) * screenWidth;
// 将y坐标映射到屏幕上
vertex.pos.Y = (vertex.pos.Y * 0.5f + 0.5f) * screenHeight;
// 透视插值矫正,这里将顶点的uv值乘于1/z,
// 这样在光栅化的时候插值就是均匀的
float reciprocalW = 1.0f / vertex.pos.W;
vertex.pos.Z = reciprocalW;
vertex.u *= vertex.pos.Z;
vertex.v *= vertex.pos.Z;
return vertex;
}
/// <summary>
/// 在屏幕上绘制一个三角形图元
/// </summary>
/// <param name="v1"></param>
/// <param name="v2"></param>
/// <param name="v3"></param>
public void DrawPrimitive(Vertex v1,Vertex v2,Vertex v3,Matrix4x4 mMatrix,Matrix4x4 vMatrix,Matrix4x4 pMatrix) {
// 将各顶点作为列矩阵进行MVP变换
//v1.pos = mvp * v1.modelSpacePos;
//v2.pos = mvp * v2.modelSpacePos;
//v3.pos = mvp * v3.modelSpacePos;
// 先将顶点变换到观察空间下
Matrix4x4 mvMatrix = vMatrix * mMatrix;
v1.pos = mvMatrix * v1.modelSpacePos;
v2.pos = mvMatrix * v2.modelSpacePos;
v3.pos = mvMatrix * v3.modelSpacePos;
// 判断是否通过面剔除
if (!FaceCulling(v1,v2,v3) && cullingFaceOn) {
return;
}
// 将所有顶点变换到裁剪空间下
v1.pos = pMatrix * v1.pos;
v2.pos = pMatrix * v2.pos;
v3.pos = pMatrix * v3.pos;
// 判断有顶点是否应该被裁剪
if (CVVCutting(v1.pos) ||
CVVCutting(v2.pos) ||
CVVCutting(v3.pos)) return;
// 对顶点进行屏幕映射操作
Vertex vertex1 = ScreenMapping(v1);
Vertex vertex2 = ScreenMapping(v2);
Vertex vertex3 = ScreenMapping(v3);
// 使用这个三个顶点的屏幕坐标绘制三角形图元
DrawTriangle(vertex1, vertex2, vertex3);
}
/// <summary>
/// 根据传递的Vertex数组和triangleIndex数组来绘制3D图形
/// triangleIndex表示顶点的顺序,triangleIndex[1] = 1,
/// 表示第二个顶点是Vertex数组的第二个元素
/// 绘制的规则是,
/// 每三个成一个三角形进行绘制
/// </summary>
/// <param name="vertices"></param>
/// <param name="triangle"></param>
/// <param name="mvp"></param>
/// <param name="drawMode">默认为连续三角形绘制</param>
public void DrawElement(Mesh mesh, Matrix4x4 mMatrix, Matrix4x4 vMatrix, Matrix4x4 pMatrix, SoftRenderDrawMode drawMode= SoftRenderDrawMode.Triangles) {
int[] triangle = mesh.triangles;
Vertex[] vertices = mesh.vertices;
if (triangle.Length % 3 != 0) return;
switch (drawMode) {
case SoftRenderDrawMode.Triangles:
for (int i = 0; i < triangle.Length; i += 3) {
Vertex v1 = vertices[triangle[i]];
Vertex v2 = vertices[triangle[i + 1]];
Vertex v3 = vertices[triangle[i + 2]];
DrawPrimitive(v1, v2, v3, mMatrix,vMatrix,pMatrix);
}
break;
case SoftRenderDrawMode.Triangles_FUN:
Vertex v_init = vertices[triangle[0]];
for (int i = 1; i+1 < triangle.Length; i+=2) {
Vertex v2 = vertices[triangle[i]];
Vertex v3 = vertices[triangle[i + 1]];
DrawPrimitive(v_init, v2, v3, mMatrix, vMatrix, pMatrix);
}
break;
case SoftRenderDrawMode.TRIANGLE_STRIP:
Vertex vfirset = vertices[triangle[0]];
Vertex vsecond = vertices[triangle[1]];
Vertex vthird = vertices[triangle[2]];
DrawPrimitive(vfirset,vsecond,vthird, mMatrix, vMatrix, pMatrix);
for (int i=3;i<triangle.Length;i++) {
Vertex v1 = vertices[triangle[i - 2]];
Vertex v2 = vertices[triangle[i - 1]];
Vertex v3 = vertices[triangle[i]];
DrawPrimitive(v1,v2,v3, mMatrix, vMatrix, pMatrix);
}
break;
}
}
#endregion
#region 深度写入/深度测试
/// <summary>
/// 对一个像素点开启深度测试,如果通过深度测试,那么将该物体的深度写入深度缓冲区
///
/// 深度测试的方法如下:
///
/// 将该片元与深度缓冲区中当前片元的深度进行比较,
/// 如果要绘制的像素深度小于深度缓冲区同样位置的深度,那么绘制该像素,并将新的深度写入深度缓冲区
/// 如果要绘制的像素深度大于深度缓冲区同样位置的深度,那么不绘制该像素
///
/// 因为在屏幕映射时,将Z改为1/Z,所以上面的大小关系要进行翻转
/// </summary>
/// <param name="px"></param>
/// <param name="py"></param>
/// <param name="pz">当前要绘制的像素的深度</param>
/// <returns></returns>
public bool ZTest(int px,int py,float pz) {
// 如果要绘制的片元的深度小于深度缓冲区中的片元,就进行绘制,并进行深度写入
// PS:深度缓冲区中当前的深度是1/Z(这个Z是视角空间下的Z),所以大小关系反转
if (pz >= ZBuffer[px,py]) {
// 进行深度写入
if (IsZWritting)
ZBuffer[px, py] = pz;
return true;
}
return false;
}
#endregion
#region 面剔除测试
public bool FaceCulling(Vertex v1,Vertex v2,Vertex v3) {
// v2指向v1的向量
Vector3 p1 = v2.pos - v1.pos;
// v3指向v1的向量
Vector3 p2 = v3.pos - v2.pos;
// 获得p1和p2的叉积
Vector3 normal = Vector3.Cross(p1,p2);
// 获得视空间内从顶点望向观察方向的向量
Vector3 viewDir = -v1.pos + Vector3.Zero;
// 如果该叉积结果和观察方向一致,那么说明该面面朝摄像机,即为逆时针,
// 否则说明该面背朝摄像机,为顺时针
switch (cullingFaceMode) {
case CullingFaceMode.Back:
if (Vector3.Dot(normal, viewDir) > 0) {
return true;
}
break;
case CullingFaceMode.Front:
if (Vector3.Dot(normal, viewDir) < 0) {
return true;
}
break;
case CullingFaceMode.All:
return true;
}
return false;
}
#endregion
#region 绘制更多图元
Mesh SpaceShip;
Mesh cube;
Mesh sphere;
/// <summary>
/// 绘制天空盒需要特殊处理
/// </summary>
public void DrawSkyBox() {
currentDrawMesh = skyBoxMesh;
// 天空盒的顶点坐标即为世界坐标,无需进行变换
Matrix4x4 modelMatrix = Matrix.GetScaleMatrix(1,1,1);
// 构建V矩阵
Matrix4x4 viewMatrix = Matrix.GetScaleMatrix(1,1,1);
//Matrix4x4 viewMatrix = camera.GetViewMatrix();
//Vector3 tempForwardDir = Matrix.GetRotateMatrix(camera.rotation.X, camera.rotation.Y, camera.rotation.Z) * camera.forwardDir;
//return Matrix.GetViewMatrix(position, position + tempForwardDir, upDir);
//Matrix4x4 viewMatrix = Matrix.GetViewMatrix(Vector3.Zero, Vector3.Zero + tempForwardDir, camera.upDir);
// 取消观察矩阵的位移,即将观察矩阵第四列置空
//for (int i = 0; i < 3; i++) viewMatrix.value[i, 3] = 0;
// 构建P矩阵
//Matrix4x4 projectionMatrix = camera.GetProjectionMatrix();
Matrix4x4 projectionMatrix = Matrix.GetScaleMatrix(1,1,1);
// 构建MVP矩阵
//Matrix4x4 MVPMatrix = projectionMatrix * viewMatrix * modelMatrix;
//// 给每个顶点引用一份MVP矩阵
//foreach (int i in mesh.triangles) {
// Vertex v = mesh.vertices[i];
// v.mMatrix = modelMatrix;
// v.vMatrix = viewMatrix;
// v.pMatrix = projectionMatrix;
//}
currentDrawMesh.mMatrix = modelMatrix;
currentDrawMesh.vMatrix = viewMatrix;
currentDrawMesh.pMatrix = projectionMatrix;
DrawElement(currentDrawMesh, modelMatrix, viewMatrix, projectionMatrix);
}
public void DrawMesh(Mesh mesh,Vector3 position,Vector3 scale,Vector3 rotation,SoftRenderDrawMode softRenderDrawMode) {
// 构建M矩阵
Matrix4x4 modelMatrix = Matrix.GetModelMatrix(position, rotation, scale);
// 构建V矩阵
Matrix4x4 viewMatrix = camera.GetViewMatrix();
// 构建P矩阵
Matrix4x4 projectionMatrix = camera.GetProjectionMatrix();
// 构建MVP矩阵
//Matrix4x4 MVPMatrix = projectionMatrix * viewMatrix * modelMatrix;
// 给每个顶点引用一份MVP矩阵
//foreach (int i in mesh.triangles) {
// Vertex v = mesh.vertices[i];
// v.mMatrix = modelMatrix;
// v.vMatrix = viewMatrix;
// v.pMatrix = projectionMatrix;
//}
mesh.mMatrix = modelMatrix;
mesh.vMatrix = viewMatrix;
mesh.pMatrix = projectionMatrix;
DrawElement(mesh, modelMatrix,viewMatrix,projectionMatrix,softRenderDrawMode);
}
/// <summary>
/// 绘制立方体
/// </summary>
public void DrawMesh(Mesh mesh,SoftRenderDrawMode drawMode=SoftRenderDrawMode.Triangles) {
currentDrawMesh = mesh;
// 坐标/旋转与缩放
angel = (angel + 1) % 720;
Vector3 rotation = new Vector3(angel, angel, angel);
Vector3 scale = new Vector3(1, 1, 1)*0.5f;
Vector3 worldPosition = new Vector3(0, 0, 0);
// 构建M矩阵
Matrix4x4 modelMatrix = Matrix.GetModelMatrix(worldPosition, rotation, scale);
// 构建V矩阵
Matrix4x4 viewMatrix = camera.GetViewMatrix();
// 构建P矩阵
Matrix4x4 projectionMatrix = camera.GetProjectionMatrix();
// 构建MVP矩阵
//Matrix4x4 MVPMatrix = projectionMatrix * viewMatrix * modelMatrix;
//// 给每个顶点引用一份MVP矩阵
//foreach (int i in mesh.triangles) {
// Vertex v = mesh.vertices[i];
// v.mMatrix = modelMatrix;
// v.vMatrix = viewMatrix;
// v.pMatrix = projectionMatrix;
//}
mesh.mMatrix = modelMatrix;
mesh.vMatrix = viewMatrix;
mesh.pMatrix = projectionMatrix;
DrawElement(mesh, modelMatrix, viewMatrix, projectionMatrix, drawMode);
}
#region 光照模型
/// <summary>
/// 基于Phong光照模型的光照
/// </summary>
/// <param name="vertex"></param>
/// <returns></returns>
public Color01 LightingWithPhong(Vertex vertex) {
return Color01.White;
}
/// <summary>
/// 基于半兰伯特光照模型的光照
/// </summary>
/// <param name="vertex"></param>
/// <returns></returns>
public Color01 LightingWithLambert(Vertex vertex,Mesh mesh,Light light) {
// 将顶点的法线变换到世界空间下,对于一个只包含旋转变换的变换矩阵,他是正交矩阵
// 使用m矩阵变换法线(仅适用于只发生旋转的物体)
Vector3 worldNormal = mesh.mMatrix * vertex.normal;
worldNormal.Normlize();
// 获得顶点当前所在世界坐标
Vector3 worldPos = mesh.mMatrix * vertex.modelSpacePos;
// 根据平行光方向及当前法线方向,
// 计算当前像素的辐照度
Vector3 lightDirection = light.GetDirection(worldPos);
// 使用半兰伯特表达式计算辐照度
float radiance = Vector3.Dot(worldNormal, lightDirection) * 0.5f + 0.5f;
// 获得贴图颜色
//Color01 albedo = Texture.Tex2D(texture2D, vertex.u, vertex.v);
Color01 albedo = Color01.White;
// 使用Blinn-Phong模型计算高光反射
// 获得世界坐标下的视角方向
Vector3 worldViewDir = camera.position - worldPos;
// 计算half向量
Vector3 h = (worldViewDir + lightDirection);
h.Normlize();
// 光泽度
float gloss = 32f;
float spec = Math.Max(0, Vector3.Dot(h, worldNormal));
// 计算高光反射
Color01 specular = lightColor * (float)Math.Pow(spec, gloss);
// 计算漫反射光照
Color01 diffuse = albedo * radiance * light.lightColor;
Color01 finalColor = diffuse+specular;
// 计算光源衰减
finalColor *= light.GetAtten(worldPos);
finalColor.A = 1;
return finalColor;
}
/// <summary>
/// 法线映射
/// </summary>
/// <param name="vertex"></param>
/// <returns></returns>
public Color01 NormalMappingWithLambert(Vertex vertex,Mesh mesh,Light light) {
// 从法线贴图中取出法线(切线空间下的)
Color01 UndecryptedNormal = Texture.Tex2D(normalTexture, vertex.u, vertex.v);
// 把法线从[0,1]区间变回到[-1,1]区间
Vector3 normal = new Vector3(
UndecryptedNormal.R * 2 - 1,
UndecryptedNormal.G * 2 - 1,
UndecryptedNormal.B * 2 - 1
);