webgl筆記-1.模型視圖矩陣和投影矩陣


最近在學習WebGL技術的過程中,我補充了一些原本了解甚少的計算機圖形學知識。如果有同學和我一樣,沒有系統學過計算機圖形學就接觸了3D圖形編程,而對不少略為艱深的概念有困惑,希望這些筆記能夠幫助你。

模型矩陣

我們必須考慮,當空間中點的位置會發生變化的時候,其坐標如何變化。考慮三種基本的變換:平移、旋轉和縮放。

“變換”的含義就是,將點的初始位置的坐標P映射到平移、旋轉、縮放后的位置坐標P’,即:

$$\begin{bmatrix}x\\y\\z\end{bmatrix}\rightarrow\begin{bmatrix}{x}'\\{y}'\\{z}'\end{bmatrix}$$

平移變換是最簡單的變換:

$$\begin{bmatrix}{x}'\\{y}'\\{z}'\end{bmatrix}=\begin{bmatrix}x\\y\\z\end{bmatrix}+\begin{bmatrix}t_{x}\\t_{y}\\t_{z}\end{bmatrix}$$

旋轉變換有一些復雜,先看在二維平面上的旋轉變換:

Untitled-1

很容易得到:

$$\begin{matrix}x'=x\cos\theta-y\sin\theta\\y'=x\sin\theta+y\sin\theta\end{matrix}$$

矩陣形式的表達更加簡潔,后面大多使用這種形式:

$$\begin{bmatrix}x'\\ y'\end{bmatrix}=\begin{bmatrix}\cos\theta & -\sin\theta\\ \sin\theta & \cos\theta\end{bmatrix}\begin{bmatrix}x\\ y\end{bmatrix}$$

推廣到三維空間中:
點繞z軸旋轉:

$$\begin{bmatrix}x'\\ y'\\ z'\end{bmatrix}=\begin{bmatrix}\cos\theta & -\sin\theta & 0\\ \sin\theta & \cos\theta & 0\\ 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x\\ y\\ z\end{bmatrix}$$

點繞x軸旋轉:

$$\begin{bmatrix}x'\\ y'\\ z'\end{bmatrix}=\begin{bmatrix}1 & 0 & 0\\ 0 & \cos\theta & -\sin\theta\\ 0 & \sin\theta & \cos\theta\end{bmatrix}\begin{bmatrix}x\\ y\\ z\end{bmatrix}$$

點繞y軸旋轉:

$$\begin{bmatrix}x'\\ y'\\ z'\end{bmatrix}=\begin{bmatrix}\cos\theta & 0 & -\sin\theta\\ 0 & 1 & 0\\ \sin\theta & 0 & \cos\theta\end{bmatrix}\begin{bmatrix}x\\ y\\ z\end{bmatrix}$$

繞指定的任意軸旋轉變換是由幾個繞坐標軸旋轉變換和平移變換效果疊加而成的,后文會有詳細敘述。

縮放變換也比較簡單:

$$\begin{bmatrix}x'\\ y'\\ z'\end{bmatrix}=\begin{bmatrix}s_{x} & 0 & 0\\ 0 & s_{y} & 0\\ 0 & 0 & s_{z} \end{bmatrix}\begin{bmatrix}x\\ y\\ z\end{bmatrix}$$

總結一下:平移變換,變換后點坐標等於初始位置點坐標加上一個平移向量;而旋轉變換和縮放變換,變換后點坐標等於初始位置點坐標乘以一個變換矩陣。

$$P'=P+T, P'=R\cdot P, P'=S\cdot P$$

齊次坐標這天才的發明,允許平移變換也表示成初始位置點坐標左乘一個變換矩陣的形式。齊次坐標使用4個分量來表示三維空間中的點,前三個分量和普通坐標一樣,第四個分量為1。

$$\begin{bmatrix}x\\ y\\ z\end{bmatrix}\rightarrow \begin{bmatrix}x\\ y\\ z\\ 1\end{bmatrix}$$

平移變換巧妙地表示為:

$$\begin{bmatrix}x'\\ y'\\ z'\\ 1\end{bmatrix}=\begin{bmatrix}1 & 0 & 0 & t_{x}\\ 0 & 1 & 0 & t_{y}\\ 0 & 0 & 1 & t_{z}\\ 0 & 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x\\ y\\ z\\ 1\end{bmatrix}$$

旋轉變換(以繞z軸旋轉為例)和縮放變換相應為:

$$\begin{bmatrix}x'\\ y'\\ z'\\ 1\end{bmatrix}=\begin{bmatrix}\cos\theta & -\sin\theta & 0 & 0\\ \sin\theta & \cos\theta & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x\\ y\\ z\\ 1\end{bmatrix}$$

$$\begin{bmatrix}x'\\ y'\\ z'\\ 1\end{bmatrix}=\begin{bmatrix}s_{x} & 0 & 0 & 0\\ 0 & s_{y} & 0 & \\ 0 & 0 & s_{z} & 0\\ 0 & 0 & 0 & 1\end{bmatrix}\begin{bmatrix}x\\ y\\ z\\ 1\end{bmatrix}$$

綜上,在齊次坐標下三種基本變換實現了形式上的統一,這種形式的統一意義重大。

$$P'=T\cdot P, P'=R\cdot P, P'=S\cdot P$$

矩陣有一個性質:

$$M\cdot (A\cdot B)=(M\cdot A)\cdot B$$

考慮一個點,先進行了一次平移變換,又進行了一次旋轉變換,結合上面矩陣的性質,可知變換后的點P’為:

$$P=R\cdot(T\cdot P)=(R\cdot T)\cdot P$$

旋轉矩陣和平移矩陣的乘積R·T也是一個4×4的矩陣,這個矩陣代表了一次平移變換和一次旋轉變換效果的疊加;如果這個點還要進行變換,只要將新的變換矩陣按照順序左乘這個矩陣,得到的新矩陣能夠表示之前所有變換效果的疊加,將最初的點坐標左乘這個矩陣就能得到一系列變換后最終的點坐標,這個矩陣稱為“模型矩陣”。一個模型矩陣乘以另一個模型矩陣得到的還是一個模型矩陣,表示先進行右側模型矩陣代表的變換,再進行左側模型矩陣代表的變換這一過程的效果之和,因此模型矩陣的乘法又可以認為是閉合的。
模型矩陣之所以稱之為“模型矩陣”,是因為該矩陣與點的位置沒有關系,僅僅包含了一系列變換的信息。而在三維世界中,一個模型里所有的頂點往往共享同一個變換,對應同一個模型矩陣,比如拋在空中的一個木塊,運轉機器的一個齒輪。

之前說到,考慮一個物體繞指定軸旋轉,如以下這個變換:繞着過頂點(x,y,z)方向為(a,b,c)的軸旋轉角度θ,利用多個變換的疊加構建繞任意軸旋轉的變換矩陣。
首先將頂點(x,y,z)平移到原點,繞x軸旋轉角度p使指定的旋轉軸在x-z平面上,繞y軸旋轉角度q使指定的旋轉軸與z軸重合,繞指定旋轉軸(也就是z軸)旋轉角度θ,繞y軸旋轉角度-q,繞x軸旋轉角度-p,將頂點平移到向量(x,y,z),p和q的值由方向(a,b,c)決定。綜上,變換矩陣為:

$$R\begin{pmatrix}\begin{bmatrix}x\\ y\\ z\end{bmatrix} & \begin{bmatrix}a\\ b\\ c\end{bmatrix} & \theta\end{pmatrix}=T\begin{pmatrix}x\\ y\\ z\end{pmatrix}\cdot R_{x}(-p)\cdot R_{y}(-q)\cdot R_{z}(\theta)\cdot R_{y}(q)\cdot R_{x}(p)\cdot T\begin{pmatrix}-x\\ -y\\ -z\end{pmatrix}$$

因此在處理圍繞非坐標軸旋轉的模型時,根據指定的旋轉參數可以直接按照上述公式生成按照指定軸旋轉的旋轉矩陣,參加模型矩陣的構建。

齊次坐標還有一個優點,能夠區分點和向量:在普通坐標里,點和向量都是由三個分量組成的,表示位置的點坐標(2,3,4)和表示方向的向量(2,3,4)沒有區別。而在齊次坐標中,第四個分量可以區分它們,點坐標的第四個分量為1,而向量坐標第四個分量為0。比如,平移一個點是有意義的,能夠得到平移后的點坐標;而平移一個向量是沒有意義的,方向不會因為平移而改變。

以上,我們已經了解到模型矩陣可以存儲一個模型空間位置變化的信息,在生成三維動畫每一幀的過程中,我們首先計算每個模型的模型矩陣,然后將最初的模型的每一頂點坐標都左乘該模型矩陣,得到這一幀表示的時刻(模型已經經過多次變換)該模型每一頂點的坐標。上面說的“幀”並不狹義地指屏幕的兩次刷新時間的短暫間隔中屏幕上呈現的圖像,而是指在這幅圖像所描繪的整個三維空間的這個瞬間的所有頂點的位置。

來看個具體的例子:一個繞z軸勻速螺旋勻速上升的立方體,在某一幀中(即在這一幀對應的時刻t下),其向z軸正方向平移的長度和繞z軸旋轉的角度分別為:

$$t_{z}=t\cdot v_{t},\theta_{z}=t\cdot \omega_{t}$$

則模型矩陣(注意上文齊次坐標下的基本變換矩陣)為:

$$mMatrix=R_{z}(\theta_{z})\cdot T(0,0,t_{z})=\begin{bmatrix}\cos\theta_{z} & -\sin\theta_{z} & 0 & 0\\ \sin\theta_{z} & \cos\theta_{z} & 0 & 0 \\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\end{bmatrix}\begin{bmatrix}1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & t_{z}\\ 0 & 0 & 0 & 1\end{bmatrix}$$

產生這一幀時,只需要計算一次模型矩陣,再將立方體中8個頂點坐標分別左乘該矩陣,就可以得到經過變換后8個頂點的坐標。當一個模型頂點數量增加到上百甚至上千個,模型變換的步驟數也增加到幾十步時,模型矩陣的作用就很明顯了:如果沒有齊次坐標(也當然沒有模型矩陣),對每個頂點都需要一步一步地變換:平移的時候加上一個向量,旋轉的時候左乘一個矩陣,才能得到變換后的頂點坐標;而模型變換只需要計算一次模型矩陣(當然也是一步一步的),然后每個頂點左乘模型矩陣就可以直接得到變換后的坐標了。

視圖矩陣

在模型矩陣中,我們關心的是空間中的點在經歷變換后在世界坐標系下的位置。事實上,我們更加關心空間中的點相對於觀察者的位置。最簡單的方案是將觀察者置於原點處,面向z軸(或x軸、y軸)正半軸,那么空間中的點在世界坐標系下的位置就是其相對於觀察者的位置。
觀察者的位置和方向會變化,看上去就好像整個世界的位置和方向發生變化了一樣,所以解決的方案很簡單,將世界里的所有模型看作一個大模型,在所有模型矩陣的左側再乘以一個表示整個世界變換的模型矩陣,就可以了。這個表示整個世界變換的矩陣又稱為“視圖矩陣”,因為他們經常一起工作,所以將視圖矩陣乘以模型矩陣得到的矩陣稱為“模型視圖矩陣”。模型視圖矩陣的作用是:乘以一個點坐標,獲得一個新的點坐標,獲得的點坐標表示點在世界里變換,觀察者也變換后,點相對於觀察者的位置。

視圖矩陣同樣也可以分為平移、旋轉和縮放,視圖矩陣是將觀察者視為一個模型,獲得的觀察者在世界中變換的模型矩陣的逆矩陣。

觀察者平移了(tx,ty,tz),視圖矩陣如下,可以看出如果將視圖矩陣看作整個世界的模型矩陣,相當於整個世界平移了(-tx,-ty,-tz)。

$$\begin{bmatrix}1 & 0 & 0 & t_{x}\\ 0 & 1 & 0 & t_{y}\\ 0 & 0 & 1 & t_{z}\\ 0 & 0 & 0 & 1 \end{bmatrix}^{-1}=\begin{bmatrix}1 & 0 & 0 & -t_{x}\\ 0 & 1 & 0 & -t_{y}\\ 0 & 0 & 1 & -t_{z}\\ 0 & 0 & 0 & 1 \end{bmatrix}$$

觀察者繞z軸旋轉了角度θ,視圖矩陣如下,相當於整個世界繞z軸旋轉了-θ度。

$$\begin{bmatrix}\cos\theta & -\sin\theta & 0 & 0\\ \sin\theta & \cos\theta & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{bmatrix}^{-1}=\begin{bmatrix}\cos\theta & \sin\theta & 0 & 0\\ -\sin\theta & \cos\theta & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\end{bmatrix}$$

觀察者在三個方向等比例縮小了s倍,視圖矩陣如下,相當於整個世界放大了s倍。

$$\begin{bmatrix}s_{x} & 0 & 0 & 0\\ 0 & s_{y} & 0 & 0\\ 0 & 0 & s_{z} & 0\\ 0 & 0 & 0 & 1\end{bmatrix}^{-1}=\begin{bmatrix}1/s_{x} & 0 & 0 & 0\\ 0 & 1/s_{y} & 0 & 0\\ 0 & 0 & 1/s_{z} & 0\\ 0 & 0 & 0 & 1\end{bmatrix}$$

觀察者縮小的情形可能會引起困惑:如果人和貓咪的眼睛在同一個位置,人看到的世界和一只貓咪看到的世界應當是一樣尺寸的,這和上述視圖矩陣的情形矛盾;但是直覺告訴我,如果你喝了縮小葯水,你應該會覺得整個世界在膨脹,就像視圖矩陣所表現的那樣。解答是這樣:如果在計算機上模擬觀察者喝了縮小葯水的情形,在屏幕上看到整個世界是膨脹的,因為在那個虛擬的三維空間中,計算機屏幕這個“窗口”也隨你(觀察者)縮小。
視圖矩陣實際上就是整個世界的模型矩陣,這給我一點啟發:一個模型可能由多個較小的子模型組成,模型自身有其模型矩陣,而子模型也有自己的局部模型矩陣。考慮一輛行駛中的汽車的輪胎,其模型視圖矩陣是局部模型矩陣(描述輪胎的旋轉)左乘汽車的模型矩陣(描述汽車的行駛)再左乘視圖矩陣得到的。

投影矩陣

模型視圖矩陣的作用是確定某一幀中,空間里每個頂點的坐標,而投影矩陣則將這些頂點坐標映射到二維的屏幕上,即:

$$\begin{bmatrix}x\\ y\\ z\\ 1\end{bmatrix}\rightarrow \begin{bmatrix}x'\\ y'\end{bmatrix}$$

最主要的有兩種投影方式,正射投影透視投影。前者用於精確制圖,如工業零件側視圖或建築物頂視圖,從屏幕上就可以量測平行於屏幕的線段長度;后者用於模擬視覺,遠處的物體看上去較小。下圖中,空間中的同一個矩形,正射投影后仍然是矩形,而透視投影后則變成了梯形。

正射投影(投影面和相機空間):

透視投影(投影面和相機空間):

三維世界的顯示中,屏幕模擬了一個窗口,你透過這個窗口觀察“外面”的世界。你的屏幕是有邊緣的(除非你有一個球形的房間,內壁全是屏幕),因此你僅僅能觀察到那個世界的一部分,即“相機空間”。相機空間的左、右、上、下邊界是受限於屏幕的邊緣,同時也設定前、后邊界,因為你很難看清太近或太遠的東西。在正射投影中,相機空間是一個規則的立方體,而在透視投影中則是一個方台體。
三維模型可能在不同的顯示器上展現,因此投影的過程中不該將顯示器參數加入進來,而是將空間中的點投影到一個規范的顯示器中。另外,透視投影中的z值並不是毫無用處,它可以用來表示頂點的“深度”:如果三維空間中的兩個不同頂點投影到平面上時重合了,那么將顯示深度較淺的頂點。

定義一個規范的視窗區域(CCV),為x,y,z都處在區間[-1,1]之間的邊長為2的立方體。x和y坐標值用來線形拉伸到到實際屏幕上,而z值存儲了“深度”。而投影的過程就是將三維空間中的點從相機空間映射到CCV中。
正射投影非常簡單,直接將矩形的相機空間線形壓縮到CCV中即可。采取頂視圖,相機空間的左右邊界為 $x_{left}$ 和 $x_{right}$ :

Untitled-21

簡單的線形成比例關系:

$$\frac{x-x_{left}}{x_{right}-x_{left}}=\frac{x'-(-1)}{1-(-1)}$$

$$x'=\frac{2}{x_{right}-x_{left}}\cdot x-\frac{x_{right}+x_{left}}{x_{right}-x_{left}}$$

推廣到y軸和z軸:

$$\begin{bmatrix}x'\\ y'\\ z'\\ 1\end{bmatrix}=\begin{bmatrix}\frac{2}{x_{right}-x{left}} & 0 & 0 & -\frac{x_{right}+x_{left}}{x_{right}-x_{left}}\\ 0 & \frac{2}{y_{top}-y_{bottom}} & 0 & -\frac{y_{top}+y_{bottom}}{y_{top}-y_{bottom}}\\ 0 & 0 & \frac{2}{z_{back}-z_{front}} & -\frac{z_{back}+z_{front}}{z_{back}-z_{front}}\\ 0 & 0 & 0 & 1\end{bmatrix}\cdot \begin{bmatrix}x\\ y\\ z\\ 1\end{bmatrix}$$

相機空間中的點經過正射投影矩陣左乘后得到的點都在CCV之中:

$$\begin{matrix}-1<x'<1\\ -1<y'<1\\ -1<z'<1\end{matrix}$$

透視投影相對較為復雜,同樣用頂視圖考慮x坐標的情況:

$$\frac{x_{p}}{h}=\frac{x}{z}$$

$$\frac{x_{p}-x_{left}}{x_{right}-x_{left}}=\frac{x'-(-1)}{1-(-1)}$$

$$x'=\frac{1}{z}\cdot \frac{2h}{x_{right}-x_{left}}-\frac{x_{right}+x_{left}}{x_{right}-x_{left}}$$

Untitled-122

轉化為齊次的方式:

$$z\cdot x'=x\cdot \frac{2h}{x_{right}-x_{left}}-\frac{x_{right}+x_{left}}{x_{right}-x_{left}}$$

推廣到y軸:

$$\begin{bmatrix}z\cdot x'\\ z\cdot y'\\ z\cdot z'\\ z\end{bmatrix}=\begin{bmatrix}\frac{2}{x_{right}-x{left}} & 0 & -\frac{x_{right}+x_{left}}{x_{right}-x_{left}} & 0\\ 0 & \frac{2}{y_{top}-y_{bottom}} & -\frac{y_{top}+y_{bottom}}{y_{top}-y_{bottom}} & 0\\ 0 & 0 & t_{z} & s_{z}\\ 0 & 0 & 1 & 0\end{bmatrix}\cdot \begin{bmatrix}x\\ y\\ z\\ 1\end{bmatrix}$$

透視投影矩陣的第三行不是我們關心的內容,只要保證不同頂點投影前后的點坐標的第三個分量z和z’的大小關系不變就可以。
透視投影矩陣尾行是(0,0,1,0),這樣就將計算得到的坐標的第四個分量賦值為z而不是1。將相機空間左乘投影矩陣后的結果不是一個CCV空間,如果你將這個空間畫出來,會發現其仍然是一個方台形。這時進行“透視除法”,將上一步得到的點坐標化為第四個分量為1的標准齊次坐標:

$$\begin{bmatrix}x'\\ y'\\ z'\\ 1\end{bmatrix}=\begin{bmatrix}z\cdot x'\\ z\cdot y'\\ z\cdot z'\\ z'\end{bmatrix}\cdot \frac{1}{z}$$

然后我們直接取齊次坐標中的x’和y’值,並將其線形映射到屏幕上,比如點(0,0)出現在屏幕中央,點(-1,1)出現在屏幕左上角。

WebGL

WebGL中對於模型視圖矩陣和投影矩陣的操作依賴於第三方庫,比如Oak3D或glMatrix,WebGL本身不支持(或者說不限制)任何對模型視圖矩陣和投影矩陣的操作。
WebGL是在瀏覽器端運行的,所以使用JavaScript編程。下面的代碼來自www.hiwebgl.com翻譯的LearningWebGL.com的WebGL教程。以glMatrix庫為例:

// 新建空模型視圖矩陣 
var mvMatrix = mat4.create(); 
// 將矩陣設置為單位陣 
mat4.identity(mvMatrix); 
// 平移和旋轉 
mat4.translate(mvMatrix, [-1.5, 0.0, -8.0]); 
mat4.rotate(mvMatrix, degToRad(45), [0, 1, 0]);

將矩陣設置為單位陣相當於說:“這個矩陣表示什么都還沒做(平移、旋轉、縮放)呢”,事實上,任意點坐標乘以單位矩陣都只能得到自己,正說明“什么都沒做”。 

平移矩陣的函數mat4.translate()做的僅僅是將mvMatrix左乘一個平移矩陣而已。

旋轉矩陣的函數mat4.rotate()也許比較復雜,它做的是上面我們討論過的“圍繞任意軸旋轉”的問題,這個函數默認使用“本地軸”,即過所有平移效果累加后的那一點的軸,參數向量[0,1,0]是軸的指向,因此上面的函數調用處理了一個圍繞本地y軸的旋轉。

// 新建空投影矩陣 
var pMatrix = mat4.create(); 
// 初始化投影矩陣 
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);

投影矩陣不會因為場景里模型的位置變化或觀察者的移動而變化(當然如果你想模擬觀察者戴眼鏡的過程你可能要考慮),故而投影矩陣只需要一次初始化就夠了。初始化需要給出相機空間的前、后、左、右、上、下邊界,很容易從函數調用里傳入的參數推知:包括前、后邊界,相機空間的寬高比和水平視場角。 

如果你使用腳本調試工具監測矩陣對象mvMatrix和pMatrix,就會發現他們僅僅是有16個元素的Float32Array對象而已,你完全可以親自處理它。

值得一提的是glMatrix庫的函數大多不返回處理后的矩陣,在將矩陣作為參數傳入時已經給了函數修改矩陣的權利,很少的情況下需要會寫這樣的代碼(但其他的庫不一定這樣):

xMatrix = matX.operate();

使用庫函數或自力更生處理完矩陣后,通過着色器程序傳遞到着色器中(着色器程序是JavaScript腳本里的概念,而着色器是用其他腳本語言編寫的在顯卡中運行的邏輯,這些不在本文的討論范圍內):

// 設置着色器程序 
…… 
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); 
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); 
…… 
// 將模型視圖矩陣和投影矩陣傳入着色器 
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix); 
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);

然后看看着色器里的代碼,這是用x-shader類型的腳本語言寫的:

void main(void){ 
        …… 
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); 
        …… 
}

 可以看到屏幕上點坐標由初始點坐標左乘模型視圖矩陣,再左乘投影矩陣得到的。對於較復雜的場景,我猜測可能需要重新編寫着色器,將模型矩陣和視圖矩陣拆開處理。

綜上所述,模型視圖矩陣和投影矩陣是三維計算機圖形學的基石。關於這兩個矩陣的知識雖然不是進行3D圖形編程的必須,但是至少能夠幫助我們更好地了解那些庫函數在做些什么,或者自己直接操作矩陣對象。

其他

我寫這篇博文參考了:
1. Donald Hearn & M. Pauline Baker 的著作《計算機圖形學》
2. 這一篇關於投影矩陣的博文:http://blog.csdn.net/yanwei2016/article/details/7326180(雖然題目是模型視圖矩陣變化,但大部分篇幅都在講投影矩陣)
3. 站點www.hiwebgl.com翻譯的LearningWebGL.com的WebGL教程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM