コンピュータグラフィックスとopengl C++版学習メモ 第6章 3Dモデル

これまでは、立方体やピラミッドなどの非常に単純な 3D オブジェクトのみを扱ってきました。これらのオブジェクトは非常に単純なので、ソース コード内のすべての頂点情報を明示的にリストし、それをバッファーに直接入れることができます。

しかし、ほとんどの興味深い 3D シーンには、かつてのように手動で構築し続けるには複雑すぎるオブジェクトが含まれています。この章では、より複雑なオブジェクト モデルと、それらを構築してシーンにロードする方法について説明します。 (暗黙的ジオメトリと明示的ジオメトリの紹介を参照してください)

3D モデリング自体は広大な分野であり、ここで説明する内容は必然的に非常に限定されます。次の 2 つのトピックに焦点を当てます。

  • プログラムを通じてモデルを構築します。
  • 外部で作成したモデルをロードします。

これは 3D モデリングの豊かな世界のごく浅い部分に触れているだけですが、これにより、さまざまな複雑でリアルに詳細なオブジェクトをシーンに含めることができるようになります。

6.1 プログラム構築モデル - 球体の構築

特定のタイプのオブジェクト (球、円錐など) には、アルゴリズムの生成を容易にする数学的定義があります。たとえば、半径 R の円の場合、その円周上の点の座標を明確に定義できます (図 6.1 を参照)。

ここに画像の説明を挿入します

図 6.1 円周を形成する点

円幾何学の知識を体系的に使用して、アルゴリズム的に球をモデル化できます。戦略は次のとおりです。

  1. 選択された精度は、球全体にわたる一連の円形の「水平スライス」を表します。図 6.2 の左側を参照してください。
  2. 各円形スライスの円周を点に再分割します。図 6.2 の右側を参照してください。より多くのポイントと水平スライスにより、より正確で滑らかな球体モデルが生成されます。このモデルでは、各スライスのポイント数は同じになります。

ここに画像の説明を挿入します

図 6.2 円形頂点の構築
  1. 頂点を三角形にグループ化します。 1 つのアプローチは、頂点を段階的に通過し、各段階で 2 つの三角形を構築することです。たとえば、図 6.3 の球上の 5 つの色付き頂点の行に沿って移動する場合、これら 5 つの頂点のそれぞれに対して、対応する色で示される 2 つの三角形を構築します (色の差し込み図を参照。これらの手順の詳細については、以下で説明します)。

ここに画像の説明を挿入します

図 6.3 頂点を三角形に結合する
  1. テクスチャ座標は、テクスチャ イメージの性質に基づいて選択されます。球の場合は、多くの地形テクスチャ イメージがあります。このテクスチャ イメージを選択したとします。このイメージが球の周りに「ラップ」されていると想像してください。最終的に対応するテクセルの位置に基づいて、各頂点のテクスチャ座標を指定できます。画像。 。
  2. 通常、頂点ごとに法線ベクトル (モデルの表面に垂直なベクトル) も生成する必要があります。これらは第 7 章ですぐに照明に使用します。

法線ベクトルを決定するのは難しい場合がありますが、球の場合、球の中心から頂点を指すベクトルは、その頂点の法線ベクトルとまったく同じです。図 6.4 はこの特徴を示しています (球の中心は「星」で表されています)。
ここに画像の説明を挿入します

図6.4 球の頂点法線ベクトル

一部のモデルでは、インデックスを使用して三角形を定義します。 図 6.3 では、各頂点が複数の三角形内に表示されているため、各頂点が複数回指定されることになることに注意してください。これを行うのではなく、各頂点を一度保存​​してから、三角形の各角にインデックスを割り当て、目的の頂点を参照します。 各頂点の位置、テクスチャ座標、法線ベクトルを保存する必要があるため、これを行うことで大規模なモデルのメモリを節約できます。

頂点は、一番下の水平スライスの頂点から始まる 1 次元配列に格納されます。インデックスを使用する場合、関連するインデックス配列には各三角形のコーナーのエントリが含まれます。その内容は、頂点配列内の整数参照 (具体的には添字) です。 図 6.5 に示すように、各スライスには n 個の頂点、つまり頂点配列の一部と対応するインデックス配列が含まれていると仮定します。
ここに画像の説明を挿入します

図 6.5 頂点配列と対応するインデックス配列

次に、球の底面から開始して、各水平スライスの周りを円を描くように頂点を移動します。図 6.3 に示すように、各頂点を訪問しながら、その右上に正方形の領域を形成する 2 つの三角形を構築します。以下に示すように、プロセス全体をネストされたループに編成します。

ここに画像の説明を挿入します

たとえば、図 6.3 の「赤い」頂点を考えてみましょう (図 6.6 にも繰り返し表示されています)。この頂点は図 6.6 に示す黄色の三角形の左下にあり、先ほど説明したループによれば、そのインデックス番号は i*n+j です。ここで、i は現在処理されているスライス (外側のループ)、j はそのスライスで現在処理されている頂点 (内側のループ)、n は各スライスの頂点の数です。図 6.6 は、この頂点 (赤) とそれに関連する 3 つの隣接する頂点
を示しています (カラー挿入を参照)。各頂点にはインデックス番号を示す式があります。

ここに画像の説明を挿入します

図 6.6 i 番目のスライスの j 番目の頂点のインデックス番号 (n = 各スライスの頂点の数)

これらの 4 つの頂点は、この (赤色) 頂点に対して生成された 2 つの三角形 (黄色で表示) を構築するために使用されます。これら 2 つの三角形のインデックス テーブル内の 6 つのエントリは、図では 1 から 6 の順に表されています。エントリ 3 と 6 は両方とも同じ頂点を指しており、エントリ 2 と 4 についても同様であることに注意してください。赤で強調表示された頂点 (つまり、vertex[i*n+j]) に到達すると、このように定義された 2 つの三角形はこれら 6 つの頂点で構成されます。三角形の 1 つは 1, 2, 3 というラベルの付いたエントリを持ち、参照される頂点には vertex[ i *n+j]、vertex[i *n+j +1] および vertex[(i+1) *n+j]; 他の三角形のエントリには 4、5、6 のマークが付けられ、参照される頂点には頂点が含まれます[i *n+j +1]、頂点[(i+1) *n+j +1]、および頂点[(i+1) *n+j]。

プログラム 6.1 は、クラス名 Sphere を持つ球体モデルの実装を示しています。結果として得られる球の中心は原点にあります。 Sphere を使用したコードもここに示されています。各頂点は、GLM クラス vec2 および vec3 のインスタンスを含む C++ ベクトルに格納されることに注意してください (これは、頂点が浮動小数点配列に格納された前の例とは異なります)。 vec2 と vec3 には、x、y、z コンポーネントに必要な浮動小数点値を取得するメソッドが含まれており、これらの値は前述したように浮動小数点バッファーに配置できます。長さは実行時に指定されたスライスの数に依存するため、これらの値は可変長の C++ ベクトルに格納されます。

図 6.6 で前述したように、Sphere クラスの三角形インデックスの計算に注目してください。変数「prec(precision)」は「精度」を指し、ここでは球面スライスの数と各スライスの頂点の数を決定するために使用されます。テクスチャ マップは球の周囲を完全に包み込むため、テクスチャ マップの左端と右端が交差する各点に追加の一致する頂点が必要です。したがって、頂点の総数は (prec+1) * (prec+1) となります。各頂点は 6 つの三角形インデックスを生成するため、インデックスの総数は prec * prec * 6 になります。

プログラム 6.1 手続き的に生成された球体

ホストシェーダー.glsl

#version 430

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 tex_coord;
out vec2 tc;

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding=0) uniform sampler2D s;

void main(void)
{
    
    	gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
	tc = tex_coord;
}

fragShader.glsl

#version 430

in vec2 tc;
out vec4 color;

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding=0) uniform sampler2D s;

void main(void)
{
    
    	color = texture(s,tc);
}

Sphere クラス (Sphere.cpp)

#include <cmath>
#include <vector>
#include <iostream>
#include <glm\glm.hpp>
#include "Sphere.h"
using namespace std;

Sphere::Sphere() {
    
    
	init(48);
}

Sphere::Sphere(int prec) {
    
    
	init(prec);
}

float Sphere::toRadians(float degrees) {
    
     return (degrees * 2.0f * 3.14159f) / 360.0f; }//精度

void Sphere::init(int prec) {
    
    
	numVertices = (prec + 1) * (prec + 1);
	numIndices = prec * prec * 6;
	for (int i = 0; i < numVertices; i++) {
    
     vertices.push_back(glm::vec3()); }
	for (int i = 0; i < numVertices; i++) {
    
     texCoords.push_back(glm::vec2()); }
	for (int i = 0; i < numVertices; i++) {
    
     normals.push_back(glm::vec3()); }
	for (int i = 0; i < numVertices; i++) {
    
     tangents.push_back(glm::vec3()); }
	for (int i = 0; i < numIndices; i++) {
    
     indices.push_back(0); }

	// 计算三角形顶点
	for (int i = 0; i <= prec; i++) {
    
    
		for (int j = 0; j <= prec; j++) {
    
    
			float y = (float)cos(toRadians(180.0f - i * 180.0f / prec));
			float x = -(float)cos(toRadians(j*360.0f / prec))*(float)abs(cos(asin(y)));
			float z = (float)sin(toRadians(j*360.0f / (float)(prec)))*(float)abs(cos(asin(y)));
			vertices[i*(prec + 1) + j] = glm::vec3(x, y, z);
			texCoords[i*(prec + 1) + j] = glm::vec2(((float)j / prec), ((float)i / prec));
			normals[i*(prec + 1) + j] = glm::vec3(x, y, z);

			// 计算切向量
			if (((x == 0) && (y == 1) && (z == 0)) || ((x == 0) && (y == -1) && (z == 0))) {
    
    
				tangents[i*(prec + 1) + j] = glm::vec3(0.0f, 0.0f, -1.0f);
			}
			else {
    
    
				tangents[i*(prec + 1) + j] = glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(x, y, z));
			}
		}
	}
	// 计算三角形索引
	for (int i = 0; i<prec; i++) {
    
    
		for (int j = 0; j<prec; j++) {
    
    
			indices[6 * (i*prec + j) + 0] = i*(prec + 1) + j;
			indices[6 * (i*prec + j) + 1] = i*(prec + 1) + j + 1;
			indices[6 * (i*prec + j) + 2] = (i + 1)*(prec + 1) + j;
			indices[6 * (i*prec + j) + 3] = i*(prec + 1) + j + 1;
			indices[6 * (i*prec + j) + 4] = (i + 1)*(prec + 1) + j + 1;
			indices[6 * (i*prec + j) + 5] = (i + 1)*(prec + 1) + j;
		}
	}
}

int Sphere::getNumVertices() {
    
     return numVertices; }
int Sphere::getNumIndices() {
    
     return numIndices; }
std::vector<int> Sphere::getIndices() {
    
     return indices; }
std::vector<glm::vec3> Sphere::getVertices() {
    
     return vertices; }
std::vector<glm::vec2> Sphere::getTexCoords() {
    
     return texCoords; }
std::vector<glm::vec3> Sphere::getNormals() {
    
     return normals; }
std::vector<glm::vec3> Sphere::getTangents() {
    
     return tangents; }

球クラス (Sphere.h)

#include <cmath>
#include <vector>
#include <glm\glm.hpp>
class Sphere
{
    
    
private:
	int numVertices;
	int numIndices;
	std::vector<int> indices;
	std::vector<glm::vec3> vertices;
	std::vector<glm::vec2> texCoords;
	std::vector<glm::vec3> normals;
	std::vector<glm::vec3> tangents;
	void init(int);
	float toRadians(float degrees);

public:
	Sphere();
	Sphere(int prec);
	int getNumVertices();
	int getNumIndices();
	std::vector<int> getIndices();
	std::vector<glm::vec3> getVertices();
	std::vector<glm::vec2> getTexCoords();
	std::vector<glm::vec3> getNormals();
	std::vector<glm::vec3> getTangents();
};

main.cpp

#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <glm\glm.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Sphere.h"
#include "Utils.h"
using namespace std;

#define numVAOs 1
#define numVBOs 3

float cameraX, cameraY, cameraZ;
float sphLocX, sphLocY, sphLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint earthTexture;
float rotAmt = 0.0f;

// variable allocation for display
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;

Sphere mySphere = Sphere(48); //球形切片的数量和每个切片中的顶点数量 顶点的总数是(48+1) * (48+1) 索引的总数是prec * prec * 6

void setupVertices(void) {
    
    
	std::vector<int> ind = mySphere.getIndices();
	std::vector<glm::vec3> vert = mySphere.getVertices();
	std::vector<glm::vec2> tex = mySphere.getTexCoords();
	std::vector<glm::vec3> norm = mySphere.getNormals();

	std::vector<float> pvalues;
	std::vector<float> tvalues;
	std::vector<float> nvalues;

	int numIndices = mySphere.getNumIndices();
	for (int i = 0; i < numIndices; i++) {
    
    
		pvalues.push_back((vert[ind[i]]).x);
		pvalues.push_back((vert[ind[i]]).y);
		pvalues.push_back((vert[ind[i]]).z);
		tvalues.push_back((tex[ind[i]]).s);
		tvalues.push_back((tex[ind[i]]).t);
		nvalues.push_back((norm[ind[i]]).x);
		nvalues.push_back((norm[ind[i]]).y);
		nvalues.push_back((norm[ind[i]]).z);
	}

	glGenVertexArrays(1, vao);
	glBindVertexArray(vao[0]);
	glGenBuffers(numVBOs, vbo);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, pvalues.size()*4, &pvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, tvalues.size()*4, &tvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
	glBufferData(GL_ARRAY_BUFFER, nvalues.size()*4, &nvalues[0], GL_STATIC_DRAW);
}

void init(GLFWwindow* window) {
    
    
	renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
	cameraX = 0.0f; cameraY = 0.0f; cameraZ = 2.0f;
	sphLocX = 0.0f; sphLocY = 0.0f; sphLocZ = -1.0f;

	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);

	setupVertices();
	earthTexture = Utils::loadTexture("earth.jpg");
}

void display(GLFWwindow* window, double currentTime) {
    
    
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(renderingProgram);

	mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");

	vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
	mMat = glm::translate(glm::mat4(1.0f), glm::vec3(sphLocX, sphLocY, sphLocZ));
	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]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, earthTexture);

	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);

	glDrawArrays(GL_TRIANGLES, 0, mySphere.getNumIndices());
}

void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
    
    
	aspect = (float)newWidth / (float)newHeight;
	glViewport(0, 0, newWidth, newHeight);
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}

int main(void) {
    
    
	if (!glfwInit()) {
    
     exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter6 - program1", NULL, NULL);
	glfwMakeContextCurrent(window);
	if (glewInit() != GLEW_OK) {
    
     exit(EXIT_FAILURE); }
	glfwSwapInterval(1);

	glfwSetWindowSizeCallback(window, window_size_callback);

	init(window);

	while (!glfwWindowShouldClose(window)) {
    
    
		display(window, glfwGetTime());
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

Sphere クラスを使用する場合、各頂点位置と法線ベクトルには 3 つの値が必要ですが、各テクスチャ座標には 2 つの値だけが必要です。これは、後でデータがバッファにロードされる Sphere.h ファイルに示されているベクトル (頂点、texCoords、および法線) の宣言に反映されています。

球の構築中にインデックスが使用されますが、VBO に保存される最終的な球の頂点データにはインデックスが使用されないことに注意してください。代わりに、setupVertices() が球インデックスをループするときに、インデックス エントリごとに VBO に個別の (多くの場合冗長な) 頂点エントリが生成されます。 OpenGL には頂点データにインデックスを付けるメカニズムがあります。簡単にするために、この例ではそれを使用しませんが、次の例では OpenGL のインデックスを使用します。

幾何学的形状から現実世界のオブジェクトに至るまで、このプログラムを使用して他の多くのモデルを作成できます。最も有名なものの 1 つは、1975 年に Martin Newell によって開発され、さまざまなベジェ曲線と曲面を使用した「ユタ ティーポット」[CH16] です。 OpenGL Utility Toolkit (または「GLUT」) [GL16] には、ティーポットを描画するためのプログラムも含まれています (図 6.7 を参照)。この本では GLUT については説明しませんが、ベジェ曲面については第 11 章で説明します。

ここに画像の説明を挿入します

図 6.7 OpenGL GLUT ティーポット

6.2 OpenGL インデックス - トーラスの構築

6.2.1 トーラス

トーラスを生成するアルゴリズムは、さまざまな Web サイトで見つけることができます。 Paul Baker は、円形のスライスを定義し、そのスライスを円の周りで回転させてトーラスを形成する方法を段階的に説明しています [PP07]。図 6.8 は、側面と上面の 2 つの図を示しています。
ここに画像の説明を挿入します

図6.8 トーラスの構築

トーラスの頂点位置を生成する方法は、球体を構築する方法とは大きく異なります。トーラスの場合、アルゴリズムは頂点を原点の右側に配置し、この頂点を XY 平面上で円を描くように Z 軸を中心に回転させて「リング」を形成します。次に、リングを「内側の半径」の距離だけ「外側」に移動します。これらの頂点を構築するとき、テクスチャ座標と法線ベクトルが頂点ごとに計算されます。トーラス面に接するベクトル (接線ベクトルと呼ばれます) が各頂点に対して追加で生成されます。

最初のリングを Y 軸を中心に回転させると、トーラスの形成に使用される他のリングの頂点が形成されます。軸は、元のリングの接線ベクトルと法線ベクトルを回転して、結果の各頂点の接線ベクトルと法線ベクトルを計算します。頂点が作成された後、すべての頂点がリングごとに走査され、頂点ごとに 2 つの三角形が生成されます。 2 つの三角形の 6 つのインデックス テーブル エントリは、前の球と同様の方法で生成されます。

残りのリングのテクスチャ座標を選択するための戦略は、テクスチャ イメージの S 軸がトーラスの水平周囲の半分を囲むようにリングを配置し、残りの半分についても同じことを繰り返すことです。 Y 軸を中心に回転してリングを生成するときは、1 から始まり、指定された精度 (ここでも「prec」と呼ばれます) まで増加する変数リングを指定します。次に、S のテクスチャ座標値を Ring*2.0/prec に設定して、S の範囲が 0.0 ~ 2.0 になるようにし、テクスチャ座標が 1.0 より大きい場合は常に 1.0 を減算します。このアプローチの動機は、水平方向のテクスチャ イメージの過度の「ストレッチ」を回避することです。逆に、本当にテクスチャをトーラスの周りに完全に伸ばしたい場合は、テクスチャ座標の計算から「*2.0」乗数を削除するだけです。

C++/OpenGL での Torus クラスの構築は、Sphere クラスとほぼ同じ方法で行うことができます。ただし、OpenGL の頂点インデックスのサポートを利用して、トーラスを構築するときに作成するインデックスを活用する機会があります (これを球体に対して行うこともできましたが、実行しませんでした)。 数千の頂点を持つ非常に大規模なモデルの場合、OpenGL インデックスを使用するとパフォーマンスが向上するため、その方法について説明します。

6.2.2 OpenGL でのインデックス作成

球体モデルとトーラス モデルでは、頂点配列を参照する整数インデックスの配列を生成します。球の場合、前の章の例で行ったように、インデックス付きリストを使用して個々の頂点の完全なセットを構築し、それらを VBO にロードします。トーラスをインスタンス化し、その頂点、法線ベクトルなどをバッファにロードすることは、プログラム 6.1 と同様の方法で実行できますが、OpenGL のインデックスを使用します。

OpenGL インデックスを使用する場合、インデックス自体を VBO にロードする必要もあります。追加の VBO を生成してインデックスを保存します。 各インデックス値は単なる整数参照であるため、まずインデックス配列を整数の C++ ベクトルにコピーし、次に glBufferData() を使用してベクトルをnew 追加された VBO で、VBO タイプを GL_ELEMENT_ARRAY_BUFFER として指定します (これにより、この VBO にインデックスが含まれることが OpenGL に伝えられます)。これを行うコードはsetupVertices(): に追加できます。
ここに画像の説明を挿入します

display() メソッドでは、glDrawArrays() 呼び出しを glDrawElements() 呼び出しに置き換えます。これにより、頂点を見つけるためにインデックス VBO を使用するように OpenGL に指示します。描く。また、 glBindBuffer() を使用してインデックスを含む VBO を有効にし、インデックスを含む VBO と GL_ELEMENT_ARRAY_BUFFER类型 を指定します。コードは次のとおりです。

ここに画像の説明を挿入します

興味深いことに、インデックス作成を実装するために C++/OpenGL アプリケーションに変更を加えたにもかかわらず、球の描画に使用されたシェーダーは変更せずにトーラスに対して引き続き機能しました。 OpenGL は GL_ELEMENT_ARRAY_BUFFER の存在を認識し、それを使用して頂点属性にアクセスできます。

プログラム 6.2 は、Baker に基づいて実装された Torus と呼ばれるクラスを示しています。「内側」変数と「外側」変数は、図 6.9 の対応する内側半径と外側半径を参照します。 prec 変数には、頂点とインデックスの数が同様に計算され、球と同様の効果があります。対照的に、法線ベクトルの決定は、球を使用するよりもはるかに複雑です。 Baker の説明にある戦略を使用しました。この戦略では、2 つの接線ベクトル (Baker では sTangent および tTangent と呼ばれますが、通常は「接線」および「複接線」と呼ばれます) が計算され、それらの外積が法線ベクトルを形成します。

本書の残りの部分では、このトーラス クラスを (前述の球体クラスとともに) 多くの例で使用します。

プログラム 6.2 手続き的に生成されたトーラス
vertShader.glsl

#version 430

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 tex_coord;
out vec2 tc;

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding=0) uniform sampler2D s;

void main(void)
{
    
    	gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
	tc = tex_coord;
}

fragShader.glsl

#version 430

in vec2 tc;
out vec4 color;

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding=0) uniform sampler2D s;

void main(void)
{
    
    
	color = texture(s,tc);
}

急ぐこと

#include <cmath>
#include <vector>
#include <glm\glm.hpp>
class Torus
{
    
    
private:
	int numVertices;
	int numIndices;
	int prec;
	float inner;
	float outer;
	std::vector<int> indices;
	std::vector<glm::vec3> vertices;
	std::vector<glm::vec2> texCoords;
	std::vector<glm::vec3> normals;
	std::vector<glm::vec3> sTangents;
	std::vector<glm::vec3> tTangents;
	void init();
	float toRadians(float degrees);

public:
	Torus();
	Torus(float inner, float outer, int prec);
	int getNumVertices();
	int getNumIndices();
	std::vector<int> getIndices();
	std::vector<glm::vec3> getVertices();
	std::vector<glm::vec2> getTexCoords();
	std::vector<glm::vec3> getNormals();
	std::vector<glm::vec3> getStangents();
	std::vector<glm::vec3> getTtangents();
};

トーラス.cpp

#include <cmath>
#include <vector>
#include <iostream>
#include <glm\gtc\matrix_transform.hpp>
#include "Torus.h"
using namespace std;

Torus::Torus() {
    
    
	prec = 48;//精度
	inner = 0.5f;//内半径
	outer = 0.2f;//外半径
	init();
}

Torus::Torus(float in, float out, int precIn) {
    
    
	prec = precIn;
	inner = in;
	outer = out;
	init();
}

float Torus::toRadians(float degrees) {
    
     return (degrees * 2.0f * 3.14159f) / 360.0f; }

void Torus::init() {
    
    
	numVertices = (prec + 1) * (prec + 1);
	numIndices = prec * prec * 6;
	for (int i = 0; i < numVertices; i++) {
    
     vertices.push_back(glm::vec3()); }
	for (int i = 0; i < numVertices; i++) {
    
     texCoords.push_back(glm::vec2()); }
	for (int i = 0; i < numVertices; i++) {
    
     normals.push_back(glm::vec3()); }
	for (int i = 0; i < numVertices; i++) {
    
     sTangents.push_back(glm::vec3()); }
	for (int i = 0; i < numVertices; i++) {
    
     tTangents.push_back(glm::vec3()); }
	for (int i = 0; i < numIndices; i++) {
    
     indices.push_back(0); }

	//计算第一个圆环
	for (int i = 0; i < prec + 1; i++) {
    
    
		float amt = toRadians(i*360.0f / prec);

		glm::mat4 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 0.0f, 1.0f));
		glm::vec3 initPos(rMat * glm::vec4(outer, 0.0f, 0.0f, 1.0f));

		vertices[i] = glm::vec3(initPos + glm::vec3(inner, 0.0f, 0.0f));
		// 为环上的每个顶点计算纹理坐标
		texCoords[i] = glm::vec2(0.0f, ((float)i / (float)prec));

		rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 0.0f, 1.0f));
		//计算了两个切向量(Baker称为sTangent和tTangent,尽管通常称为“切向量(tangent)”和“副切向量(bitangent)”),它们的叉乘积形成法向量
		tTangents[i] = glm::vec3(rMat * glm::vec4(0.0f, -1.0f, 0.0f, 1.0f));// 计算切向量和法向量,第一个切向量是绕Z轴旋转的Y轴

		sTangents[i] = glm::vec3(glm::vec3(0.0f, 0.0f, -1.0f));// 第二个切向量是 -Z 轴
		normals[i] = glm::cross(tTangents[i], sTangents[i]);// 它们的叉乘积就是法向量
	}
	// 绕原点旋转点,形成环,然后将它们向外移动
	for (int ring = 1; ring < prec + 1; ring++) {
    
    
		for (int i = 0; i < prec + 1; i++) {
    
    
			// 绕Y轴旋转最初那个环的顶点坐标
			float amt = (float)toRadians((float)ring * 360.0f / (prec));

			glm::mat4 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
			vertices[ring*(prec + 1) + i] = glm::vec3(rMat * glm::vec4(vertices[i], 1.0f));
			// 计算新环顶点的纹理坐标
			texCoords[ring*(prec + 1) + i] = glm::vec2((float)ring*2.0f / (float)prec, texCoords[i].t);
			if (texCoords[ring*(prec + 1) + i].s > 1.0) texCoords[ring*(prec+1)+i].s -= 1.0f;
			// 绕Y轴旋转切向量和副切向量
			rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
			sTangents[ring*(prec + 1) + i] = glm::vec3(rMat * glm::vec4(sTangents[i], 1.0f));
			// 绕Y轴旋转法向量
			rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
			tTangents[ring*(prec + 1) + i] = glm::vec3(rMat * glm::vec4(tTangents[i], 1.0f));
			
			rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
			normals[ring*(prec + 1) + i] = glm::vec3(rMat * glm::vec4(normals[i], 1.0f));
		}
	}
	// 按照逐个顶点的两个三角形,计算三角形索引
	for (int ring = 0; ring < prec; ring++) {
    
    
		for (int i = 0; i < prec; i++) {
    
    
			indices[((ring*prec + i) * 2) * 3 + 0] = ring*(prec + 1) + i;
			indices[((ring*prec + i) * 2) * 3 + 1] = (ring + 1)*(prec + 1) + i;
			indices[((ring*prec + i) * 2) * 3 + 2] = ring*(prec + 1) + i + 1;
			indices[((ring*prec + i) * 2 + 1) * 3 + 0] = ring*(prec + 1) + i + 1;
			indices[((ring*prec + i) * 2 + 1) * 3 + 1] = (ring + 1)*(prec + 1) + i;
			indices[((ring*prec + i) * 2 + 1) * 3 + 2] = (ring + 1)*(prec + 1) + i + 1;
		}
	}
}
// 环面索引和顶点的访问函数
int Torus::getNumVertices() {
    
     return numVertices; }
int Torus::getNumIndices() {
    
     return numIndices; }
std::vector<int> Torus::getIndices() {
    
     return indices; }
std::vector<glm::vec3> Torus::getVertices() {
    
     return vertices; }
std::vector<glm::vec2> Torus::getTexCoords() {
    
     return texCoords; }
std::vector<glm::vec3> Torus::getNormals() {
    
     return normals; }
std::vector<glm::vec3> Torus::getStangents() {
    
     return sTangents; }
std::vector<glm::vec3> Torus::getTtangents() {
    
     return tTangents; }

main.cpp

#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Torus.h"
#include "Utils.h"
using namespace std;

float toRadians(float degrees) {
    
     return (degrees * 2.0f * 3.14159f) / 360.0f; }

#define numVAOs 1
#define numVBOs 4

float cameraX, cameraY, cameraZ;
float torLocX, torLocY, torLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint brickTexture;
float rotAmt = 0.0f;

// variable allocation for display
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;

Torus myTorus(0.5f, 0.2f, 48);

void setupVertices(void) {
    
    
	std::vector<int> ind = myTorus.getIndices();//将索引数组复制到整型的C++向量
	std::vector<glm::vec3> vert = myTorus.getVertices();
	std::vector<glm::vec2> tex = myTorus.getTexCoords();
	std::vector<glm::vec3> norm = myTorus.getNormals();

	std::vector<float> pvalues;
	std::vector<float> tvalues;
	std::vector<float> nvalues;

	for (int i = 0; i < myTorus.getNumVertices(); i++) {
    
    
		pvalues.push_back(vert[i].x);
		pvalues.push_back(vert[i].y);
		pvalues.push_back(vert[i].z);
		tvalues.push_back(tex[i].s);
		tvalues.push_back(tex[i].t);
		nvalues.push_back(norm[i].x);
		nvalues.push_back(norm[i].y);
		nvalues.push_back(norm[i].z);
	}
	glGenVertexArrays(1, vao);
	glBindVertexArray(vao[0]);
	glGenBuffers(numVBOs, vbo);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);//这里为什么要*4

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
	glBufferData(GL_ARRAY_BUFFER, nvalues.size() * 4, &nvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]); //使用OpenGL索引时,需要将索引本身加载到VBO中。生成一个额外的VBO用于保存索引
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * 4, &ind[0], GL_STATIC_DRAW);//将向量加载到新增的VBO 指定VBO的类型为 `GL_ELEMENT_ARRAY_BUFFER`(告诉OpenGL这个VBO包含索引)
}

void init(GLFWwindow* window) {
    
    
	renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
	cameraX = 0.0f; cameraY = 0.0f; cameraZ = 2.0f;
	torLocX = 0.0f; torLocY = 0.0f; torLocZ = -0.5f;

	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);

	setupVertices();
	brickTexture = Utils::loadTexture("brick1.jpg");
}

void display(GLFWwindow* window, double currentTime) {
    
    
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(renderingProgram);

	mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");

	vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
	mMat = glm::translate(glm::mat4(1.0f), glm::vec3(torLocX, torLocY, torLocZ));
	//mMat *= glm::eulerAngleXYZ(toRadians(30.0f), 0.0f, 0.0f);
	mMat = glm::rotate(mMat, toRadians(30.0f), glm::vec3(1.0f, 0.0f, 0.0f));

	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]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, brickTexture);

	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);//启用包含索引的VBO,指定哪个VBO包含索引并且是`GL_ELEMENT_ARRAY_BUFFER类型
	glDrawElements(GL_TRIANGLES, myTorus.getIndices().size(), GL_UNSIGNED_INT, 0);//将`glDrawArrays()`调用替换为 `glDrawElements()`调用 告诉OpenGL利用索引VBO来查找要绘制的顶点
}

void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
    
    
	aspect = (float)newWidth / (float)newHeight;
	glViewport(0, 0, newWidth, newHeight);
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}

int main(void) {
    
    
	if (!glfwInit()) {
    
     exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(800, 800, "Chapter6 - program2", NULL, NULL);
	glfwMakeContextCurrent(window);
	if (glewInit() != GLEW_OK) {
    
     exit(EXIT_FAILURE); }
	glfwSwapInterval(1);

	glfwSetWindowSizeCallback(window, window_size_callback);

	init(window);

	while (!glfwWindowShouldClose(window)) {
    
    
		display(window, glfwGetTime());
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

Torus クラスを使用するコードでは、setupVertices() のループは、(球の例の場合のように) インデックス エントリごとに 1 回ではなく、各頂点に関連付けられたデータを 1 回だけ保存するようになりました。この違いは、VBOに入力するデータの配列宣言サイズにも反映されます。また、トーラスの例では、頂点データを取得するときにインデックス値を使用するのではなく、単に VBO #3 に直接ロードされることにも注意してください。この VBO は GL_ELEMENT_ARRAY_BUFFER として指定されているため、OpenGL はこの VBO に頂点インデックスが含まれていることを認識します。

図 6.9 は、トーラスをインスタンス化し、レンガ テクスチャでテクスチャリングした結果を示しています。

ここに画像の説明を挿入します

図6.9 手続き的に生成されたトーラス

6.3 外部で構築されたモデルのロード

ビデオ ゲームやコンピューター生成映画のキャラクターなどの複雑な 3D モデルは、多くの場合、モデリング ツールを使用して生成されます。この「DCC」(デジタルコンテンツ作成)ツールは、アーティストなどの人々が3D空間上に任意の形状を構築し、頂点、テクスチャ座標、頂点法線ベクトルなどを自動生成することを可能にします。このようなツールは多すぎてここにすべてをリストすることはできません。いくつかの例としては、MAYA、Blender、Lightwave、Cinema4D などが挙げられます。 Blender は無料でオープンソースです。図6.10に3Dモデル編集時のBlender画面例を示します。
ここに画像の説明を挿入します

図6.10 Blenderモデル作成例【BL16】

DCC ツールを使用して作成したモデルを OpenGL シーンで機能させるには、プログラムに読み込む (インポート) ことができる形式でモデルを保存 (エクスポート) する必要があります。標準の 3D モデル ファイル形式がいくつかあります。ここでも多すぎてリストできませんが、参考までに、Wavefront (.obj)、3D Studio Max (.3ds)、Stanford Scan Repository (.ply)、Ogre3D (.mesh) などの例を示します。その中で最も単純なものは Wavefront (OBJ と呼ばれることが多い) なので、これについて詳しく説明します。

OBJ ファイルはシンプルなので、基本的なインポーターを比較的簡単に開発できます。 OBJ ファイルでは、頂点ジオメトリ データ、テクスチャ座標、法線ベクトル、およびその他の情報がテキスト行として指定されます。これにはいくつかの制限があります。たとえば、OBJ ファイルではモデル アニメーションを指定できません。

OBJ ファイル内の、その行のデータの種類を示す文字マークで始まる行。一般的なタグには次のようなものがあります。

  • v ジオメトリ (頂点位置) データ。
  • vt-テクスチャ座標;
  • vn 頂点法線ベクトル。
  • f 面 (通常は三角形の頂点)。

オブジェクト名、使用されるマテリアル、曲線、影、その他多くの詳細を保存するために使用できるタグは他にもあります。ここでは、さまざまな複雑なモデルをインポートするのに十分な、上記の 4 つのタグについてのみ説明します。

Blender を使用して、プログラム 4.3 用に開発したような単純なピラミッドを構築するとします。図 6.11 は、Blender で作成された同様のピラミッドのスクリーンショットです。

ここに画像の説明を挿入します

図6.11 Blenderで構築されたピラミッド

Blender でピラミッド モデルをエクスポートし、.obj 形式を指定し、テクスチャ座標と頂点法線ベクトルを出力するように Blender を設定すると、これらすべての情報を含む OBJ ファイルが作成されます。生成されたOBJファイルを図6.12に示します。 (テクスチャ座標の実際の値はモデルの構築方法によって異なる場合があります。)
ここに画像の説明を挿入します

図 6.12 Pyramid によってエクスポートされた OBJ ファイル

参照用に、OBJ ファイルの重要な部分を色分けしました。 先頭の「#」で始まる行は、Blender によって配置されたコメントであり、インポーターによって無視されます。次は、オブジェクトの名前を示す「o」で始まる行です。弊社の輸入者はこの行を無視することもできます。その後に、表面を滑らかにしてはならないことを指定する「s」で始まる行があります。このコードでは、「s」で始まる行も無視されます。

実際のコンテンツ行を含む OBJ ファイルの最初の部分は、「v」で始まる部分 (4 行目から 8 行目) です。これらは、原点を基準としたピラミッド モデルの 5 つの頂点の X Y および Z ローカル空間座標を指定します。ここでは、原点はピラミッドの中心にあります。

赤色の値 (「vt」で始まる) はさまざまなテクスチャ座標です。 テクスチャ座標リストが頂点リストよりも長い理由は、一部の頂点が複数の三角形に参加しており、これらの場合には異なるテクスチャ座標が使用される可能性があるためです。

緑色の値 (「vn」で始まる) はさまざまな法線ベクトルです。 このリストも通常、頂点リストよりも長くなります (この例では異なりますが)。これもまた、いくつかの頂点が複数の三角形に参加しており、そのような場合には異なる法線ベクトルが使用される可能性があるためです。

ファイルの下部近くにある紫色でマークされた値 (「f」で始まる) は、三角形 (つまり「面」) を指定します。この例では、各面 (三角形) に 3 つの要素があり、各要素には「/」で区切られた 3 つの値があります (OBJ では他の形式も可能です)。各要素の値は、それぞれ頂点リスト、テクスチャ座標、法線ベクトルのインデックスです。

例:3 番目の面は次のとおりです: f 2 / 7 / 3 5 / 8 / 3 3 / 9 / 3 これは、頂点リストの 2 番目、5 番目、および3 番目の頂点 (青) は三角形を形成します (OBJ インデックスは 1 から始まることに注意してください)。対応するテクスチャ座標は、赤いセクションのテクスチャ座標リストの項目 7、8、および 9 です。 3 つの頂点はすべて同じ法線ベクトルを持ちます。これは、緑色で示されている法線ベクトル リストの 3 番目の項目です。

OBJ 形式のモデルには法線ベクトルやテクスチャ座標さえも必要ありません。モデルにテクスチャ座標または法線ベクトルがない場合、面の数値は頂点インデックスのみを指定します。

f 2 5 3

モデルにテクスチャ座標はあるが法線ベクトルがない場合、形式は次のようになります。

f 2 / 7 5 / 8 3 / 9

モデルに法線ベクトルはあるがテクスチャ座標がない場合、形式は次のとおりです:
f 2 / / 3 5 / / 3 3 / / 3

モデルに数万の頂点があることは珍しいことではありません。これらのモデルは、動物、建物、車、飛行機、神話上の生き物、人物など、考えられるほぼすべての用途に合わせてインターネット上でダウンロードして利用できます。

インターネット上では、さまざまな複雑さの OBJ モデルをインポートできるさまざまなインポート プログラムが提供されています。これまでに見た基本的なタグ (v、vt、vn、f) を処理する非常に単純な OBJ ローダー関数を作成するのは難しくありません。プログラム 6.3 は、そのようなローダーの 1 つを示していますが、機能が非常に限定されています。これには、インポートされたモデルを保持するクラスが含まれており、このクラスがインポーターを呼び出します。

単純な OBJ インポーターのコードを説明する前に、 読者にその制限について警告する必要があります。

  • 3 つの顔属性フィールドすべてを含むモデルのみをサポートします。つまり、頂点位置、テクスチャ座標、法線ベクトルはすべて f #/#/# #/#/# #/#/# の形式で存在する必要があります。
  • マテリアル タグは無視されます。テクスチャリングは、第 5 章で説明されている方法を使用して実行する必要があります。
  • 単一の三角形メッシュで構成される OBJ モデルのみがサポートされます (OBJ 形式は複合モデルをサポートしますが、単純なインポーターはサポートしません) (複合モデル ローダーはこれを参照できます)。
  • 各行の要素はスペース 1 つだけで区切られていると仮定します。

https://blog.csdn.net/hyy_suit_yuan/article/details/104885628?spm=1001.2014.3001.5506
https://blog.csdn.net/qinyuanpei/article /details/49991607?spm=1001.2014.3001.5506

OBJ モデルが上記の基準をすべて満たしていない場合でも、プログラム 6.3 のシンプル ローダーを使用してインポートすることは可能です。通常、このようなモデルを Blender にロードし、ローダーの制約を満たす別の OBJ ファイルにエクスポートすることが可能です。たとえば、モデルに法線ベクトルが含まれていない場合、変更した OBJ ファイルをエクスポートするときに Blender に法線ベクトルを生成させることができます。

OBJ ローダーのもう 1 つの制限は、インデックス作成に関係しています。 前の説明で述べたように、「f」タグを使用すると、頂点位置、テクスチャ座標、法線ベクトルを組み合わせて一致させることができます。たとえば、2 つの異なる「face」行に、同じ v エントリではあるが異なる vt エントリを指すインデックスが含まれる場合があります。残念ながら、OpenGL のインデックス作成メカニズムはこの柔軟性をサポートしていません。OpenGL のインデックス エントリは、特定の頂点とその属性のみを指すことができます。 三角形の面のエントリからインデックス配列に参照を単純にコピーすることができないため、OBJ モデル ローダーの作成がある程度複雑になります。対照的に、OpenGL インデックスを使用するには、noodle v、vt、および vn 値の組み合わせ全体がインデックス配列内に独自の参照を持っていることを確認する必要があります。より単純ですが効率は劣りますが、三角形の面のエントリごとに新しい頂点を作成する方法もあります。 OpenGL インデックスの使用にはスペースを節約できるという利点がありますが (特に大規模なモデルをロードする場合)、わかりやすくするためにこの単純なアプローチを選択します。

ModelImporter クラスには parseOBJ() 関数が含まれています。この関数は OBJ ファイルを 1 行ずつ読み取り、v、vt、vn、f の 4 つのケースをそれぞれ処理します。いずれの場合も、行上の後続の数値が抽出されます。最初に Erase() を使用して最初の v、vt、vn、または f 文字をスキップし、次に C++ stringstream クラスの ">>" 演算子を使用してそれぞれを抽出します。後続の引数の値を C++ 浮動小数点ベクトルに格納します。面 (f) エントリを処理する場合、頂点は、頂点位置、テクスチャ座標、法線ベクトルを含む C++ 浮動小数点ベクトルの対応するエントリを使用して構築されます。

ModelImporter クラスと ippedModel クラスは同じファイルに含まれており、インポートされた頂点を vec2 および vec3 オブジェクトのベクトルに配置することで、OBJ ファイルの頂点の読み込みとアクセスのプロセスを簡素化します。これらの GLM クラスを思い出してください。ここでは、頂点位置、テクスチャ座標、および法線ベクトルを保存するためにこれらのクラスを使用します。インポートされたモデル クラスの読み取り関数は、Sphere クラスや Torus クラスと同じ方法で C++/OpenGL アプリケーションで使用できるようにします。

ModelImporter クラスと ownedModel クラスの後には、OBJ ファイルをロードし、その後のレンダリングのために頂点情報を一連の VBO に転送する一連の呼び出し例が続きます。

図 6.13 は、NASA Web サイト [NA16] からダウンロードされ、プログラム 6.3 のコードを使用してインポートされ、プログラム 5.1 のコードと、異方性フィルタリング テクスチャリングを備えた対応する NASA テクスチャ イメージ ファイルを使用してレンダリングされた、OBJ 形式のスペース シャトル レンダリング モデルを示しています。このテクスチャ イメージは、モデル内のテクスチャ座標がテクスチャ イメージの特定の領域に注意深くマッピングされている UV マッピングの使用例です。 (第 5 章で説明したように、UV マッピングの詳細は本書の範囲外です。)
ここに画像の説明を挿入します

図 6.13 テクスチャ付きの NASA スペースシャトル モデル

プログラム 6.3 簡素化された (制限された) OBJ ローダー
vertShader.glsl

#version 430

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 tex_coord;
out vec2 tc;

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding=0) uniform sampler2D s;

void main(void)
{
    
    	gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
	tc = tex_coord;
}

fragShader.glsl

#version 430

in vec2 tc;
out vec4 color;

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding=0) uniform sampler2D s;

void main(void)
{
    
    
	color = texture(s,tc);
}

インポートされたModel.h

#include <vector>
//通过将导入的顶点放入vec2和vec3对象的向量中,简化了加载和访问OBJ文件顶点的过程
class ImportedModel
{
    
    
private:
	// 从OBJ文件读取的数值
	int numVertices;
	// 保存为顶点属性以供后续使用的数值
	std::vector<glm::vec3> vertices;
	std::vector<glm::vec2> texCoords;
	std::vector<glm::vec3> normalVecs;
public:
	ImportedModel();
	ImportedModel(const char *filePath);
	int getNumVertices();
	std::vector<glm::vec3> getVertices();
	std::vector<glm::vec2> getTextureCoords();
	std::vector<glm::vec3> getNormals();
};

class ModelImporter
{
    
    
private:
	std::vector<float> vertVals;
	std::vector<float> triangleVerts;
	std::vector<float> textureCoords;
	std::vector<float> stVals;
	std::vector<float> normals;
	std::vector<float> normVals;
public:
	ModelImporter();
	void parseOBJ(const char *filePath);//逐行读取OBJ文 件,分别处理v、vt、vn和f这4种情况
	int getNumVertices();
	std::vector<float> getVertices();
	std::vector<float> getTextureCoordinates();
	std::vector<float> getNormals();
};

インポートされたモデル.cpp

#include <fstream>
#include <sstream>
#include <glm\glm.hpp>
#include "ImportedModel.h"
using namespace std;

ImportedModel::ImportedModel() {
    
    }

ImportedModel::ImportedModel(const char *filePath) {
    
    
	ModelImporter modelImporter = ModelImporter();
	modelImporter.parseOBJ(filePath); 使用modelImporter获取顶点信息
	numVertices = modelImporter.getNumVertices();
	std::vector<float> verts = modelImporter.getVertices();
	std::vector<float> tcs = modelImporter.getTextureCoordinates();
	std::vector<float> normals = modelImporter.getNormals();

	for (int i = 0; i < numVertices; i++) {
    
    
		vertices.push_back(glm::vec3(verts[i*3], verts[i*3+1], verts[i*3+2]));
		texCoords.push_back(glm::vec2(tcs[i*2], tcs[i*2+1]));
		normalVecs.push_back(glm::vec3(normals[i*3], normals[i*3+1], normals[i*3+2]));
	}
}

int ImportedModel::getNumVertices() {
    
     return numVertices; }
std::vector<glm::vec3> ImportedModel::getVertices() {
    
     return vertices; }
std::vector<glm::vec2> ImportedModel::getTextureCoords() {
    
     return texCoords; }
std::vector<glm::vec3> ImportedModel::getNormals() {
    
     return normalVecs; }

// ---------------------------------------------------------------

ModelImporter::ModelImporter() {
    
    }

//逐行读取OBJ文 件,分别处理v、vt、vn和f这4种情况
void ModelImporter::parseOBJ(const char *filePath) {
    
    
	float x, y, z;
	string content;
	ifstream fileStream(filePath, ios::in);
	string line = "";
	while (!fileStream.eof()) {
    
    
		getline(fileStream, line);
		if (line.compare(0, 2, "v ") == 0) {
    
    // 顶点位置("v"的情况)
			stringstream ss(line.erase(0, 1)); //使用erase()跳过初始的v、vt、vn或f字符
			ss >> x; ss >> y; ss >> z;// 提取顶点位置数值
			vertVals.push_back(x);
			vertVals.push_back(y);
			vertVals.push_back(z);
		}
		if (line.compare(0, 2, "vt") == 0) {
    
    // 纹理坐标
			stringstream ss(line.erase(0, 2));// 提取纹理坐标数值
			ss >> x; ss >> y;
			stVals.push_back(x);
			stVals.push_back(y);
		}
		if (line.compare(0, 2, "vn") == 0) {
    
    // 顶点法向量
			stringstream ss(line.erase(0, 2));// 提取法向量数值
			ss >> x; ss >> y; ss >> z;
			normVals.push_back(x);
			normVals.push_back(y);
			normVals.push_back(z);
		}
		if (line.compare(0, 2, "f ") == 0) {
    
    // 三角形面("f"的情况)
			string oneCorner, v, t, n;
			stringstream ss(line.erase(0, 2));
			for (int i = 0; i < 3; i++) {
    
    
				getline(ss, oneCorner, ' ');// 提取三角形面引用
				stringstream oneCornerSS(oneCorner);
				getline(oneCornerSS, v, '/');
				getline(oneCornerSS, t, '/');
				getline(oneCornerSS, n, '/');

				int vertRef = (stoi(v) - 1) * 3;// "stoi"将字符串转化为整型
				int tcRef = (stoi(t) - 1) * 2;
				int normRef = (stoi(n) - 1) * 3;
				// 构建顶点向量
				triangleVerts.push_back(vertVals[vertRef]);
				triangleVerts.push_back(vertVals[vertRef + 1]);
				triangleVerts.push_back(vertVals[vertRef + 2]);
				// 构建纹理坐标向量
				textureCoords.push_back(stVals[tcRef]);
				textureCoords.push_back(stVals[tcRef + 1]);
				// 法向量的向量
				normals.push_back(normVals[normRef]);
				normals.push_back(normVals[normRef + 1]);
				normals.push_back(normVals[normRef + 2]);
			}
		}
	}
}
int ModelImporter::getNumVertices() {
    
     return (triangleVerts.size()/3); }
std::vector<float> ModelImporter::getVertices() {
    
     return triangleVerts; }
std::vector<float> ModelImporter::getTextureCoordinates() {
    
     return textureCoords; }
std::vector<float> ModelImporter::getNormals() {
    
     return normals; }

main.cpp

#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <glm\glm.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "ImportedModel.h"
#include "Utils.h"
using namespace std;

#define numVAOs 1
#define numVBOs 3

float cameraX, cameraY, cameraZ;
float objLocX, objLocY, objLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint shuttleTexture;

// variable allocation for display
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;
// 在顶层声明中使用模型导入器
ImportedModel myModel("shuttle.obj");

float toRadians(float degrees) {
    
     return (degrees * 2.0f * 3.14159f) / 360.0f; }

void setupVertices(void) {
    
    
 	std::vector<glm::vec3> vert = myModel.getVertices();
	std::vector<glm::vec2> tex = myModel.getTextureCoords();
	std::vector<glm::vec3> norm = myModel.getNormals();

	std::vector<float> pvalues;
	std::vector<float> tvalues;
	std::vector<float> nvalues;

	for (int i = 0; i < myModel.getNumVertices(); i++) {
    
    
		pvalues.push_back((vert[i]).x);
		pvalues.push_back((vert[i]).y);
		pvalues.push_back((vert[i]).z);
		tvalues.push_back((tex[i]).s);
		tvalues.push_back((tex[i]).t);
		nvalues.push_back((norm[i]).x);
		nvalues.push_back((norm[i]).y);
		nvalues.push_back((norm[i]).z);
	}

	glGenVertexArrays(1, vao);
	glBindVertexArray(vao[0]);
	glGenBuffers(numVBOs, vbo);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
	glBufferData(GL_ARRAY_BUFFER, nvalues.size() * 4, &nvalues[0], GL_STATIC_DRAW);
}

void init(GLFWwindow* window) {
    
    
	renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
	cameraX = 0.0f; cameraY = 0.0f; cameraZ = 1.5f;
	objLocX = 0.0f; objLocY = 0.0f; objLocZ = 0.0f;

	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);

	setupVertices();
	shuttleTexture = Utils::loadTexture("spstob_1.jpg");
}

void display(GLFWwindow* window, double currentTime) {
    
    
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(renderingProgram);

	mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");

	vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
	mMat = glm::translate(glm::mat4(1.0f), glm::vec3(objLocX, objLocY, objLocZ));

	mMat = glm::rotate(mMat, 0.0f, glm::vec3(1.0f, 0.0f, 0.0f));
	mMat = glm::rotate(mMat, toRadians(135.0f), glm::vec3(0.0f, 1.0f, 0.0f));
	mMat = glm::rotate(mMat, toRadians(35.0f), glm::vec3(0.0f, 0.0f, 1.0f));

	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]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, shuttleTexture);

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glDrawArrays(GL_TRIANGLES, 0, myModel.getNumVertices());
}

void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
    
    
	aspect = (float)newWidth / (float)newHeight;
	glViewport(0, 0, newWidth, newHeight);
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}

int main(void) {
    
    
	if (!glfwInit()) {
    
     exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter6 - program1", NULL, NULL);
	glfwMakeContextCurrent(window);
	if (glewInit() != GLEW_OK) {
    
     exit(EXIT_FAILURE); }
	glfwSwapInterval(1);

	glfwSetWindowSizeCallback(window, window_size_callback);

	init(window);

	while (!glfwWindowShouldClose(window)) {
    
    
		display(window, glfwGetTime());
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

おすすめ

転載: blog.csdn.net/weixin_44848751/article/details/130900403