最近學習構(gòu)建三維圖形的時候盾剩,深感幾何功底不夠蚁廓,一個視圖變化矩陣看了幾天也沒想過來访圃,只勉強理解原理,細節(jié)部分自己還需要加強學習
1. 視圖變換
在二維圖形繪制的時候相嵌,不用考慮z
軸腿时,但是繪制的三維圖形處于一個立體空間,就要使用z
軸了饭宾。由于存在z
軸批糟,那么在物體觀察的時候首先就是要確定觀察坐標系
1.1 視點,觀察點看铆,正方向
觀察坐標系可以通過定義:視點徽鼎,觀察點,正方向確定
視點:是眼睛所在的位置弹惦,可以認為是觀察坐標系的原點
觀察點:是觀察對象所在位置否淤,雖然觀察對象是一個三維物體,但是物體都是有點組成棠隐,那么可以確認任何一個點作為觀察點
視線:視點和觀察點之間的連線
正方向:我的理解是觀察坐標系下y
軸的正方向石抡,也就是當人觀察事物的時候,頭頂?shù)某?/p>
當確定好以上三個要素以后助泽,就可以確定一個觀察的坐標系了啰扛,為什么?
目前使用坐標系都是直角坐標系嗡贺,也就是在y
軸和視線方向確定好以后隐解,y
軸和視線組成了一個平面,那么x
軸需要和這個平面垂直诫睬,并且經(jīng)過視點(因為默認視點為原點)厢漩,之后z
軸需要和y
軸還有x
軸垂直,所以也就確定下來了岩臣。
在使用WebGL繪制點的時候都是使用的WebGL的坐標系溜嗜,但是現(xiàn)在的視點并不一定是WebGL坐標系中的原點,所以這個時候架谎,需要把視點轉(zhuǎn)換為WebGL坐標系中的原點炸宵,才能正確的利用WebGL繪制圖形,將視點轉(zhuǎn)換為WebGL原點的且y
軸正方向和WebGL的y
軸重合的變換矩陣就是視圖矩陣谷扣。
連續(xù)看了幾天也沒有特別明白這個轉(zhuǎn)換矩陣是如何運作的土全,所以這里先暫時貼出公式(代碼摘抄自WebGL編程指南)
Matrix4.prototype.setLookAt = function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) {
var e, fx, fy, fz, rlf, sx, sy, sz, rls, ux, uy, uz;
fx = centerX - eyeX;
fy = centerY - eyeY;
fz = centerZ - eyeZ;
// Normalize f.
rlf = 1 / Math.sqrt(fx*fx + fy*fy + fz*fz);
fx *= rlf;
fy *= rlf;
fz *= rlf;
// Calculate cross product of f and up.
sx = fy * upZ - fz * upY;
sy = fz * upX - fx * upZ;
sz = fx * upY - fy * upX;
// Normalize s.
rls = 1 / Math.sqrt(sx*sx + sy*sy + sz*sz);
sx *= rls;
sy *= rls;
sz *= rls;
// Calculate cross product of s and f.
ux = sy * fz - sz * fy;
uy = sz * fx - sx * fz;
uz = sx * fy - sy * fx;
// Set to this.
e = this.elements;
e[0] = sx;
e[1] = ux;
e[2] = -fx;
e[3] = 0;
e[4] = sy;
e[5] = uy;
e[6] = -fy;
e[7] = 0;
e[8] = sz;
e[9] = uz;
e[10] = -fz;
e[11] = 0;
e[12] = 0;
e[13] = 0;
e[14] = 0;
e[15] = 1;
// Translate.
return this.translate(-eyeX, -eyeY, -eyeZ);
};
Matrix4.prototype.translate = function(x, y, z) {
var e = this.elements;
e[12] += e[0] * x + e[4] * y + e[8] * z;
e[13] += e[1] * x + e[5] * y + e[9] * z;
e[14] += e[2] * x + e[6] * y + e[10] * z;
e[15] += e[3] * x + e[7] * y + e[11] * z;
return this;
};
PS:公式主要是將點進行了反向的旋轉(zhuǎn)和平移變換
2. 可視范圍
我們觀察時候的坐標系是我們自定定義的尺度,而WebGL坐標系中坐標范圍是(-1.0,1.0)会涎,一旦超出的坐標就不會繪制了裹匙,從而導致圖形缺失。如果要讓視野所見的所有內(nèi)容都包含在坐標系中末秃,就可能需要對坐標進行縮放和平移概页,來改變可視范圍,常用的方式有兩種
2.1 正射投影
長方體的可視范圍练慕,是一種盒狀空間惰匙,用于建筑平面設(shè)計。利用近裁剪面和遠裁剪面來確定可視區(qū)域铃将,因此使用六個參數(shù)可以確定正射投影:left项鬼,right,top劲阎,bottom绘盟,near,far悯仙,具體正射投影矩陣構(gòu)建公式如下:
Matrix4.prototype.setOrtho = function(left, right, bottom, top, near, far) {
var e, rw, rh, rd;
if (left === right || bottom === top || near === far) {
throw 'null frustum';
}
rw = 1 / (right - left);
rh = 1 / (top - bottom);
rd = 1 / (far - near);
e = this.elements;
e[0] = 2 * rw;
e[1] = 0;
e[2] = 0;
e[3] = 0;
e[4] = 0;
e[5] = 2 * rh;
e[6] = 0;
e[7] = 0;
e[8] = 0;
e[9] = 0;
e[10] = -2 * rd;
e[11] = 0;
e[12] = -(right + left) * rw;
e[13] = -(top + bottom) * rh;
e[14] = -(far + near) * rd;
e[15] = 1;
return this;
};
2.2 透視矩陣
透視矩陣是四棱錐的可視空間龄毡,一般用于游戲設(shè)計,符合現(xiàn)實場景(近大遠小的效果)雁比。也存在近裁剪面和遠裁剪面稚虎,需要4個參數(shù)來確定相關(guān)變換矩陣:fovy(可視空間頂面和底面的夾角),aspect(裁剪面高寬比),near偎捎,far
Matrix4.prototype.setPerspective = function(fovy, aspect, near, far) {
var e, rd, s, ct;
if (near === far || aspect === 0) {
throw 'null frustum';
}
if (near <= 0) {
throw 'near <= 0';
}
if (far <= 0) {
throw 'far <= 0';
}
fovy = Math.PI * fovy / 180 / 2;
s = Math.sin(fovy);
if (s === 0) {
throw 'null frustum';
}
rd = 1 / (far - near);
ct = Math.cos(fovy) / s;
e = this.elements;
e[0] = ct / aspect;
e[1] = 0;
e[2] = 0;
e[3] = 0;
e[4] = 0;
e[5] = ct;
e[6] = 0;
e[7] = 0;
e[8] = 0;
e[9] = 0;
e[10] = -(far + near) * rd;
e[11] = -1;
e[12] = 0;
e[13] = 0;
e[14] = -2 * near * far * rd;
e[15] = 0;
return this;
};
3. 深度處理
3.1 隱藏面消除
WebGL默認深度的并不會對深度進行處理蠢终,會按照我們對點/面的繪制順序進行繪制,也就最后繪制的內(nèi)容會在最前面茴她,違背了本來的意圖寻拂,不過WebGL提供了對應的方法來處理深度關(guān)系:
首先,利用
gl.enable(gl.DEPTH_TEST)
開啟隱藏面消除功能
然后丈牢,利用深度清理gl.clear(gl.DEPTH_BUFFER_BIT)
祭钉,可以讓WebGL自己處理好深度關(guān)系
3.2 深度沖突
由于存在兩個平面處在同一個深度的情況,這個時候WebGL繪制會出現(xiàn)深度沖突己沛,表現(xiàn)為圖形繪制結(jié)果看上去表面斑駁慌核,于是WebGL提供了多邊形偏移的功能距境,讓即使深度一致的兩個表面也會發(fā)生一定深度的偏移
首先,利用
gl.enable(gl.POLYGON_OFFSET_FILL)
開啟多邊形偏移功能
然后垮卓,利用gl.polygonOffset(1.0, 1.0)
來指定計算偏移量的參數(shù)
4. 繪制立方體
要繪制立方體垫桂,仍然可以使用gl.drawArrays()
,利用緩沖區(qū)數(shù)據(jù)來繪制表面粟按,除此之外诬滩,為了高效利用圖形中的坐標信息,可以使用gl.drawElements()
配合將序列放到gl.ELEMENT_BUFFER_ARRAY
來繪制表面灭将。
具體做法如下:
function initPoint(gl, a_Position, a_Color) {
let pointData = new Float32Array([
0.0, 0.5, 0.0, 1.0, 0.0, 0.0,
-0.5, -0.5, 0.5, 1.0, 0.0, 0.0,
0.5, -0.5, 0.5, 1.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0, 1.0, 0.0,
0.5, -0.5, 0.5, 0.0, 1.0, 0.0,
0.0, -0.5, -0.5, 0.0, 1.0, 0.0,
0.0, 0.5, 0.0, 0.0, 0.0, 1.0,
0.0, -0.5, -0.5, 0.0, 0.0, 1.0,
-0.5, -0.5, 0.5 ,0.0, 0.0, 1.0,
-0.5, -0.5, 0.5, 1.0, 0.0, 1.0,
0.0, -0.5, -0.5, 1.0, 0.0, 1.0,
0.5, -0.5, 0.5, 1.0, 0.0, 1.0
]);
// 利用的坐標序列
let indexData = new Uint8Array([
0, 1, 2,
3, 4, 5,
6, 7, 8,
9, 10, 11
])
let FSIZE = pointData.BYTES_PER_ELEMENT;
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0);
gl.enableVertexAttribArray(a_Position);
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 3*FSIZE);
gl.enableVertexAttribArray(a_Color);
// 將坐標序列信息存儲到ELEMENT_ARRAY_BUFFER
let indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
}
5. 總結(jié)
構(gòu)建三維圖形相比二維圖形來說疼鸟,需要使用視圖轉(zhuǎn)換和可視范圍的矩陣信息,因此坐標信息就變?yōu)榱?gl_Position = 可視矩陣 X 視圖矩陣 X 變換矩陣 X 原始坐標
庙曙,同時要利用深度規(guī)則消除深度影響空镜,最后可以利用gl.drawElements()
,定義序列矾利,復用坐標信息
6. 參考
《WebGL編程指南》