2D圖形如何運動模擬出3D效果
一.先看看實現效果圖
(左邊的2d圖片如何運動出右邊3d的效果)
引言:
對於這個題目,真的很尷尬,不知道取啥,就想了這個題目,涵蓋範圍很廣,很抽象,算是通用知識點吧。想要了解下面幾個問題的,可以看看。
①2D圖形如何運動出3D空間的效果。
②3D物體如何渲染成2D圖形到螢幕上。
③Unity中模型到世界,世界到相機,相機到螢幕的關係。
④如何通過矩陣進行各種風騷(旋轉,縮放,平移,投影等)的變換操作。
二.應用知識
①向量
②矩陣,矩陣變換規則
③透視投影
三.實現
問題:圖形不斷變換,通過簡化,圖形的本質是由頂點組合而成,因此,可以簡化為頂點不斷變換。不斷意思大概就是每隔一段時間變換,我們這裡
再簡化,可以簡化為變換一次。即問題可以不斷簡化如圖
對於“頂點變換“,頂點,即是向量,根據矩陣的相關知識,我們可以瞭解到,變換矩陣可以使向量得到指定的變換。因此就是“頂點通過矩陣變換”。
最後,問題的本質即為 頂點和矩陣的之間的互動 即可。
1)頂點
頂點,當然是擁有x,y,z三個分量, 根據矩陣變換規則,我們想要使用矩陣對向量進行變換,需要多一個維度,且第四個分量為1。至於具體原因這裡不加詳述,
詳情可見:https://blog.csdn.net/zl_gsyy/article/details/73278742。
即,需要設定一個擁有四維向量 Vector4.cs
class Vector4 { public double a, b, c, d; public Vector4() { } public Vector4(double a,double b,double c, double d) { this.a = a; this.b = b; this.c = c; this.d = d; } public Vector4(Vector4 v) { this.a = v.a; this.b = v.b; this.c = v.c; this.d = v.d; } }
2)矩陣
因為我們需要變換的是4維向量(頂點,可以看做1X4的矩陣),所以再根據矩陣變換規則,我們需要設定矩陣維4x4的。
定義 Matrix4x4.cs
class Matrix4x4 { double[,] ts; public Matrix4x4() { ts = new double[4, 4]; } public Matrix4x4(Matrix4x4 m) { for(int i=0;i<4;i++) { for(int j=0;j<4;j++) { ts[i, j] = m[i, j]; } } } public double this[int x,int y] { get{ return ts[x, y]; } set { ts[x, y] = value; } } /// <summary> /// 用這個矩陣變換一個四維向量,返回另外一個向量 /// </summary> /// <param name="v"></param> /// <returns></returns> public Vector4 Mul(Vector4 v) { Vector4 vNew = new Vector4(); vNew.a = ts[0, 0] * v.a + ts[1, 0] * v.b + ts[2, 0] * v.c + ts[3, 0] * v.d; vNew.b = ts[0, 1] * v.a + ts[1, 1] * v.b + ts[2, 1] * v.c + ts[3, 1] * v.d; vNew.c = ts[0, 2] * v.a + ts[1, 2] * v.b + ts[2, 2] * v.c + ts[3, 2] * v.d; vNew.d = ts[0, 3] * v.a + ts[1, 3] * v.b + ts[2, 3] * v.c + ts[3, 3] * v.d; return vNew; } }
根據矩陣變換規則,如果需要變換多次,多次變換可以通過相乘合併為一個矩陣,比如某頂點需要先平移,再縮放,可以平移*縮放=合併矩陣。
所以需要在Matrix4x4.cs 中 增加一個矩陣*矩陣得到另外一個矩陣 方法。
public Matrix4x4 Mul(Matrix4x4 m) { Matrix4x4 newM = new Matrix4x4(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { for (int k = 0; k < 4; k++) { newM[i, j] += this[i, k] * m[k, j]; } } } return newM; }
3)模擬變換過程
因為我們這邊直接使用點來變換顯示,不夠直觀,可以使用由3個頂點組成的三角形,來模擬測試效果。所以,這裡再定義一個三角形類 Triangles.cs .
直接使用矩陣對該Triangles進行變換,即是對三個頂點進行變換。
class Triangles { public Vector4 A, B, C; private Vector4 a, b, c; //臨時變數使用 public Triangles(Vector4 a,Vector4 b,Vector4 c) { A =this.a= new Vector4(a); B =this.b= new Vector4(b); C =this.c= new Vector4(c); } /// <summary> /// 變換過後原始頂點保留(變換針對原始資料) /// </summary> /// <param name="m"></param> public void Transform(Matrix4x4 m) { this.a = m.Mul(this.A); this.b= m.Mul(this.B); this.c = m.Mul(this.C); } #region 系統內建方法(這邊瞭解一下就行) /// <summary> /// 應用windows窗體應用程式內部方法,根據三個頂點畫出三角形 /// </summary> /// <param name="e"></param> public void Draw(System.Drawing.Graphics e) { e.TranslateTransform(300, 300);//不然會太靠左上角。Windowform的內部功能 e.DrawLines(new Pen(Color.Red, 2f), Get2DPointFArr()); } PointF[] Get2DPointFArr() { PointF[] points = new PointF[4]; points[0] = Get2DPointF(this.a); points[1] = Get2DPointF(this.b); points[2] = Get2DPointF(this.c); points[3] = Get2DPointF(this.a); //我們這邊需要畫第四個與第一個頂點一樣,系統需求這樣做 return points; } PointF Get2DPointF(Vector4 v) { PointF point = new PointF(); point.X = (float)(v.a / v.d); point.Y = (float)(v.b / v.d); return point; } #endregion }
通過以上,我們可以實現通過變換矩陣(平移,縮放,旋轉)來實現簡單變換了。當然通過某種變換的時候首先需要明白對應的哪個矩陣。參考https://www.cnblogs.com/u3ddjw/p/10282186.html
======================================================正式進入正題=====================================================================
4)如何實現3D空間變換效果
①首先,我們先宣告一個三角形,並且給出其三個頂點的座標。
Triangles triangles; //注意這邊螢幕左上角為(0,0)座標原點 Vector4 v1 = new Vector4(0, -0.5, 0, 1); Vector4 v2 = new Vector4(0.5f, 0.5, 0, 1); Vector4 v3 = new Vector4(-0.5f, 0.5f, 0, 1); triangles = new Triangles(v1, v2, v3);
因為我們這裡給出座標位置是比例座標(模型座標),這種大小當然很小,與螢幕(1320,1080)差距很大,所以我們需要把它放大道合適的大小。假設就放大250倍剛好是我們需要的大小。就需要縮放矩陣了,通過https://www.cnblogs.com/u3ddjw/p/10282186.html 查詢到縮放矩陣 。
故,我宣告scale =250,
//模型到世界 m_scale = new Matrix4x4(); m_scale[0, 0] = scale; //scale =250,這個數值可以任意調節,你可以試試改變這個大小,加強得到縮放的效果的記憶。 m_scale[1, 1] = scale; m_scale[2, 2] = scale; m_scale[3, 3] = 1; triangles.Transform(m_scale);
最終一張正常的圖出現:
②為了模擬出直觀的3D效果,再來實現把這個三角形進行旋轉
參考上面方法找到旋轉矩陣表示式,為了觀察直觀,我們每隔一小段時間增加2個角度並顯示出來。我們增加個Timer(就是可以實現
間隔時間呼叫方法的工具類,系統一般都會自帶,比如unity中update)
float a = 2; /// <summary> /// 每隔0.02s呼叫一次 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer1_Tick(object sender, EventArgs e) { a++; double angle = a / 360 * Math.PI; m_rotation[0, 0] = Math.Cos(angle); m_rotation[0, 2] = Math.Sin(angle); m_rotation[1, 1] = 1; m_rotation[2, 0] = -1 * Math.Sin(angle); m_rotation[2, 2] = Math.Cos(angle); m_rotation[3, 3] = 1; //因為矩陣變換規則得知多次變換可以先將變換矩陣相乘 縮放矩陣*旋轉矩陣 var newTriangle = m_scale.Mul(m_rotation); triangles.Transform(newTriangle); this.Invalidate(); //重繪,必須寫上 }
得到結果:
上圖總是變寬變宰,下面底部線條沒有感覺在3D空間中變換。這不是真正意義3D變換, 僅僅是模型到世界。
③透視投影變換
假設右邊的圈就是我們需要拍攝的攝像機空間的點,還需要進行透視投影變換到螢幕。故,我們還需要世界到攝像機,
要讓他從世界到攝像機, 所以我們還需要架設一個攝像機去拍攝他,假設這個模型在z軸為0的位置,攝像機假設到z為負的位置,對於模型而言
在攝像機空間上實際上做了位置平移。所以 我們需要一個可以轉換為攝像機空間的矩陣(平移矩陣)。
同上找到平移矩陣,並初始化一個平移矩陣,平移多少位置?還是先假設250(這邊可以自行修改資料調節看看效果)。
故在初始化函式中,
//View世界到相機 m_view = new Matrix4x4(); m_view[0, 0] = 1; m_view[1, 1] = 1; m_view[2, 2] = 1; m_view[3, 2] = 1 * scale; m_view[3, 3] = 1;
目前效果上還是沒有變換的,雖然平移了攝像機,但是我們沒有 真正的做投影變換 。關於投影矩陣,還是參考https://www.cnblogs.com/u3ddjw/p/10282186.html,
找到投影矩陣。繼續在初始化函式中並實現
//Projection 相機到螢幕 m_projection = new Matrix4x4(); m_projection[0, 0] = 1; m_projection[1, 1] = 1; m_projection[2, 2] = 1; m_projection[2, 3] = 1.0f / scale;
在timer.cs 中的 timer1_Tick 每隔一段時間函式中補充為
/// <summary> /// 每隔0.02s呼叫一次 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer1_Tick(object sender, EventArgs e) { a+=3; double angle = a / 360 * Math.PI; m_rotation[0, 0] = Math.Cos(angle); m_rotation[0, 2] = Math.Sin(angle); m_rotation[1, 1] = 1; m_rotation[2, 0] = -1 * Math.Sin(angle); m_rotation[2, 2] = Math.Cos(angle); m_rotation[3, 3] = 1; //因為矩陣變換規則得知多次變換可以先將變換矩陣相乘 縮放矩陣*旋轉矩陣 var newTriangle = m_scale.Mul(m_rotation); newTriangle = newTriangle.Mul(m_view); newTriangle = newTriangle.Mul(m_projection); triangles.Transform(newTriangle); this.Invalidate(); //重繪,必須寫上 }
這樣,最終我們看到了第一張圖的效果圖,完全的3D空間的效果。現在是具有透視變換的三角形在螢幕上不停的旋轉,繞著y軸。
四.拓展
①可以嘗試改變各個變換矩陣中的值,比如平移矩陣(世界到相機)的值,來看看效果。
private void trackBar1_Scroll(object sender, EventArgs e) { m_view[3, 2] = (sender as TrackBar).Value; } private void trackBar2_Scroll(object sender, EventArgs e) { double value = (sender as TrackBar).Value; m_projection[2, 3] = 1.0f / value; }
②不用三角形,自己嘗試五角星,四邊形之類的變換,效果各種拉風,但是本質還是都是 頂點通過矩陣的變換 。