前言
最近的需求需要涉及到在web上實現各個矩陣的變換和逆變換,不過功能也僅限於此,沒必要因爲這個功能而引用three.js,所以就想着gluProject和gluUnProject兩個函數在JavaScript上實現一遍,於是對照着這兩個函數的源碼進行改寫。改寫用到了矩陣向量庫glMatrix。
gluProject和gluUnProject源碼
GLint GLAPIENTRY
gluProject(GLdouble objx, GLdouble objy, GLdouble objz,
const GLdouble modelMatrix[16],
const GLdouble projMatrix[16],
const GLint viewport[4],
GLdouble *winx, GLdouble *winy, GLdouble *winz)
{
double in[4];
double out[4];
in[0]=objx;
in[1]=objy;
in[2]=objz;
in[3]=1.0;
__gluMultMatrixVecd(modelMatrix, in, out);
__gluMultMatrixVecd(projMatrix, out, in);
if (in[3] == 0.0) return(GL_FALSE);
in[0] /= in[3];
in[1] /= in[3];
in[2] /= in[3];
/* Map x, y and z to range 0-1 */
in[0] = in[0] * 0.5 + 0.5;
in[1] = in[1] * 0.5 + 0.5;
in[2] = in[2] * 0.5 + 0.5;
/* Map x,y to viewport */
in[0] = in[0] * viewport[2] + viewport[0];
in[1] = in[1] * viewport[3] + viewport[1];
*winx=in[0];
*winy=in[1];
*winz=in[2];
return(GL_TRUE);
}
GLint GLAPIENTRY
gluUnProject(GLdouble winx, GLdouble winy, GLdouble winz,
const GLdouble modelMatrix[16], //注意:模型視圖矩陣
const GLdouble projMatrix[16],
const GLint viewport[4],
GLdouble *objx, GLdouble *objy, GLdouble *objz)
{
double finalMatrix[16];
double in[4];
double out[4];
//合併MVP矩陣
__gluMultMatricesd(modelMatrix, projMatrix, finalMatrix);
//MVP矩陣求逆矩陣
if (!__gluInvertMatrixd(finalMatrix, finalMatrix)) return(GL_FALSE);
in[0]=winx;
in[1]=winy;
in[2]=winz;
in[3]=1.0;
//從屏幕座標變換爲0到1之間的座標
/* Map x and y from window coordinates */
in[0] = (in[0] - viewport[0]) / viewport[2];
in[1] = (in[1] - viewport[1]) / viewport[3];
//從0到1之間變換到-1到1.
/* Map to range -1 to 1 */
in[0] = in[0] * 2 - 1;
in[1] = in[1] * 2 - 1;
in[2] = in[2] * 2 - 1;
//乘以MVP逆矩陣之後得到世界空間的座標
__gluMultMatrixVecd(finalMatrix, in, out);
//從齊次座標變換爲笛卡爾座標
if (out[3] == 0.0) return(GL_FALSE);
out[0] /= out[3];
out[1] /= out[3];
out[2] /= out[3];
//將結果賦值。
*objx = out[0];
*objy = out[1];
*objz = out[2];
return(GL_TRUE);
}
JavaScript改寫版本
其實,如果懂渲染管線的知識,應該這兩個函數的源碼應該非常清晰易懂。如果看不懂,請先搞懂各個座標轉換的知識。
下面是兩個函數的JavaScript版本改寫,要先下載glMatrix.js。
//將世界座標轉換爲屏幕座標。
function project(objX, objY, objZ, modelviewMatrix, projectMatrix, viewport, winX, winY, winZ) {
var input = new Array(4);
var output = new Array(4);
input[0] = objX;
input[1] = objY;
input[2] = objZ;
input[3] = 1.0;
vec4.transformMat4(output, input, modelviewMatrix);//模型視圖變換
vec4.transformMat4(input, output, projectMatrix);//投影變換
//透視除法,進入標準化設備座標
if (input[3] == 0.0) return;
input[0] /= input[3];
input[1] /= input[3];
input[2] /= input[3];
//將座標由-1到1,轉換到0-1
input[0] = input[0] * 0.5 + 0.5;
input[1] = input[1] * 0.5 + 0.5;
input[2] = input[2] * 0.5 + 0.5;
//將x,y轉換到屏幕座標
input[0] = input[0] * viewport[2] + viewport[0];
input[1] = input[1] * viewport[3] + viewport[1];
winX = input[0];
winY = input[1];
winZ = input[2];
}
//將屏幕座標轉換爲世界座標。
function Unproject(winX, winY, winZ, modelviewMatrix, projectMatrix, viewport, objX, objY, objZ) {
var finalMatrix = mat4.create();
var input = new Array(4);
var output = new Array(4);
mat4.invert(modelviewMatrix, modelviewMatrix);
mat4.invert(projectMatrix, projectMatrix);
//合併MVP矩陣
mat4.multiply(finalMatrix, modelviewMatrix, projectMatrix);
input[0] = winX;
input[1] = winY;
input[2] = winZ;
input[3] = 1.0;
//從屏幕座標變換爲0到1之間的座標
input[0] = (input[0] - viewport[0]) / viewport[2];
input[1] = (input[1] - viewport[1]) / viewport[3];
//從0到1之間變換到-1到1之間
input[0] = input[0] * 2 - 1;
input[1] = input[1] * 2 - 1;
input[2] = input[2] * 2 - 1;
//乘以MVP逆矩陣之後得到世界空間的座標
vec4.transformMat4(output, input, finalMatrix);
//從齊次座標變換爲笛卡爾座標
if (output[3] == 0.0) return;
output[0] /= output[3];
output[1] /= output[3];
output[2] /= output[3];
//將結果賦值。
objX = output[0];
objY = output[1];
objZ = output[2];
}
我自己寫了個小的測試例子,大家如果感興趣可以下載,自行打斷點進行測試。
下載地址