You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用GL_TRIANGLES生成并绘制可纹理化带光照的n×n正方形平面?

解决方案:用OpenGL生成带纹理与光照的n×n平面(基于GL_TRIANGLES)

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

  1. Generate vertex data (with positions, normals, UVs) and triangle indices using the functions above.
  2. Create and bind a VAO, VBO, and EBO; fill them with your vertex/index data.
  3. Configure vertex attribute pointers for position, normal, and UV.
  4. Load and bind your texture.
  5. Compile and link your shaders, set all required uniforms (model/view/projection matrices, lighting values).
  6. In your render loop: bind the VAO and shader program, then call glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0).

内容的提问来源于stack exchange,提问作者iansmathew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:09:08