如何用GL_TRIANGLES生成并绘制可纹理化带光照的n×n正方形平面?
Hey there! Let's tackle your problem step by step—generating an n×n plane made of unit quadrilaterals, adding texture/lighting, and specifically using GL_TRIANGLES for rendering.
1. Core Logic: Building the Plane with GL_TRIANGLES
First, let's clarify how to break down unit squares into triangles (since OpenGL's GL_TRIANGLES expects groups of 3 vertices per triangle). An n×n grid has n*n unit quadrilaterals, each split into 2 triangles—so we'll need 2*n*n total triangles.
To avoid redundant vertices, we can generate a grid of unique vertices: an n×n grid has (n+1)*(n+1) unique points (since each edge has n+1 vertices). Here's how to adapt your existing vertex generation code:
float* Plane::getUniqueVertices(int n) { if (uniqueVertices) { delete[] uniqueVertices; // Clean up existing data first uniqueVertices = nullptr; } const int gridSize = n + 1; const int NUM_VERTICES = gridSize * gridSize * 3; // 3 components (x,y,z) per vertex uniqueVertices = new float[NUM_VERTICES]; const float unitStep = 1.0f; // Each quad is 1x1 units int idx = 0; // Generate vertices in a grid (x from 0 to n, z from 0 to n, y=0 for flat plane) for (int z = 0; z < gridSize; ++z) { for (int x = 0; x < gridSize; ++x) { uniqueVertices[idx++] = x * unitStep; uniqueVertices[idx++] = 0.0f; uniqueVertices[idx++] = z * unitStep; } } return uniqueVertices; }
Next, we need an index array to define which vertices form each triangle. This keeps our vertex data compact:
unsigned int* Plane::getTriangleIndices(int n) { const int numQuads = n * n; const int NUM_INDICES = numQuads * 6; // 6 indices per quad (2 triangles × 3 vertices) unsigned int* indices = new unsigned int[NUM_INDICES]; const int gridSize = n + 1; int idx = 0; for (int z = 0; z < n; ++z) { for (int x = 0; x < n; ++x) { // Calculate indices for the 4 corners of the current quad const unsigned int topLeft = z * gridSize + x; const unsigned int topRight = z * gridSize + x + 1; const unsigned int bottomLeft = (z + 1) * gridSize + x; const unsigned int bottomRight = (z + 1) * gridSize + x + 1; // First triangle: top-left → top-right → bottom-left indices[idx++] = topLeft; indices[idx++] = topRight; indices[idx++] = bottomLeft; // Second triangle: top-right → bottom-right → bottom-left indices[idx++] = topRight; indices[idx++] = bottomRight; indices[idx++] = bottomLeft; } } return indices; }
2. Adding Texture Coordinates
To map textures to the plane, we need to add UV coordinates to each vertex. Let's modify the vertex generation to include UVs (5 components per vertex: x,y,z,u,v):
float* Plane::getVerticesWithUV(int n) { if (uniqueVertices) { delete[] uniqueVertices; uniqueVertices = nullptr; } const int gridSize = n + 1; const int NUM_VERTICES = gridSize * gridSize * 5; // x,y,z + u,v uniqueVertices = new float[NUM_VERTICES]; const float unitStep = 1.0f; const float uvStep = 1.0f / n; // Each quad gets 1/n of the texture int idx = 0; for (int z = 0; z < gridSize; ++z) { for (int x = 0; x < gridSize; ++x) { // Position uniqueVertices[idx++] = x * unitStep; uniqueVertices[idx++] = 0.0f; uniqueVertices[idx++] = z * unitStep; // UV coordinates (flip V since texture origin is top-left) uniqueVertices[idx++] = x * uvStep; uniqueVertices[idx++] = 1.0f - (z * uvStep); } } return uniqueVertices; }
Then, configure OpenGL to recognize the UV attribute in your VAO setup:
// After binding your VBO glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // Position attribute glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // UV attribute
Load and bind your texture (using a library like stb_image for image loading):
unsigned int textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); // Set texture wrapping/filtering parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Load texture image int width, height, channels; unsigned char* texData = stbi_load("your_texture.jpg", &width, &height, &channels, 0); if (texData) { GLenum format = (channels == 4) ? GL_RGBA : GL_RGB; glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, texData); glGenerateMipmap(GL_TEXTURE_2D); } else { std::cerr << "Failed to load texture!" << std::endl; } stbi_image_free(texData);
Bind the texture before drawing:
glBindTexture(GL_TEXTURE_2D, textureID); glDrawElements(GL_TRIANGLES, NUM_INDICES, GL_UNSIGNED_INT, 0);
3. Adding Lighting Support
For lighting, we need per-vertex normals. Since this is a flat plane, all normals point upward (0,1,0). Let's update the vertex data to include normals (7 components per vertex: x,y,z,nx,ny,nz,u,v):
float* Plane::getVerticesWithUVAndNormals(int n) { if (uniqueVertices) { delete[] uniqueVertices; uniqueVertices = nullptr; } const int gridSize = n + 1; const int NUM_VERTICES = gridSize * gridSize * 7; uniqueVertices = new float[NUM_VERTICES]; const float unitStep = 1.0f; const float uvStep = 1.0f / n; int idx = 0; for (int z = 0; z < gridSize; ++z) { for (int x = 0; x < gridSize; ++x) { // Position uniqueVertices[idx++] = x * unitStep; uniqueVertices[idx++] = 0.0f; uniqueVertices[idx++] = z * unitStep; // Normal (upward for flat plane) uniqueVertices[idx++] = 0.0f; uniqueVertices[idx++] = 1.0f; uniqueVertices[idx++] = 0.0f; // UV uniqueVertices[idx++] = x * uvStep; uniqueVertices[idx++] = 1.0f - (z * uvStep); } } return uniqueVertices; }
Update the vertex attribute pointers:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // Position glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // Normal glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2); // UV
Use these simple shaders for basic lighting:
Vertex Shader
#version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aNormal; layout(location = 2) in vec2 aTexCoord; out vec3 FragPos; out vec3 Normal; out vec2 TexCoord; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; TexCoord = aTexCoord; gl_Position = projection * view * model * vec4(aPos, 1.0); }
Fragment Shader
#version 330 core out vec4 FragColor; in vec3 FragPos; in vec3 Normal; in vec2 TexCoord; uniform sampler2D texture1; uniform vec3 lightPos; uniform vec3 viewPos; uniform vec3 lightColor; uniform vec3 objectColor; void main() { // Ambient lighting float ambientStrength = 0.1; vec3 ambient = ambientStrength * lightColor; // Diffuse lighting vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diff * lightColor; // Specular lighting (optional) float specularStrength = 0.5; vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular = specularStrength * spec * lightColor; // Combine all lighting components with texture color vec3 result = (ambient + diffuse + specular) * objectColor * texture(texture1, TexCoord).rgb; FragColor = vec4(result, 1.0); }
Don't forget to set the lighting uniforms in your C++ code before drawing:
// Example values glUniform3f(glGetUniformLocation(shaderProgram, "lightPos"), 5.0f, 5.0f, 5.0f); glUniform3f(glGetUniformLocation(shaderProgram, "viewPos"), cameraPos.x, cameraPos.y, cameraPos.z); glUniform3f(glGetUniformLocation(shaderProgram, "lightColor"), 1.0f, 1.0f, 1.0f); glUniform3f(glGetUniformLocation(shaderProgram, "objectColor"), 1.0f, 0.5f, 0.31f);
4. Full Rendering Workflow Recap
- Generate vertex data (with positions, normals, UVs) and triangle indices using the functions above.
- Create and bind a VAO, VBO, and EBO; fill them with your vertex/index data.
- Configure vertex attribute pointers for position, normal, and UV.
- Load and bind your texture.
- Compile and link your shaders, set all required uniforms (model/view/projection matrices, lighting values).
- In your render loop: bind the VAO and shader program, then call
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0).
内容的提问来源于stack exchange,提问作者iansmathew

