WebGL自学课程(4):WebGL矩阵、Camera基础操作

时间:2022-07-02 03:46:28

直接使用WebGL进行开发比较困难,所以用WebGL进行三维开发一般都要使用框架,本人也在学习WebGL,所以想构建一个自己习惯的框架,正好加深自己对WebGL技术的理解。WebGL框架中最重要的部分应该是矩阵变换以及Camera操作,所以本人首先构建矩阵和摄像机方面的基础代码。本人的代码还比较基础,谈不上框架,只是为了是自己加深理解而已。代码如下:

isZero = function(value){
if(Math.abs(value) < 0.000001){
return true;
}
else{
return false;
}
};
//////////////////////////////////////////////////////////////////////////////////////
Vertice = function(x,y,z){
this.x = x||0;
this.y = y||0;
this.z = z||0;
};

//////////////////////////////////////////////////////////////////////////////////////
Vector = function(x,y,z){
this.x = x||0;
this.y = y||0;
this.z = z||0;
};

Vector.prototype = {
constructor:Vector,

getLength:function(){
return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z);
},

normalize:function(){
var length = this.getLength();
if(!isZero(length)){
this.x /= length;
this.y /= length;
this.z /= length;
}
else{
this.x = 0;
this.y = 0;
this.z = 0;
}

return this;
},

cross:function(other){
var x = this.y * other.z - this.z * other.y;
var y = this.z * other.x - this.x * other.z;
var z = this.x * other.y - this.y * other.x;

return new Vector(x,y,z);
}
};
//////////////////////////////////////////////////////////////////////////////////////
Matrix = function(m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44){
this.elements = new Float32Array(16);

this.setElements(
m11||1, m12||0, m13||0, m14||0,
m21||0, m22||1, m23||0, m24||0,
m31||0, m32||0, m33||1, m34||0,
m41||0, m42||0, m43||0, m44||1
);
};

Matrix.prototype = {
constructor:Matrix,

setElements:function(m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44){
var values = this.elements;
values[0]=m11;values[4]=m12;values[8]=m13;values[12]=m14;
values[1]=m21;values[5]=m22;values[9]=m23;values[13]=m24;
values[2]=m31;values[6]=m32;values[10]=m33;values[14]=m34;
values[3]=m41;values[7]=m42;values[11]=m43;values[15]=m44;
},

setColumnX:function(x,y,z){
this.elements[0] = x;
this.elements[1] = y;
this.elements[2] = z;
},

getColumnX:function(){
return new Vertice(this.elements[0],this.elements[1],this.elements[2]);
},

setColumnY:function(x,y,z){
this.elements[4] = x;
this.elements[5] = y;
this.elements[6] = z;
},

getColumnY:function(){
return new Vertice(this.elements[4],this.elements[5],this.elements[6]);
},

setColumnZ:function(x,y,z){
this.elements[8] = x;
this.elements[9] = y;
this.elements[10] = z;
},

getColumnZ:function(){
return new Vertice(this.elements[8],this.elements[9],this.elements[10]);
},

setColumnTrans:function(x,y,z){
this.elements[12] = x;
this.elements[13] = y;
this.elements[14] = z;
},

getColumnTrans:function(){
return new Vertice(this.elements[12],this.elements[13],this.elements[14]);
},

setLastRowDefault:function(){
this.elements[3]=0;
this.elements[7]=0;
this.elements[11]=0;
this.elements[15]=1;
},

transpose:function(){
var result = new Matrix();
result.elements[0]=this.elements[0];
result.elements[4]=this.elements[1];
result.elements[8]=this.elements[2];
result.elements[12]=this.elements[3];

result.elements[1]=this.elements[4];
result.elements[5]=this.elements[5];
result.elements[9]=this.elements[6];
result.elements[13]=this.elements[7];

result.elements[2]=this.elements[8];
result.elements[6]=this.elements[9];
result.elements[10]=this.elements[10];
result.elements[14]=this.elements[11];

result.elements[3]=this.elements[12];
result.elements[7]=this.elements[13];
result.elements[11]=this.elements[14];
result.elements[15]=this.elements[15];

this.setMatrixByOther(result);
},

setMatrixByOther:function(otherMatrix){
for(var i=0;i<otherMatrix.elements.length;i++){
this.elements[i]=otherMatrix.elements[i];
}
},

setSquareMatrix:function(){
this.setElements(1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1);
},

copy:function(){
var clone = new Matrix(this.elements[0],this.elements[4],this.elements[8],this.elements[12],
this.elements[1],this.elements[5],this.elements[9],this.elements[13],
this.elements[2],this.elements[6],this.elements[10],this.elements[14],
this.elements[3],this.elements[7],this.elements[11],this.elements[15]);
return clone;
},

multiply:function(otherMatrix){
var values1 = this.elements;
var values2 = otherMatrix.elements;
var m11 = values1[0]*values2[0]+values1[4]*values2[1]+values1[8]*values2[2]+values1[12]*values2[3];
var m12 = values1[0]*values2[4]+values1[4]*values2[5]+values1[8]*values2[6]+values1[12]*values2[7];
var m13 = values1[0]*values2[8]+values1[4]*values2[9]+values1[8]*values2[10]+values1[12]*values2[11];
var m14 = values1[0]*values2[12]+values1[4]*values2[13]+values1[8]*values2[14]+values1[12]*values2[15];
var m21 = values1[1]*values2[0]+values1[5]*values2[1]+values1[9]*values2[2]+values1[13]*values2[3];
var m22 = values1[1]*values2[4]+values1[5]*values2[5]+values1[9]*values2[6]+values1[13]*values2[7];
var m23 = values1[1]*values2[8]+values1[5]*values2[9]+values1[9]*values2[10]+values1[13]*values2[11];
var m24 = values1[1]*values2[12]+values1[5]*values2[13]+values1[9]*values2[14]+values1[13]*values2[15];
var m31 = values1[2]*values2[0]+values1[6]*values2[1]+values1[10]*values2[2]+values1[14]*values2[3];
var m32 = values1[2]*values2[4]+values1[6]*values2[5]+values1[10]*values2[6]+values1[14]*values2[7];
var m33 = values1[2]*values2[8]+values1[6]*values2[9]+values1[10]*values2[10]+values1[14]*values2[11];
var m34 = values1[2]*values2[12]+values1[6]*values2[13]+values1[10]*values2[14]+values1[14]*values2[15];
var m41 = values1[3]*values2[0]+values1[7]*values2[1]+values1[11]*values2[2]+values1[15]*values2[3];
var m42 = values1[3]*values2[4]+values1[7]*values2[5]+values1[11]*values2[6]+values1[15]*values2[7];
var m43 = values1[3]*values2[8]+values1[7]*values2[9]+values1[11]*values2[10]+values1[15]*values2[11];
var m44 = values1[3]*values2[12]+values1[7]*values2[13]+values1[11]*values2[14]+values1[15]*values2[15];

return new Matrix(m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44);
},

checkZero:function(){
for(var i = 0;i < this.elements.length;i++){
if(isZero(this.elements[i])){
this.elements[i] = 0;
}
}
},

worldTranslate:function(x,y,z){
this.elements[12] += x;
this.elements[13] += y;
this.elements[14] += z;
},

worldRotateX:function(radian){
var c = Math.cos(radian);
var s = Math.sin(radian);
var m = new Matrix(1,0,0,0,
0,c,-s,0,
0,s,c,0,
0,0,0,1);
var result = m.multiply(this);
result.checkZero();
this.setMatrixByOther(result);
},

worldRotateY:function(radian){
var c = Math.cos(radian);
var s = Math.sin(radian);
var m = new Matrix(c,0,s,0,
0,1,0,0,
-s,0,c,0,
0,0,0,1);
var result = m.multiply(this);
result.checkZero();
this.setMatrixByOther(result);
},

worldRotateZ:function(radian){
var c = Math.cos(radian);
var s = Math.sin(radian);
var m = new Matrix(c,-s,0,0,
s,c,0,0,
0,0,1,0,
0,0,0,1);
var result = m.multiply(this);
result.checkZero();
this.setMatrixByOther(result);
},

worldRotateByVector:function(radian,vector){
var x = vector.x;
var y = vector.y;
var z = vector.z;

var length, s, c;
var xx, yy, zz, xy, yz, zx, xs, ys, zs, one_c;

s = Math.sin(radian);
c = Math.cos(radian);

length = Math.sqrt( x*x + y*y + z*z );

// Rotation matrix is normalized
x /= length;
y /= length;
z /= length;

xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * s;
ys = y * s;
zs = z * s;
one_c = 1.0 - c;

var m11 = (one_c * xx) + c;//M(0,0)
var m12 = (one_c * xy) - zs;//M(0,1)
var m13 = (one_c * zx) + ys;//M(0,2)
var m14 = 0.0;//M(0,3) 表示平移X

var m21 = (one_c * xy) + zs;//M(1,0)
var m22 = (one_c * yy) + c;//M(1,1)
var m23 = (one_c * yz) - xs;//M(1,2)
var m24 = 0.0;//M(1,3) 表示平移Y

var m31 = (one_c * zx) - ys;//M(2,0)
var m32 = (one_c * yz) + xs;//M(2,1)
var m33 = (one_c * zz) + c;//M(2,2)
var m34 = 0.0;//M(2,3) 表示平移Z

var m41 = 0.0;//M(3,0)
var m42 = 0.0;//M(3,1)
var m43 = 0.0;//M(3,2)
var m44 = 1.0;//M(3,3)

var mat = new Matrix(m11,m12,m13,m14,
m21,m22,m23,m24,
m31,m32,m33,m34,
m41,m42,m43,m44);

var result = mat.multiply(this);
result.checkZero();
this.setMatrixByOther(result);
},

localRotateX:function(radian){
var transX = this.elements[12];
var transY = this.elements[13];
var transZ = this.elements[14];

this.worldTranslate(-transX,-transY,-transZ);
var columnX = this.getColumnX();
this.worldRotateByVector(radian,columnX);
this.worldTranslate(transX,transY,transZ);
},

localRotateY:function(radian){
var transX = this.elements[12];
var transY = this.elements[13];
var transZ = this.elements[14];

this.worldTranslate(-transX,-transY,-transZ);
var columnY = this.getColumnY();
this.worldRotateByVector(radian,columnY);
this.worldTranslate(transX,transY,transZ);
},

localRotateZ:function(radian){
var transX = this.elements[12];
var transY = this.elements[13];
var transZ = this.elements[14];

this.worldTranslate(-transX,-transY,-transZ);
var columnZ = this.getColumnZ();
this.worldRotateByVector(radian,columnZ);
this.worldTranslate(transX,transY,transZ);
}
};

////////////////////////////////////////////////////////////////////////////////////////////////////
Object3D = function(){
this.matrix = new Matrix();
}
Object3D.prototype = {
constructor:Object3D,

worldTranslate:function(x,y,z){
this.matrix.worldTranslate(x,y,z);
},

worldRotateX:function(radian){
this.matrix.worldRotateX(radian);
},

worldRotateY:function(radian){
this.matrix.worldRotateY(radian);
},

worldRotateZ:function(radian){
this.matrix.worldRotateZ(radian);
},

worldRotateByVector:function(radian,vector){
this.matrix.worldRotateByVector(radian,vector);
},

localRotateX:function(radian){
this.matrix.localRotateX(radian);
},

localRotateY:function(radian){
this.matrix.localRotateY(radian);
},

localRotateZ:function(radian){
this.matrix.localRotateZ(radian);
}
};

////////////////////////////////////////////////////////////////////////////////////////////////////
PerspectiveCamera = function(fov,aspect,near,far){
this.fov = fov;
this.aspect = aspect;
this.near = near;
this.far = far;
this.matrix = new Matrix();//相当于Camera的一般的模型矩阵
this.projMatrix = new Matrix();
this.getPerspectiveMatrix(fov||90,aspect||1,near||0.1,far||100);
}
PerspectiveCamera.prototype = new Object3D();
PerspectiveCamera.prototype.constructor = PerspectiveCamera;

PerspectiveCamera.prototype.getPerspectiveMatrix = function(fov,aspect,near,far){
this.fov = fov||this.fov;
this.aspect = aspect||this.aspect;
this.near = near||this.near;
this.far = far||this.far;

var a = 1.0/Math.tan(this.fov / 2 * Math.PI / 180);
var b = this.far/(this.far-this.near);
var c = -this.near*this.far/(this.far-this.near);

this.projMatrix.setElements(a/aspect, 0, 0, 0,
0, a, 0, 0,
0, 0, b, 1,
0, 0, c, 0);
};

PerspectiveCamera.prototype.getViewMatrix = function(){
var columnTrans = this.matrix.getColumnTrans();
var transX = columnTrans.x;
var transY = columnTrans.y;
var transZ = columnTrans.z;

var mat1 = new Matrix();
mat1.setMatrixByOther(this.matrix);

mat1.transpose();//因为视点矩阵与模型矩阵相反,所以要对各个方向进行转置操作,从而实现设置模型矩阵的XYZ方向。通过Camera的一般的模型矩阵对XYZ方向进行转置,得到视点矩阵的XYZ方向
mat1.setColumnTrans(0,0,0);
mat1.setLastRowDefault();

var mat2 = new Matrix();
mat2.setColumnTrans(-transX,-transY,-transZ);

var viewMatrix = mat1.multiply(mat2);
viewMatrix.checkZero();
return viewMatrix;
};

PerspectiveCamera.prototype.look = function(/*vertice*/ cameraPnt,/*vertice*/ targetPnt,/*vector*/ upDirection){
var transX = cameraPnt.x;
var transY = cameraPnt.y;
var transZ = cameraPnt.z;
var up = upDirection||new Vector(0,1,0);
var zAxis = new Vector(cameraPnt.x-targetPnt.x,cameraPnt.y-targetPnt.y,cameraPnt.z-targetPnt.z).normalize();
var xAxis = up.cross(zAxis).normalize();
var yAxis = zAxis.cross(xAxis).normalize();

this.matrix.setColumnX(xAxis.x,xAxis.y,xAxis.z);//此处相当于对Camera的模型矩阵(还不是视点矩阵)设置X轴方向
this.matrix.setColumnY(yAxis.x,yAxis.y,yAxis.z);//此处相当于对Camera的模型矩阵(还不是视点矩阵)设置Y轴方向
this.matrix.setColumnZ(zAxis.x,zAxis.y,zAxis.z);//此处相当于对Camera的模型矩阵(还不是视点矩阵)设置Z轴方向
this.matrix.setColumnTrans(transX,transY,transZ);//此处相当于对Camera的模型矩阵(还不是视点矩阵)设置偏移量
this.matrix.setLastRowDefault();
};