| Trees | Indices | Help |
|---|
|
|
1 #!/usr/bin/env python
2
3 #! This file is a literate Python program. You can compile the documentation
4 #! using mylit (http://pypi.python.org/pypi/mylit/).
5 ## title = "glitter Example: Introduction"
6 ## stylesheet = "pygments_style.css"
7
8 # <h1><i>glitter</i> Example: Introduction</h1>
9
10 # <h2>Summary</h2>
11
12 # This program will open a GLUT window and render a textured, rotating fan,
13 # overlaid with a textured background. To illustrate the use of framebuffer
14 # objects, the scene will first be rendered to a texture, then displayed on
15 # screen by a different shader. This example has been adapted from <a
16 # href="http://openglbook.com/the-book/">OpenGLBook.com</a>.
17
18 # This is a fairly advanced "introductory" example using vertex arrays, shader
19 # programs, pipelines, textures, and logging. If you are looking for something
20 # simpler (but less complete) to start with, look at the <a
21 # href="simple.html">simple example</a>.
22
23 # <img src="introduction.png">
24
25 # <h2>Front matter</h2>
26
27 # <h3>Module docstring</h3>
28
29 # The module docstring is used as a description of this example in the
30 # generated documentation:
31 """Basic example using L{VertexArray}s, L{ShaderProgram}s, L{Pipeline}s, L{Texture}s, and logging.
32
33 @author: Stephan Wenger
34 @date: 2012-02-29
35 """
36
37 # <h3>Imports</h3>
38
39 # <i>glitter</i> can use the <a
40 # href="http://docs.python.org/library/logging.html">logging</a> module for
41 # logging all OpenGL commands issued by the library. The log entries are
42 # emitted at <i>DEBUG</i> level, so we enable printing of debug messages:
43 import logging
44 logging.basicConfig(level=logging.DEBUG)
45
46 # Our scene is going to rotate. The rotating modelview matrix is computed using
47 # the sine and cosine functions from the math module:
48 from math import sin, cos, pi
49
50 # <i>glitter</i> uses <a href="http://numpy.scipy.org/">numpy</a> for
51 # representation of array data. We will use numpy's <code>random()</code>
52 # function to generate random textures:
53 from numpy.random import random
54
55 # We can usually import classes and functions contained in <i>glitter</i>
56 # submodules directly from glitter:
57 from glitter import ShaderProgram, RectangleTexture, Texture2D, Pipeline, VertexArray, add_logger
58
59 # Modules with external dependencies other than numpy, such as platform
60 # dependent parts like methods for the generation of an OpenGL context,
61 # however, have to be imported from their respective submodules:
62 from glitter.contexts.glut import GlutWindow, main_loop, get_elapsed_time
63
64 # <h2>Shaders</h2>
65
66 # <h3>Primary shader program</h3>
67
68 # In the OpenGL core profile, there is no such thing as a "standard pipeline"
69 # any more. We can either use the minimalistic <code>defaultpipeline</code>
70 # from the <code>glitter.convenience</code> module, or define our own shaders.
71 # We'll do the latter since we want some additional functionality. Instead of
72 # loading the shader from a file at runtime, we can define it inline as a
73 # Python string:
74 vertex_shader = """
75 #version 400 core
76
77 layout(location=0) in vec4 in_position;
78 layout(location=1) in vec4 in_color;
79 uniform mat4 modelview_matrix;
80 out vec4 ex_color;
81 out vec2 ex_texcoord;
82
83 void main() {
84 gl_Position = modelview_matrix * in_position;
85 ex_color = in_color;
86 ex_texcoord = in_position.xy * 0.5 + 0.5;
87 }
88 """
89 # The vertex shader will receive its per-vertex inputs <code>in_position</code>
90 # and <code>in_color</code> from a vertex array. The uniform variable
91 # <code>modelview_matrix</code> will be set directly on the shader program
92 # object. The varying values <code>ex_color</code> and <code>ex_texcoord</code>
93 # are passed to the fragment shader.
94
95 # The fragment shader is executed once for every rasterized fragment. It
96 # receives interpolated values for <code>ex_color</code> and
97 # <code>ex_texcoord</code> from the vertex shader, reads from textures
98 # <code>texture_0</code> and <code>texture_1</code>, and writes the fragment
99 # color to <code>out_color</code>, which is written to a texture by the
100 # framebuffer object.
101 fragment_shader = """
102 #version 400 core
103 #extension GL_ARB_texture_rectangle : enable
104
105 in vec4 ex_color;
106 in vec2 ex_texcoord;
107 uniform sampler2D texture_0;
108 uniform sampler2DRect texture_1;
109 layout(location=0) out vec4 out_color;
110
111 void main() {
112 out_color = 0.5 * ex_color
113 + texture(texture_0, ex_texcoord)
114 * texture(texture_1, gl_FragCoord.xy / 10.0)
115 ;
116 }
117 """
118 # The textures that are used by the fragment shader are automatically bound to
119 # free texture units by the shader object.
120
121 # <h3>Copying shader program</h3>
122
123 # To copy the image from a texture to the screen, we will use the following simple shaders:
124 copy_vertex_shader = """
125 #version 400 core
126
127 layout(location=0) in vec4 in_position;
128
129 void main() {
130 gl_Position = in_position;
131 }
132 """
133
134 copy_fragment_shader = """
135 #version 400 core
136 #extension GL_ARB_texture_rectangle : enable
137
138 uniform sampler2DRect image;
139 layout(location=0) out vec4 out_color;
140
141 void main() {
142 out_color = texture(image, gl_FragCoord.xy);
143 }
144 """
145
146 # <h2>Vertex arrays</h2>
147
148 # The geometry is specified as arrays (or nested lists) of vertices, colors,
149 # and indices into the vertex and color arrays. Lists are automatically
150 # converted to appropriate numpy arrays. The shape of these arrays describes
151 # how the arrays are to be drawn. When an index array is used, as in this
152 # example, the vertex and color arrays are two-dimensional: the first dimension
153 # is the number of vertices, the second is the number of values for each vertex
154 # (e.g. the red, green, blue, and alpha channels for a color array). The shape
155 # of the index array is also two-dimensional: the first dimension is the number
156 # of primitives to be drawn, the second is the number of vertices in each
157 # primitive. Here, we draw sixteen triangles.
158 vertices = (
159 ( 0.0, 0.0, 0.0, 1.0), # center
160 (-0.2, 0.8, 0.0, 1.0), ( 0.2, 0.8, 0.0, 1.0), ( 0.0, 0.8, 0.0, 1.0), ( 0.0, 1.0, 0.0, 1.0), # top
161 (-0.2, -0.8, 0.0, 1.0), ( 0.2, -0.8, 0.0, 1.0), ( 0.0, -0.8, 0.0, 1.0), ( 0.0, -1.0, 0.0, 1.0), # bottom
162 (-0.8, -0.2, 0.0, 1.0), (-0.8, 0.2, 0.0, 1.0), (-0.8, 0.0, 0.0, 1.0), (-1.0, 0.0, 0.0, 1.0), # left
163 ( 0.8, -0.2, 0.0, 1.0), ( 0.8, 0.2, 0.0, 1.0), ( 0.8, 0.0, 0.0, 1.0), ( 1.0, 0.0, 0.0, 1.0), # right
164 )
165
166 colors = (
167 (1.0, 1.0, 1.0, 1.0), # center
168 (0.0, 1.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (1.0, 0.0, 0.0, 1.0), # top
169 (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 1.0, 1.0, 1.0), (1.0, 0.0, 0.0, 1.0), # bottom
170 (0.0, 1.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (1.0, 0.0, 0.0, 1.0), # left
171 (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 1.0, 1.0, 1.0), (1.0, 0.0, 0.0, 1.0), # right
172 )
173
174 indices = (
175 (0, 1, 3), (0, 3, 2), ( 3, 1, 4), ( 3, 4, 2), # top
176 (0, 5, 7), (0, 7, 6), ( 7, 5, 8), ( 7, 8, 6), # bottom
177 (0, 9, 11), (0, 11, 10), (11, 9, 12), (11, 12, 10), # left
178 (0, 13, 15), (0, 15, 14), (15, 13, 16), (15, 16, 14), # right
179 )
180 # If no index array is given, the vertex and color arrays are
181 # three-dimensional: the first dimension is then the number of primitives, the
182 # second is the number of vertices per primitive, and the third is the number
183 # of values per vertex. Since in a typical geometry several primitives share a
184 # common vertex, it is usually more memory efficient to use an index array.
185
186 # <h2>Main class</h2>
187
188 # We wrap all the OpenGL interaction in a class. The class will contain an
189 # <code>__init__()</code> method to set up all OpenGL objects, any required
190 # callback methods, as well as a <code>run()</code> method to trigger execution
191 # of the GLUT main loop.
193 # <h3>Initialization</h3>
194
195 # When an <code>IntroductionExample</code> instance is created, we need to
196 # initialize a few OpenGL objects.
198 # First, we create a window; this also creates an OpenGL context.
199 self.window = GlutWindow(double=True, multisample=True)
200
201 # Then, we set the GLUT display and keyboard callback functions which
202 # will be defined later.
203 self.window.display_callback = self.display
204 self.window.keyboard_callback = self.keyboard
205
206 # A shader program is built from the previously defined vertex and
207 # fragment codes:
208 self.shader = ShaderProgram(vertex=vertex_shader, fragment=fragment_shader)
209
210 # This shader program is then used to build a pipeline. A pipeline is a
211 # convenience object that encapsulates a vertex array for input, a
212 # shader program for processing, and a framebuffer for output. The
213 # framebuffer is optional (we could render directly to the screen if we
214 # wanted), but we will render into a texture and copy the texture to
215 # the screen for instructional purposes. The <code>Pipeline</code>
216 # constructor automatically creates an empty vertex array and a
217 # framebuffer with no attachments. Named constructor arguments are
218 # interpreted as attributes of the vertex array and the framebuffer or
219 # as named inputs and outputs of the shader. This means we can directly
220 # pass in arrays of vertices and colors that will be bound to
221 # <code>in_position</code> and <code>in_color</code>, respectivey, as
222 # well as the array of element indices to draw and an empty texture to
223 # bind to the framebuffer:
224 self.render_pipeline = Pipeline(self.shader, in_position=vertices, in_color=colors,
225 elements=indices, out_color=RectangleTexture(shape=(300, 300, 3)))
226
227 # Shader uniform variables like textures can also be set directly on
228 # the pipeline. Here we initialize two textures with random data:
229 self.render_pipeline.texture_0 = Texture2D(random((30, 30, 4)))
230 self.render_pipeline.texture_1 = RectangleTexture(random((30, 30, 4)))
231
232 # Many properties, such as the filtering mode for textures, can
233 # directly be set as attributes on the corresponding objects:
234 self.render_pipeline.texture_0.min_filter = Texture2D.min_filters.NEAREST
235 self.render_pipeline.texture_0.mag_filter = Texture2D.mag_filters.NEAREST
236
237 # For copying the texture to the screen, we create another shader
238 # program.
239 self.copy_shader = ShaderProgram(vertex=copy_vertex_shader, fragment=copy_fragment_shader)
240
241 # The input texture of this shader program is the output texture of the
242 # previous pipeline. Since all textures and framebuffers are
243 # automatically bound and unbound, we do not need to worry about
244 # whether the framebuffer is still writing to the texture.
245 self.copy_shader.image = self.render_pipeline.out_color
246
247 # Instead of using a pipeline with named vertex shader inputs, we can
248 # also create a vertex array object directly with a list of vertex
249 # shader inputs to use. Here we use only a single vertex shader input:
250 # the coordinates of a fullscreen quad. The <code>elements</code>
251 # parameter defines two triangles that make up the quad.
252 self.vao = VertexArray(((-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0), (1.0, -1.0)), elements=((0, 1, 2), (0, 2, 3)))
253
254 # <h3>Callback functions</h3>
255
256 # <h4>Display function</h4>
257
258 # Here we define the display function. It will be called by GLUT whenever
259 # the screen has to be redrawn.
261 # We can simply clear the pipeline and render the vertices into the
262 # framebuffer using the shader with the following two lines:
263 self.render_pipeline.clear()
264 self.render_pipeline.draw()
265
266 # For output on the screen, we have created a GLUT window. It can be
267 # cleared in very much the same way:
268 self.window.clear()
269
270 # To copy the results of the pipeline to the screen, we use the shader
271 # that simply displays a texture. The shader can be bound by using a
272 # <code>with</code> statement:
273 with self.copy_shader:
274 # All textures used by the shader are then bound automatically, and
275 # everything is reset to its previous state when we leave the
276 # <code>with</code> block.
277
278 # With the shader bound, we simply draw a fullscreen quad that is
279 # stored in a vertex array we will create in the initialization
280 # section:
281 self.vao.draw()
282
283 # After all rendering commands have been issued, we swap the back
284 # buffer to the front, making the rendered image visible all at once:
285 self.window.swap_buffers()
286
287 # Finally, we disable logging so that we only see the OpenGL calls of
288 # the first run of the display function:
289 add_logger(None)
290
291 # <h4>Keyboard function</h4>
292
293 # To further illustrate the concept of GLUT callbacks, here's a keyboard
294 # handler that will simply make the program exit when any key is pressed:
297
298 # <h4>Timer function</h4>
299
300 # The animation is controlled by a GLUT timer. The timer callback changes
301 # the modelview matrix, schedules the next timer event, and causes a screen
302 # redraw:
304 # We first get the elapsed time from GLUT using
305 # <code>get_elapsed_time()</code>:
306 t = get_elapsed_time()
307 phi = 2 * pi * t / 4.0
308
309 # We then set the <code>modelview_matrix</code> uniform variable of the
310 # shader created in the initialization section simply by setting an
311 # attribute:
312 self.shader.modelview_matrix = ((cos(phi), sin(phi), 0, 0), (-sin(phi), cos(phi), 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
313
314 # The following line schedules the next timer event to execute after
315 # ten milliseconds.
316 self.window.add_timer(10, self.timer)
317
318 # Finally, we tell GLUT to redraw the screen.
319 self.window.post_redisplay()
320
321 # <h3>Running</h3>
322
323 # We will call the <code>run()</code> method later to run the OpenGL code.
325 # To start the animation, we call the timer once; all subsequent timer
326 # calls will be scheduled by the timer function itself.
327 self.timer()
328
329 # Now that all the initialization is done, we add the default logger to
330 # all OpenGL commands so that we can see what OpenGL the display
331 # function issues, and in which order.
332 add_logger()
333
334 # Finally, to start rendering, we enter the GLUT main loop.
335 main_loop()
336
337 # When the main loop exits, control is handed back to the script, unless
338 # <code>SystemExit</code> has been raised by the keyboard handler.
339
340 # <h2>Main section</h2>
341
342 # Finally, if this program is being run from the command line, we instanciate
343 # the main class and run it.
344 if __name__ == "__main__":
345 IntroductionExample().run()
346
| Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Fri Mar 16 17:56:06 2012 | http://epydoc.sourceforge.net |