同じシーン内で異なるオブジェクトをレンダリングする簡単な方法は、モデルごとに個別のバッファを使用することです。各モデルには独自のモデル マトリックスが必要なので、レンダリングするモデルごとに新しいモデル ビュー マトリックスを生成する必要があります。また、モデルごとに個別に glDrawArrays() を呼び出す必要があります。したがって、init() 関数と display() 関数を変更する必要があります。
次に、単純なピラミッドを追加して、シーンが立方体とピラミッドで構成されるようにしましょう。
頂点シェーダとフラグメント シェーダのコードは省略されていますが、セクション 4 の最後に示されているコードと同じです。
main.cpp
void setupVertices(void)
{
// 立方体
float cubePositions[108] = {
-1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f,
};
// 金字塔有18个顶点,由6个三角形组成(侧面4个,底面2个)
float pyramidOsitions[54] = {
-1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 前面
1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // 右面
1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // 后面
-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 左面
-1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, // 底面 - 左前一半
1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, // 底面 - 右后一半
};
glGenVertexArrays(1, vao); // 创建一个vao,并返回它的整数型ID存进数组vao中
glBindVertexArray(vao[0]); // 激活vao
glGenBuffers(numVBOs, vbo);// 创建两个vbo,并返回它们的整数型ID存进数组vbo中
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 激活vbo第0个缓冲区
glBufferData(GL_ARRAY_BUFFER, sizeof(cubePositions), cubePositions, GL_STATIC_DRAW); // 将包含顶点数据的数组复制进活跃缓冲区(这里是第0个VBO)
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // 激活vbo第0个缓冲区
glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidOsitions), pyramidOsitions, GL_STATIC_DRAW); // 将包含顶点数据的数组复制进活跃缓冲区(这里是第1个VBO)
}
void init(GLFWwindow* window) {
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
cameraX = 0.0f; cameraY = 0.0f; cameraZ = 8.0f;
cubeLocX = 0.0f; cubeLocY = -2.0f; cubeLocZ = 0.0f; // 沿Y轴下移以展示透视
pyrLocX = 2.0f; pyrLocY = 2.0f; pyrLocZ = 0.0f;
setupVertices();
}
void display(GLFWwindow* window, double currentTime)
{
glClear(GL_DEPTH_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(renderingProgram);
// 获取MV矩阵和投影矩阵的统一变量的引用
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
// 构建透视矩阵
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees
// 构建视图矩阵、模型矩阵
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
// 绘制立方体(使用0号缓冲区)
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ));
mvMat = vMat * mMat;
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat)); // 着色器需要视图矩阵的统一变量
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 标记第0个缓冲区为“活跃”
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); // 将第0个属性关联到缓冲区
glEnableVertexAttribArray(0); // 启用第0个顶点属性
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, 36); // 执行该语句,第0个VBO中的数据将被传输给拥有位置0的layout修饰符的顶点属性中。这会将立方体的顶点数据发送到着色器。
// 绘制金字塔(使用1号缓冲区)
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(pyrLocX, pyrLocY, pyrLocZ));
mvMat = vMat * mMat;
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat)); // 着色器需要视图矩阵的统一变量
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // 标记第1个缓冲区为“活跃”
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); // 将第0个属性关联到缓冲区
glEnableVertexAttribArray(0); // 启用第0个顶点属性
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, 18); // 执行该语句,第1个VBO中的数据将被传输给拥有位置0的layout修饰符的顶点属性中。这会将金字塔的顶点数据发送到着色器。
}
結果は次のとおりです。
マトリックススタック
行列スタックは、変換行列の束です。これにより、変換を他の変換の上に構築する (または他の変換から削除する) ことができます。
C++ 標準テンプレート ライブラリ (STL) のスタック クラスを使用できます。
「*=」演算子は mat4 でオーバーロードされているため、行列の連結に使用できます。
mat4 のインスタンスを作成して変換を構築する代わりに、push() コマンドを使用してスタックの一番上に新しい行列を作成します。次に、必要に応じて、スタックの最上部に新しく作成された行列に目的の変換が適用されます。
太陽系の動きをシミュレートするアニメーション効果。
ビュー行列をスタックに入れると、ビュー行列のすぐ上の行列が太陽の MV 行列になります。その上の行列は地球の MV 行列となり、太陽の MV 行列のコピーとその上に適用される地球モデルの行列変換で構成されます。つまり、地球の MV マトリックスは、惑星の変換を太陽の変換に組み合わせることによって確立されます。同様に、月の MV 行列は惑星の MV 行列の上にあり、月のモデル行列変換をそのすぐ下の惑星の MV 行列に適用することによって構築されます。
月をレンダリングした後、スタックから最初の月の行列を「ポップ」し(スタックの最上位を惑星のモデルビュー行列に戻す)、2番目の月に対してこのプロセスを繰り返すことで、2番目の「月」をレンダリングできます。 。
基本的な方法は以下の通りです。
(1) スタックを宣言し、「mvStack」という名前を付けます。
(2)親オブジェクトに対して相対的に新しいオブジェクトを作成する場合は、「mvStack.push(mvStack.top())」を呼び出します。
(3) 必要な変換を新しいオブジェクトに適用します。つまり、必要な変換に必要な変換を掛けます。
(4) オブジェクトまたはサブオブジェクトの描画が完了したら、「mvStack.pop()」を呼び出して、そのモデルビュー行列を行列スタックの最上位から削除します。
main.cpp
void display(GLFWwindow* window, double currentTime)
{
glClear(GL_DEPTH_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(renderingProgram);
// 获取MV矩阵和投影矩阵的统一变量的引用
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
// 构建透视矩阵
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
// 将视图矩阵推入堆栈
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
mvStack.push(vMat);
// 金字塔 == 太阳
mvStack.push(mvStack.top());
mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f)); // 太阳位置
mvStack.push(mvStack.top());
mvStack.top() *= glm::rotate(glm::mat4(1.0f), (float)currentTime, glm::vec3(1.0f, 0.0f, 0.0f));
// 太阳旋转
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, 18); // 绘制太阳
mvStack.pop(); // 从堆栈中移除太阳的轴旋转
// 立方体 == 行星
mvStack.push(mvStack.top());
mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(sin((float)currentTime)*4.0, 0.0f, cos((float)currentTime)*4.0));
mvStack.push(mvStack.top());
mvStack.top() *= glm::rotate(glm::mat4(1.0f), (float)currentTime, glm::vec3(0.0, 1.0, 0.0));
// 行星旋转
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 36); // 绘制行星
mvStack.pop(); // 从堆栈中移除行星的轴旋转
// 小立方体 == 月球
mvStack.push(mvStack.top());
mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, sin((float)currentTime)*2.0, cos((float)currentTime)*2.0));
mvStack.top() *= glm::rotate(glm::mat4(1.0f), (float)currentTime, glm::vec3(0.0f, 0.0f, 1.0f));
// 月球旋转
mvStack.top() *= glm::scale(glm::mat4(1.0f), glm::vec3(0.25f, 0.25, 0.25)); // 让月球小一些
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 36); // 绘制行星
// 从堆栈中移除月球缩放、旋转、位置矩阵,行星位置矩阵,太阳位置矩阵,和视图矩阵
mvStack.pop();
mvStack.pop();
mvStack.pop();
mvStack.pop();
}
シェーダーコードは変更なし
効果は次のとおりです。