OpenGL中如何用纹理数组为物体贴纹理?(PyOpenGL优先)
Got it, let's break down exactly how to use that texture array you’ve already loaded to apply textures to your objects in OpenGL. Since you mentioned prioritizing PyOpenGL, I’ll lead with that, and toss in a quick C++ snippet if you need it later.
The biggest difference between a single texture and a texture array is how you sample it in GLSL. You’ll need to use a sampler2DArray instead of a regular sampler2D, and sample with a 3D coordinate (texture UVs + layer index).
First, make sure your GLSL version is at least 330 (since sampler2DArray requires this). Here’s what your shaders should look like:
Vertex Shader
This passes vertex positions, texture coordinates, and the texture layer index to the fragment shader:
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec2 aTexCoords; layout (location = 2) in float aLayerIndex; // Which texture layer this vertex uses out vec2 TexCoords; out float LayerIndex; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); TexCoords = aTexCoords; LayerIndex = aLayerIndex; }
Fragment Shader
This samples the texture array using the UVs and layer index from the vertex shader:
#version 330 core out vec4 FragColor; in vec2 TexCoords; in float LayerIndex; uniform sampler2DArray textureArray; // Matches our loaded texture array void main() { // Sample with vec3(UV.x, UV.y, layer_index) FragColor = texture(textureArray, vec3(TexCoords, LayerIndex)); }
You’ll need to add a layer index attribute to your vertex data. For example, if you’re texturing a cube with 6 different textures (one per face), each face’s vertices will get a unique layer index (0 to 5, since your texture array has 6 layers).
Here’s a simplified PyOpenGL example of cube vertex data (each entry is x, y, z, u, v, layer_index):
import numpy as np import ctypes # Cube vertices: position (3f) + UV (2f) + layer index (1f) cube_vertices = np.array([ # Front face (layer 0) -0.5, -0.5, 0.5, 0.0, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, # Back face (layer 1) -0.5, -0.5, -0.5, 0.0, 0.0, 1.0, 0.5, -0.5, -0.5, 1.0, 0.0, 1.0, 0.5, 0.5, -0.5, 1.0, 1.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, 1.0, # Left face (layer 2) -0.5, 0.5, 0.5, 0.0, 1.0, 2.0, -0.5, 0.5, -0.5, 1.0, 1.0, 2.0, -0.5, -0.5, -0.5, 1.0, 0.0, 2.0, -0.5, -0.5, 0.5, 0.0, 0.0, 2.0, # Right face (layer 3) 0.5, 0.5, 0.5, 0.0, 1.0, 3.0, 0.5, 0.5, -0.5, 1.0, 1.0, 3.0, 0.5, -0.5, -0.5, 1.0, 0.0, 3.0, 0.5, -0.5, 0.5, 0.0, 0.0, 3.0, # Top face (layer 4) -0.5, 0.5, -0.5, 0.0, 1.0, 4.0, 0.5, 0.5, -0.5, 1.0, 1.0, 4.0, 0.5, 0.5, 0.5, 1.0, 0.0, 4.0, -0.5, 0.5, 0.5, 0.0, 0.0, 4.0, # Bottom face (layer 5) -0.5, -0.5, -0.5, 0.0, 1.0, 5.0, 0.5, -0.5, -0.5, 1.0, 1.0, 5.0, 0.5, -0.5, 0.5, 1.0, 0.0, 5.0, -0.5, -0.5, 0.5, 0.0, 0.0, 5.0 ], dtype=np.float32) # Set up VAO/VBO to handle the new layer attribute vao = glGenVertexArrays(1) vbo = glGenBuffers(1) glBindVertexArray(vao) glBindBuffer(GL_ARRAY_BUFFER, vbo) glBufferData(GL_ARRAY_BUFFER, cube_vertices.nbytes, cube_vertices, GL_STATIC_DRAW) # Position attribute (location 0) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * 4, ctypes.c_void_p(0)) glEnableVertexAttribArray(0) # UV attribute (location 1) glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * 4, ctypes.c_void_p(3 * 4)) glEnableVertexAttribArray(1) # Layer index attribute (location 2) glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 6 * 4, ctypes.c_void_p(5 * 4)) glEnableVertexAttribArray(2)
Looking at your existing load_texture_array function, there’s a common pitfall: if your source image is a horizontal/vertical strip of 6 textures, you can’t upload the entire image at once. You need to split it into individual tiles and upload each to a separate layer. Here’s the corrected version:
def load_texture_array(path, tile_width, tile_height): teximg = pygame.image.load(path) img_width, img_height = teximg.get_size() # Verify the image matches our tile dimensions (6 tiles wide, 1 tall) assert img_width == tile_width * 6 and img_height == tile_height, "Image size doesn't match tile layout" texture = GLuint(0) layer_count = 6 mip_levels = 1 glGenTextures(1, texture) glBindTexture(GL_TEXTURE_2D_ARRAY, texture) glTexStorage3D(GL_TEXTURE_2D_ARRAY, mip_levels, GL_RGBA8, tile_width, tile_height, layer_count) # Upload each texture tile to its own layer for layer in range(layer_count): # Extract the tile from the source image start_x = layer * tile_width tile_surface = pygame.Surface((tile_width, tile_height)) tile_surface.blit(teximg, (0, 0), (start_x, 0, tile_width, tile_height)) texels = tile_surface.get_buffer().raw # Upload to the current layer glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, tile_width, tile_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels) # Set texture parameters glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) return texture.value
In your main render loop, you just need to bind the texture array, link it to your shader’s uniform, and draw your object:
# Assume you have a compiled shader program stored in shader_program # And texture_array_id is the ID returned from load_texture_array while running: # Clear screen glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Use shader program glUseProgram(shader_program) # Set up model/view/projection matrices (fill in your own values) model = glm.mat4(1.0) view = glm.lookAt(glm.vec3(3.0, 3.0, 3.0), glm.vec3(0.0, 0.0, 0.0), glm.vec3(0.0, 1.0, 0.0)) projection = glm.perspective(glm.radians(45.0), width/height, 0.1, 100.0) glUniformMatrix4fv(glGetUniformLocation(shader_program, "model"), 1, GL_FALSE, glm.value_ptr(model)) glUniformMatrix4fv(glGetUniformLocation(shader_program, "view"), 1, GL_FALSE, glm.value_ptr(view)) glUniformMatrix4fv(glGetUniformLocation(shader_program, "projection"), 1, GL_FALSE, glm.value_ptr(projection)) # Bind texture array to texture unit 0 glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D_ARRAY, texture_array_id) # Tell the shader which texture unit to use glUniform1i(glGetUniformLocation(shader_program, "textureArray"), 0) # Draw the cube glBindVertexArray(vao) glDrawArrays(GL_TRIANGLES, 0, 36) # Swap buffers pygame.display.flip()
The logic is identical in C++—here’s the core rendering code:
// Bind texture array to unit 0 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, textureArrayID); // Link to shader uniform shader.setInt("textureArray", 0); // Replace with your shader's uniform set method // Draw glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 36);
- GLSL Version Check: Make sure your OpenGL context is set to 3.3 or higher. In PyOpenGL with Pygame, add this before creating the window:
pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MAJOR_VERSION, 3) pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MINOR_VERSION, 3) pygame.display.gl_set_attribute(pygame.GL_CONTEXT_PROFILE_MASK, pygame.GL_CONTEXT_PROFILE_CORE) - Layer Index Range: Ensure your layer indices are between 0 and 5 (since your array has 6 layers). Using values outside this range will cause sampling errors.
- Texture Format Match: Confirm your image’s RGBA format matches the
GL_RGBA8andGL_RGBA/GL_UNSIGNED_BYTEparameters in your loading code.
内容的提问来源于stack exchange,提问作者User-92

