Drawing
Drawing graphics in ClanLib is done by first acquiring an instance of the CL_GraphicContext interface. Graphical contexts can be provided by different sources, with CL_DisplayWindow being the common source.
A simple example of obtaining a graphic-context:
CL_DisplayWindow window(0, 0, 640, 480, "Hello World"); CL_GraphicContext gc = window.get_gc();
A graphic context is an interface which you can use to draw graphics and create graphical objects like textures, shader objects and so on. Each graphic context has a number of states that determine how each drawing command is to be executed.
If the graphic context comes from a CL_DisplayWindow object, it is normally written to an off-screen buffer in order to avoid the user seeing your changes as they are being made. In order to change the content being drawn onto the screen, you need to call CL_DisplayWindow::flip() or CL_DisplayWindow::update(rect). The following example shows how to make a ClanLib application drawing some graphics onto the screen:
CL_SetupCore setup_core; CL_SetupDisplay setup_display; CL_SetupGL setup_gl; CL_DisplayWindow window(640, 480, "Hello World"); CL_GraphicContext gc = window.get_gc(); CL_InputContext ic = window.get_ic(); CL_Font font_tahoma(gc, "Tahoma", 16); CL_BlendMode blend_transparent; blend_transparent.enable_blending(true); while (ic.get_keyboard().get_keycode(CL_KEY_ESCAPE) == false) { gc.clear(CL_Colord::smokewhite); gc.set_map_mode(cl_map_2d_upper_left); gc.set_blend_mode(blend_transparent); font_tahoma.draw_text(gc, 10, 10, "Hello There", CL_Colord::lemonchiffon); window.flip(); CL_KeepAlive::process(); }
High-level Rendering
ClanLib provides a set of higher level classes to draw simple 2D graphics easilly:
- CL_Draw
- CL_Image
- CL_Sprite
- CL_Font
- CL_SpanLayout
Using these classes are fairly straight forward. In the following loop we draw some text, show a simple image and render a gradient:
CL_Image image(gc, "image.png"); CL_Font font(gc, "Tahoma", 16); while (ic.get_keyboard().get_keycode(CL_KEY_ESCAPE) == false) { gc.clear(CL_Colord::smokewhite); CL_Gradient color(CL_Colorf::mediumspringgreen, CL_Colorf::honeydew); CL_Draw::gradient_fill(gc, 0.0f, 0.0f, 100.0f, 100.0f, color); image.draw(gc, 10.0f, 10.0f); font.draw_text(gc, 50.0f, 50.0f, "High level 2D drawing", CL_Colord::navy); window.flip(); CL_KeepAlive::process(); }
For further information on each of these classes, see the overview documentation dedicated to them or check the reference documentation.
Low-level Rendering
The graphic context class itself draws primitives, with each primitive being a point, line segment, polygon or a rectangle of pixels. The primitives are defined by a group of one or more vertices. A vertex defines a point, an endpoint of an edge, or a corner of a polygon where two edges meet. Additional data is associated with each vertex, such as colors, normals and texture coordinates.
For example, a single line drawn is described as one line-segment primitive using two vertices. The first vertex may have position 10,10 using a red color, and the second vertex may have position 100,100 using a green color. The line drawn will then go from 10,10 to 100,100 and changing color from red to green along the line.
Data associated with a vertex is called a vertex attribute. In our line drawing example the two attributes are position and color and are assigned index 0 and 1 respectively. To draw the line we use the class CL_PrimitivesArray to describe our vertex data and then we call draw_primitives to render the line:
CL_Vec4f red_color(1.0f, 0.0f, 0.0f, 1.0f); CL_Vec4f green_color(0.0f, 1.0f, 0.0f, 1.0f); CL_Vec2i positions[] = { CL_Vec2i(10,10), CL_Vec2i(100,100) }; CL_Vec4f colors[] = { red_color, green_color }; CL_PrimitivesArray vertices(gc); vertices.set_attribute(0, positions); vertices.set_attribute(1, colors); gc.set_program(cl_program_color_only); gc.draw_primitives(cl_lines, 2, vertices);
The actual look and position of the line is determined by a shader program. A shader program consists of two individual shaders called a vertex shader and a fragment shader. Each shader is run at a different stage when rendering the line. Consider the following pseudo-code for how a line is rendered:
struct Vertex { VertexAttribute attributes[max_attributes]; }; struct ShadedVertex { Vec2 position; VaryingVariable varyings[max_varyings]; }; void draw_line(Vertex v1, Vertex v2) { ShadedVertex sv1 = vertex_shader(v1); ShadedVertex sv2 = vertex_shader(v2); foreach pixel covered by line (sv1.position,sv2.position) { VaryingVariable varyings[] = interpolate(pixel position, sv1, sv2); Color shaded_pixel = fragment_shader(varyings); framebuffer[pixel position] = blend_function(framebuffer_pixel, shaded_pixel); } }
To do: Describe the process of rendering a primitive a lot better :)