This will probably be a little steep if you don't know any OpenGL. However, fear not. Read An intro to modern OpenGL by Joe Groff. It's great, and it's how I learned to write GLSL shaders. Well, I say learned to write them – I've pretty much only written the one that you're going to see in these articles. The tutorial's in C, but I've translated the early parts into Python: Python OpenGL tutorial Lastly, don't worry if the 3D maths is scary – we won't be needing it since we're sticking solidly to 2D. (That said, I am quite scared of the maths in the ambient occlusion bit. But we'll worry about that when we come to it.)
In addition to Python and Pygame, you're going to need PyOpenGL and NumPy. I installed them on Ubuntu using:
sudo apt-get install python-opengl sudo apt-get install python-numpy(This should all work fine on Windows and Mac, but you'll need to download and install all these things separately.)
#!/usr/bin/env python # Copyright 2011, Annette Wilson # Licensed under the MIT license: # http://www.opensource.org/licenses/MIT # # Minecraft mapping - Rendering something with OpenGL # # With great thanks to Joe Groff: # http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Chapter-1:-The-Graphics-Pipeline.html from OpenGL.GL import * import pygame, pygame.image, pygame.key from pygame.locals import * from opengl_tools import * vertex_shader='''\ #version 130 uniform vec2 screen_dimensions; uniform vec2 cam_position; uniform float zoom; uniform float texture_dimension; uniform float map_dimension; in vec4 position; out vec2 texcoord; void main() { gl_Position.xy = ( (position.xy / 2.0 + 0.5) * texture_dimension - cam_position ) * 2.0 * zoom / screen_dimensions; gl_Position.zw = vec2(0.0, 1.0); texcoord = position.xy * 0.5 + 0.5; } '''
fragment_shader = '''\ #version 130 uniform sampler2D texture_atlas; uniform usampler2D map_texture; in vec2 texcoord; out vec4 fragcolor; void main() { fragcolor = texture2D(texture_atlas, texcoord); } '''
class Resources(object): pass def make_resources(): minecraft_map = pygame.Surface((512,512)) atlas = pygame.image.load('numbered_texture_atlas.png') vertex_buffer_data = float_array( -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0) element_buffer_data = short_array( 0,1,2,3) resources = Resources() resources.vertex_buffer = make_buffer( GL_ARRAY_BUFFER, vertex_buffer_data, vertex_buffer_data.nbytes) resources.element_buffer = make_buffer( GL_ELEMENT_ARRAY_BUFFER, element_buffer_data, element_buffer_data.nbytes) resources.map_texture = make_texture( image=minecraft_map, interpolate=False, alpha=True, integer=True) resources.texture_atlas = make_texture( image=atlas, interpolate=False, alpha=True) resources.program = assemble_shader_program( vertex_shader, fragment_shader, uniform_names=[ 'screen_dimensions', 'cam_position', 'zoom', 'texture_dimension', 'texture_atlas', 'map_texture'], attribute_names=[ 'position']) return resources
- map_texture is a placeholder for our map data. We'll make use of this next time.
- texture_atlas is the texture atlas that we created last time.
- vertex_buffer contains the four vertices of our square.
- element_buffer is a list of indices into vertex_buffer_data, describing the order they should be connected up in a triangle strip.
- program is our shader program, combining the vertex and fragment shaders.
def render(resources, position, zoom, screen_dimensions): screen_w, screen_h = screen_dimensions glViewport(0,0,screen_w,screen_h) glClearColor(0.4, 0.4, 0.4, 1.0) glClear(GL_COLOR_BUFFER_BIT) glUseProgram(resources.program.program) uniforms = resources.program.uniforms glUniform2f(uniforms['screen_dimensions'], screen_w, screen_h) glUniform2f(uniforms['cam_position'], position[0], position[1]) glUniform1f(uniforms['zoom'], zoom) glUniform1f(uniforms['texture_dimension'], 512.0) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, resources.map_texture) glUniform1i(resources.program.uniforms['map_texture'], 0) glActiveTexture(GL_TEXTURE1) glBindTexture(GL_TEXTURE_2D, resources.texture_atlas) glUniform1i(resources.program.uniforms['texture_atlas'], 1) glBindBuffer(GL_ARRAY_BUFFER, resources.vertex_buffer) glVertexAttribPointer( resources.program.attributes['position'], 4, # size GL_FLOAT, # type GL_FALSE, # normalized? ctypes.sizeof(GLfloat)*4, # stride None # offset ) position_attribute = resources.program.attributes['position'] glEnableVertexAttribArray(position_attribute) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, resources.element_buffer) glDrawElements( GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, None) glDisableVertexAttribArray(position_attribute) pygame.display.flip()
def main(): video_flags = OPENGL|DOUBLEBUF pygame.init() screen_dimensions = 800, 600 surface = pygame.display.set_mode( screen_dimensions, video_flags) resources = make_resources() frames = 0 done = 0 zoom = 1.0 position = [256.0, 256.0] dragging = False draglast = 0,0 while not done: while 1: event = pygame.event.poll() if event.type == NOEVENT: break if event.type == KEYDOWN: pass if event.type == QUIT: done = 1 render(resources, position, zoom, screen_dimensions) frames += 1 if __name__ == '__main__': main()




