Three.js中的webgl_hierarch2例子展示了如何進行3D場景的層級結構化矩陣變換。
實現過程
webgl_hierarchy2例子中坯辩,首先生成了一個擁有眾多內嵌子部件的物體秧倾,每個子部件的位置和方位都關聯(lián)于父級部件設置怨酝。當父級部件運動時,子部件也會隨著父部件運動那先。要實現這個物體及所有子部件的層級結構化變換农猬,需要借助matrix stack概念。矩陣棧的構造原理售淡,需要借助matrix stack概念斤葱。矩陣棧的構造原理為,棧中的每個頂部元素都是copy其下部矩陣元素并串接當前變換矩陣揖闸。另外揍堕,webgl_hierarch2例子中還使用了幾何法線對立方體進行著色。
這里汤纸,我們模仿webgl_hierarchy2例子衩茸,使用OpenGL ES 3.0和C++獲得了如下的渲染效果,iOS版本實現源碼可以從github上獲取贮泞。
實現層級結構化的幾何體物體表達和矩陣變換
webgl_hierarchy2例子中的3D物體可以看作是由很多立方體模型構成的“章魚”楞慈,由中心立方體連接六條由依次內嵌的子立方體構成的觸須幔烛。要表達這樣的“章魚”幾何體,我們只需要讓一個立方體模型對象繼承一個支持父子關系的根對象即可囊蓝,這個對象的代碼示意如下:
class Object{
public:
Object(std::string name = "", Object* parentObj = NULL);
virtual ~Object();
void SetName(std::string mdlName){ name = mdlName;}
std::string GetName(){return name;}
void SetParent(Object* parent = 0);
void RemoveParent();
void SetChild(Object* child = 0);
void RemoveFromParentChildList();
Object* GetParent();
std::vector<Object*>* GetChildren();
protected:
//模型名稱
std::string name;
//父節(jié)點
Object* parent;
//子節(jié)點
std::vector<Object*> childList;
};
利用這種繼承關系饿悬,利用立方體模型我們就可以構造一個復雜的“章魚”。由于這種模型實際上由眾多的立方體模型構成聚霜,如果繼續(xù)由立方體模型完全負責繪制動作會極大的增加圖形api調用開銷狡恬,我們將涉及GPU的圖形緩存數據設置和繪制工作交由抽象出的幾何體對象負責。代碼示例如下:
//著色器狀態(tài)由所有的立方體模型共享
shared_ptr<NormalColorShaderState> shaderState;
shaderState.reset(new NormalColorShaderState());
//立方幾何體對象由所有的立方體模型共享
shared_ptr<Geometry> geometry;
CubeGeometry cb = CubeGeometry(100,100,100);
CubeGeometry::CubeVertexData cbVdata = makeCubeVertexData(cb);
int num_vertices_ = (int)cbVdata.vData.size();
int num_indices_ = (int)cbVdata.iData.size();
vector<VertexPNX> vertices = cbVdata.vData;
vector<unsigned short> indices = cbVdata.iData;
geometry.reset(new Geometry(&vertices[0],&indices[0],num_vertices_,num_indices_));
//每條觸須的立方體模型數量
int amount = 100;
//中心根立方體模型
rootObj = new CubeModel(NULL,geometry,shaderState);
rootObj->position = Cvec3(1000,0,0);
rootObj->scale = vec3(1,1,1);
rootObj->UpdateMatrixWorld();
rootObj->matrixStatck = matrixStack;
Object *parentObj = rootObj;
//每個循環(huán)生成一條觸須俯萎,每個觸須都是根立方體模型的子部件
for(int i = 0; i < amount; i ++){
CubeModel *model = new CubeModel(parentObj,geometry,shaderState);
model->position = Cvec3(100,0,0);
model->scale = Cvec3(1,1,1);
model->matrixStatck = matrixStack;
model->setPerspectiveCamera(mainCamera);
parentObj = model;
}
//總共生成六條觸須
... ...
parentObj = rootObj;
for(int i = 0; i < amount; i ++){
CubeModel *model = new CubeModel(parentObj,geometry,shaderState);
model->position = Cvec3(0,0,100);
model->scale = Cvec3(1,1,1);
model->matrixStatck = matrixStack;
model->setPerspectiveCamera(mainCamera);
parentObj = model;
}
接下來我們考慮傲宜,立方體模型如何使用matrix stack數據結構實現層級化變換。matrix stack數據結構是實現層級化變換的基礎夫啊,其工作原理比較簡單,為基本的棧數據結構的使用辆憔。一般情況下撇眯,每次繪制前,你需要進行matrix stack壓棧虱咧,首先copy當前的頂部元素熊榛,然后在當前位置將頂部元素串接當前外部的參數矩陣。而繪制之后腕巡,你需要對matrix stack進行出棧操作玄坦,彈出當前的頂部元素。matrix stack的關鍵代碼示意如下:
class MatrixStack
{
public:
MatrixStack();
~MatrixStack();
//矩陣棧元素初始化操作
void MatrixStackLoadMatrix(Matrix4 *m);
//壓棧操作
void MatrixStackPushMatrix();
//出棧操作
void MatrixStackPopMatrix();
//為壓棧后的部矩陣元素串接當前繪制所需的變換矩陣
void MatrixStackMultiplyMatrix(Matrix4 *m);
...
//獲取當前棧中的頂部元素
Matrix4 *MatrixStackGetTopMatrix();
...
};
繪制立方體時需要使用matrix stack的代碼示意如下:
//首先壓棧
matrixStatck->MatrixStackPushMatrix();
//將壓棧后的頂部矩陣元素串接當前運動參數矩陣
matrixStatck->MatrixStackMultiplyMatrix(&mat_model_);
//矩陣棧的使用
Matrix4 mat_mv = mat_view_ * (*matrixStatck->MatrixStackGetTopMatrix());
GLfloat glmatrix[16];
mat_mv.writeToColumnMajorMatrix(glmatrix);
glUniformMatrix4fv(shaderState_->matrix_mv_, 1, GL_FALSE,
glmatrix);
... ...
geometry_->draw(*shaderState_);
for(auto object:*this->GetChildren()){
CubeModel *model = dynamic_cast<CubeModel *>(object);
model->Render();
}
//出棧
matrixStatck->MatrixStackPopMatrix();
上述步驟實現后绘沉,我們只是渲染出一個靜態(tài)的十字架形狀的結構煎楣,如下圖所示:
要實現webgl_hierarch2例子中的動態(tài)螺旋變換效果,還需要設置每條觸須中對應幾何體模型的運動參數以及觀看視角的控制车伞,我們會加入下面的運動控制代碼:
float touchX = ( touch_location_x - g_windowWidth ) * 10;
float touchY = ( touch_location_y - g_windowHeight ) * 10;
//每幀累加一個固定數值
timer+=0.005;
//利用點擊像素坐標動態(tài)設置相機位置
cameraPosition = Cvec3(cameraPosition[0]+(touchX - cameraPosition[0])*0.05,cameraPosition[1]+(- touchY - cameraPosition[1])*0.05,cameraPosition[2]);
//先生成眼睛(相機)坐標系矩陣
Matrix4 mat_eye = Matrix4::makeLookAtEyeMatrix(cameraPosition, Cvec3(0,0,0), Cvec3(0,1,0));
//再生成view矩陣
Matrix4 viewMat = inv(mat_eye);
//每條觸須的螺旋運動控制參數
float rx = sin(timer * 0.7)*0.2*180;
float ry = sin(timer * 0.3)*0.1*180;
float rz = sin(timer * 0.2)*0.1*180;
rootObj->rotation = Cvec3(rx,ry,rz);
rootObj->UpdateMatrixWorld();
rootObj->setPerspectiveCamera(mainCamera);
rootObj->mat_view_ = viewMat;
//遍歷所有子模型對象择懂,設置視圖矩陣和螺旋運動參數
traverse(rootObj,Cvec3(rx,ry,rz),viewMat);
使用法線為物體著色
對于這次渲染的所有立方體,我們使用一種特別的但是簡單的著色方式另玖。利用立方體的法線坐標困曙,將法線坐標從經典立方體域變換為單位立方體域,然后將變換后的坐標值直接作為輸出色彩谦去。fragment shader的實現代碼如下:
#version 300 es
precision highp float;
in vec3 vNormal;
out vec4 fragColor;
vec3 packNormal2Color(in vec3 normal) {
return normalize(normal) * 0.5 + 0.5;
}
void main() {
vec3 normalColor = packNormal2Color(vNormal);
fragColor = vec4(normalColor,0.9);
}
經過上述步驟慷丽,我們可以獲得如下的渲染效果。