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()
where do you get the opengl_tools module from? I've got pyopengl installed, but that module is missing.
ReplyDeleteSorry to leave your comment in moderation so long. There's a link at the bottom of the article to the opengl_tools.py file.
DeleteSorry, but neither of the links at the bottom work anymore, for me at least. Are they hosted anywhere else?
ReplyDeleteAck, you're right. I forgot to renew my domain and lost control of it a few months ago, and I've not gotten around to sorting out a replacement yet. I've put those files on github here: https://github.com/weeble/clockworkcodex_ogl I'll update the article shortly.
DeleteSorry to leave your comment in moderation so long. I *think* I've turned on email notifications properly this time. Fingers-crossed.