mutter/doc/cookbook/effects.xml

770 lines
29 KiB
XML
Raw Normal View History

<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<chapter id="effects" xmlns:xi="http://www.w3.org/2003/XInclude">
<title>Effects</title>
<epigraph>
<attribution>Roger Zelazny, from <citetitle>Prince of Chaos</citetitle>
</attribution>
<para>Don't wake me for the end of the world unless it has very
good special effects</para>
</epigraph>
<section id="effects-introduction">
<title>Introduction</title>
<para>Effects modify an actor's appearance, such
as how it is positioned, colored and textured.</para>
<para>The Clutter API for effects contains several
abstract classes you can subclass to create your own effects.
It also contains several built-in effects you can use to
modify the visual appearance of actors in a variety of ways.</para>
<para>The recipes in this section of the cookbook cover how to create
your own effects as well as how to apply Clutter's effects.</para>
<section>
<title>Creating effects using the abstract effect classes</title>
<tip>
<para>One of the original design goals of Clutter was to abstract
the complexity of GL. However, the effects API partially circumvents
these abstractions, to give you finer-grained access to the
graphics pipeline. Therefore, if you want to write your own effects,
some understanding of Cogl, OpenGL, and general graphics programming
is essential.</para>
</tip>
<para>Each abstract effect class is tailored to modifying different
aspects of an actor, as explained below:</para>
<itemizedlist>
<listitem>
<formalpara>
<title><type>ClutterEffect</type></title>
<para>If you're just using the Clutter and Cogl APIs to
decorate an actor, this is simplest type of effect to
implement.</para>
</formalpara>
<para>Subclassing <type>ClutterEffect</type> enables you to
"wrap" how an actor is painted, by injecting some code before
and/or after the actor's own <function>paint()</function>
implementation.</para>
<note>
<para>This is the preferred way to modify how an actor is
painted, short of creating your own actor subclass.</para>
</note>
<para><emphasis>Subclasses of
<type>ClutterEffect</type></emphasis>:</para>
<itemizedlist>
<listitem>
<formalpara>
<title><type>ClutterOffscreenEffect</type></title>
<para>Use this class as a basis if you need GL textures
for your effect.</para>
</formalpara>
<para>GL textures are required for effects which need
an offscreen framebuffer. The offscreen framebuffer is
used to store a modified rendering of an actor (e.g.
with its colors altered or with deformed geometry).
This buffer is then redirected to a texture in the
stage window.</para>
<para>An example is <type>ClutterBlurEffect</type>,
which uses a GLSL fragment shader to blur an
actor's appearance in an offscreen framebuffer.</para>
<para><emphasis>Subclasses of
<type>ClutterOffscreenEffect</type></emphasis>:</para>
<itemizedlist>
<listitem>
<formalpara>
<title><type>ClutterDeformEffect</type></title>
<para>Use this base class if you want to modify
an actor's geometry, at the level of individual
vertices.</para>
</formalpara>
<para><type>ClutterDeformEffect</type> removes the
complexity of dealing with vertex-based deformations
at the OpenGL level, instead enabling you to easily plug
a deformation callback into the graphics pipeline.</para>
<para>If you are writing your own deform effects,
a good example to work from is
<type>ClutterPageTurnEffect</type>.</para>
<para>There is also a
<link linkend="effects-custom-deform">recipe which
explains how to implement a simple custom deform
effect</link> (a page fold).</para>
</listitem>
<listitem>
<formalpara>
<title><type>ClutterShaderEffect</type></title>
<para>Use this if you want to apply custom
GLSL vertex or fragment shaders to your actors.</para>
</formalpara>
<para>Writing <type>ClutterShaderEffects</type> gives
you very fine-grained control over the GL pipeline.
However, this makes them the most complex
effects to implement.</para>
<tip>
<para>If you want to write your own GLSL shaders, the
<ulink url="http://www.opengl.org/documentation/glsl/">GLSL
specification</ulink> is a good starting point.</para>
</tip>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</section>
<section id="effects-introduction-using-the-built-in-effects">
<title>Using the built-in effects</title>
<para>Clutter comes with a number of built-in effects
which can easily be applied to your actors. This section
explains how to do this.</para>
<para>First, create an actor. For this
example, we use a texture loaded with an image:</para>
<informalexample>
<programlisting>
/* filename could be set from command line or constant */
gchar *filename;
/* create a texture */
ClutterActor *texture = clutter_texture_new ();
/* ...set texture size, keep aspect ratio etc... */
/* NB ignoring missing file errors here for brevity */
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
filename,
NULL);
/* ...add texture to the stage... */
</programlisting>
</informalexample>
<para>Next, create an instance of an effect; here, we're
creating a <type>ClutterColorizeEffect</type> with a pink tint:</para>
<informalexample>
<programlisting>
ClutterColor *pink = clutter_color_new (230, 187, 210, 255);
ClutterEffect *effect = clutter_colorize_effect_new (pink);
</programlisting>
</informalexample>
<para>Finally, apply the effect to the actor:</para>
<informalexample>
<programlisting>
clutter_actor_add_effect (texture, effect);
</programlisting>
</informalexample>
<para>The result in this case is an image colorized with
a pink tint, like this:</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-built-in.png" />
</imageobject>
<alt>
<para>Applying a <type>ClutterColorizeEffect</type>
to a texture loaded with an image (drawing by
Madeleine Smith)</para>
</alt>
</mediaobject>
</screenshot>
<para>The same set of steps applies for any of the built-in
Clutter effects. Your own custom effects classes should also
behave in a similar way: constructors should return
<type>ClutterEffect</type> instances so your effect can
be added to an actor through the standard API.</para>
<para>One further thing worth mentioning is that because an
effect is a GObject, any properties you expose for your effect
can be animated via implicit animations,
<type>ClutterAnimator</type> or <type>ClutterState</type>. For
example, the <type>ClutterPageTurnEffect</type> can be animated
by manipulating its <varname>period</varname> property. An example
of how to do this for your own effect is given in the
<link linkend="effects-custom-deform">custom deform effect
recipe</link>.</para>
<para>The full code for the <type>ClutterColorizeEffect</type>
example is below.</para>
<example id="effects-introduction-example-1">
<title>Applying a <type>ClutterColorizeEffect</type> to
a texture loaded with an image</title>
<programlisting>
<xi:include href="examples/effects-built-in.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
</section>
<section id="effects-custom-deform">
<title>Creating and animating a custom <type>ClutterDeformEffect</type></title>
<section>
<title>Problem</title>
<para>You want to deform an actor's geometry: for example,
to make it appear stretched, twisted or folded.</para>
<para>This recipe demonstrates how to do this with a simple page
fold effect, which folds one half of the actor over its other half.</para>
</section>
<section id="effects-custom-deform-solution">
<title>Solution</title>
<para>Subclass <type>ClutterDeformEffect</type> and
implement a <function>deform_vertex()</function> function
to modify the actor's vertices.</para>
<para>The signature for <function>deform_vertex()</function>
is:</para>
<informalexample>
<programlisting>
void
deform_vertex (ClutterDeformEffect *effect,
gfloat width,
gfloat height,
CoglTextureVertex *vertex);
</programlisting>
</informalexample>
<para>The <varname>width</varname> and <varname>height</varname>
are the width and height of the target material, stored in
the offscreen buffer. Usually the target material's size will
match the actor's transformed size; however, if the effect
implements <function>create_texture()</function>, the target
material's size may differ from the actor's transformed size.</para>
<para>The <varname>vertex</varname> contains the position
and color of a vertex, to be deformed by your effect.
Your <function>deform_vertex()</function>
function should modify the member variables of this
<type>CoglTextureVertex</type> in place. Usually, this will
mean modifying the <varname>x</varname>, <varname>y</varname>
and <varname>y</varname> member variables of the vertex,
which describe its position in 3D space.</para>
<para>The example function below, taken from
<link linkend="effects-custom-deform-example-2">the
full example</link>, applies a transformation to vertices falling
in the "right-hand" half of the actor (i.e. vertices with an
<varname>x</varname> value greater than or equal to half the
width of the actor).</para>
<informalexample>
<programlisting>
static void
cb_page_fold_effect_deform_vertex (ClutterDeformEffect *effect,
gfloat width,
gfloat height,
CoglTextureVertex *vertex)
{
CbPageFoldEffectPrivate *priv = CB_PAGE_FOLD_EFFECT (effect)->priv;
/* the rotation angle is modified by the percentage progress of the fold,
* as represented by the period variable
*/
gfloat radians = (priv->angle * priv->period) / (180.0f / G_PI);
/* rotate from the center of the actor on the y axis */
gfloat adjusted_x = vertex->x - (width / 2);
/* only rotate vertices to the right of the middle of the actor */
if (adjusted_x >= 0.0)
{
vertex->x = (vertex->z * sin (radians))
+ (adjusted_x * cos (radians))
+ width / 2;
/* NB add 1 to z to prevent "z fighting"; otherwise, when fully-folded
* the image has "stripes" where vertices from the folded part
* of the actor interfere with vertices from the unfolded part
*/
vertex->z = (vertex->z * cos (radians))
+ (adjusted_x * sin (radians))
+ 1;
}
/* adjust depth of all vertices so they fit inside the actor while folding;
* this has the effect of making the image smaller within the texture,
* but does produce a cleaner fold animation
*/
vertex->z -= width / 2;
}
</programlisting>
</informalexample>
<para>Note that this effect has two properties set in its
constructor or through setters:</para>
<orderedlist>
<listitem>
<para><varname>angle</varname>, representing the angle of
the full fold; for the actor to fully fold in half, this
would be set to 180.0</para>
</listitem>
<listitem>
<para><varname>period</varname>, representing the percentage
of the fold to apply</para>
</listitem>
</orderedlist>
<para>As well as rotating the vertex, the
<function>deform_vertex()</function> function also shifts
the <varname>z</varname> coordinate "up" by 1
(towards the viewpoint) for vertices on the right-hand side of the
actor. This is so that the "folded over" vertices
are above vertices on the left-hand side. Without this small
shift, the vertices interfere with each other, which can cause striping
artefacts.</para>
<para><emphasis>All</emphasis> vertices are also shifted "down",
so that the the folding part of the actor remains within the texture.
Otherwise the part which is folding may be clipped to the allocation of
the actor.</para>
<para>This effect can now be applied to an actor, using the
approach
<link linkend="effects-introduction-using-the-built-in-effects">outlined
in the introduction</link>. The result looks like this when
<varname>period</varname> is set to 0.25 and <varname>angle</varname>
to 180.0 (i.e. the page is folded by 45 degrees):</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-custom-deform.png" />
</imageobject>
<alt>
<para>Applying a custom <type>ClutterDeformEffect</type>
to a texture loaded with an image</para>
</alt>
</mediaobject>
</screenshot>
<para>Because the effect is a GObject which exposes its
properties, it can easily be animated, as described in
<link linkend="effects-custom-deform-discussion-animating">the
discussion section</link>.</para>
</section>
<section id="effects-custom-deform-discussion">
<title>Discussion</title>
<para>A deform effect processes an actor as follows:</para>
<itemizedlist>
<listitem>
<para>The actor is divided into a series of
triangular tiles. The number of
horizontal and vertical tiles is configurable;
more tiles implies more vertices. See
<link linkend="effects-custom-deform-discussion-tiles">this
section</link> for more details about tiles.</para>
</listitem>
<listitem>
<para>The position of each vertex of each
tile is then modified (or not) by the
<function>deform_vertex()</function> function. In this
function, you can change the vertex's position
(<varname>x</varname>, <varname>y</varname>,
<varname>z</varname> coordinates). You can also
modify the color at the vertex if desired.</para>
<para>The resulting deformed vertices are stored
in an offscreen buffer.</para>
</listitem>
<listitem>
<para>Once the deformation has been applied to
all vertices, the content of the offscreen buffer
is painted at the onscreen position of the actor.</para>
</listitem>
</itemizedlist>
<para>You may find it useful to visualise this process by imagining
your actor's surface as a net, composed of triangles. (Something
like a fishing net, not a mathematical one.) At each corner of
each triangle is a marble; and between each pair of corners
is an infinitely flexible length of elastic. Moving a marble
doesn't change the position of its neighbours; it just stretches
or relaxes the elastic.</para>
<para>In this analogy, the marbles are the vertices; and the
surfaces between the marbles, bordered by triangles of
elastic, are the tiles. More triangles (tiles) means more
marbles (vertices).</para>
<para>When you create a <type>ClutterDeformEffect</type>,
think of it as specifying movements of marbles in the net.
Changing the position of a vertex corresponds to moving a marble
up/down (-/+ <varname>y</varname> position), left/right
(-/+ <varname>x</varname> position) or away/towards
you (-/+ <varname>z</varname> position) (ignoring color for the
moment).</para>
<para>Now imagine that you are asked to fold the whole net of
marbles; but you can't just grab the edge of the net and pull
it over: you can only move one marble at a time. However, once moved,
each marble magically stays where you put it in 3D space.</para>
<para>To do this, you could project where each marble would be if
you could fold the whole sheet in one go; then move the
marbles one by one to their projected positions. Even though
you'd be moving the marbles one at a time, it would eventually
look as though you'd folded the whole net with a single movement.</para>
<para>When you write a <type>ClutterDeformEffect</type>, you have
to accomplish a similar feat: change the shape of an actor
by individually modifying the positions of points on its surface. In
most cases, your <function>deform_vertex()</function> implementation
can take advantage of an existing geometric transformation
method to achieve this. (For example, the page fold in this recipe
is based on equations from p.412 of <citetitle pubwork="book">Computer
Graphics (C Version), 2nd Edition</citetitle> by Hearn and
Baker, 1996.)</para>
<section>
<title>Customising the back material</title>
<para>When you set up a deform effect, you
can optionally specify a material to use for the "back" of
any actor it is applied to.</para>
<para>If you think of an actor as a sheet of paper with a
picture on it, specifying a back is similar to turning the
sheet of paper over (rotating it around the
<varname>y</varname> axis) and drawing another picture on
the other side. If you then folded or twisted the paper,
you would be able to see parts of the pictures on both the
front and back of the paper.</para>
<para>Similarly, during deformation of an actor, if any
vertices of the actor are deformed such that the actor's surface
is folded or twisted over itself, parts of its back
become visible. If you set a back material, you will see parts
of that where the surface is folded over. If you don't set a back
material, you will instead see mirror images of parts of the actor's
front: as if the actor was flexible stained glass, rather than paper.
You can see this if you watch the animation in
<link linkend="effects-custom-deform-discussion-animating">this
section</link>.</para>
<para>The back material should be an instance of
<type>CoglMaterial</type>. You can either create this via
the Cogl API directly; or indirectly through the Clutter API
(for example, by getting the material from a
<type>ClutterTexture</type>). The code below gives an example
of how to do the latter:</para>
<informalexample>
<programlisting>
<![CDATA[
/* create a texture */
ClutterActor *back = clutter_texture_new ();
clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (back), TRUE);
clutter_actor_set_width (back, 400);
/* load image into texture (ignoring errors for brevity) */
clutter_texture_set_from_file (CLUTTER_TEXTURE (back),
back_image_file,
NULL);
/* get a handle to the texture's Cogl material */
CoglHandle material = clutter_texture_get_cogl_material (CLUTTER_TEXTURE (back));
/* cast the effect to ClutterDeformEffect and set its back material
* to the handle
*/
clutter_deform_effect_set_back_material (CLUTTER_DEFORM_EFFECT (effect),
material);
]]>
</programlisting>
</informalexample>
<para>See the <type>ClutterDeformEffect</type> API reference
for more details about back materials.</para>
<para>Here's a screenshot of the
<link linkend="effects-custom-deform-example-3">example</link>
with the addition of a back material, folded at an angle
of 60 degrees:</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-custom-deform-back-material.png" />
</imageobject>
<alt>
<para>Applying a custom <type>ClutterDeformEffect</type>
to a texture loaded with an image</para>
</alt>
</mediaobject>
</screenshot>
</section>
<section id="effects-custom-deform-discussion-animating">
<title>Animating a custom deform effect</title>
<para>Clutter's animation API can animate any GObject which
exposes its properties. In the case of the page fold effect,
we can expose the <varname>period</varname> property using
standard GObject property installation:</para>
<informalexample>
<programlisting>
/* GObject class init */
static void
cb_page_fold_effect_class_init (CbPageFoldEffectClass *klass)
{
GParamSpec *pspec;
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
/* ...other class setup code... */
/* expose the period as a GObject property */
pspec = g_param_spec_double ("period",
"Period",
"The period of the page fold",
0.0, 1.0,
0.0,
G_PARAM_READWRITE);
obj_props[PROP_PERIOD] = pspec;
g_object_class_install_property (gobject_class, PROP_PERIOD, pspec);
/* ...install other properties... */
}
</programlisting>
</informalexample>
<para>We also add a <function>get_property()</function>
implementation, as well as a setter (see
<link linkend="effects-custom-deform-example-2">the full
GObject implementation</link> for details).</para>
<para>Then set up an animation for the property; in this case,
using a <type>ClutterState</type>:</para>
<informalexample>
<programlisting>
ClutterEffect *effect = cb_page_fold_effect_new (180.0, 0.0);
ClutterState *transitions = clutter_state_new ();
clutter_state_set_duration (transitions, NULL, NULL, 500);
clutter_state_set (transitions, NULL, "unfolded",
effect, "period", CLUTTER_LINEAR, 0.0,
NULL);
clutter_state_set (transitions, NULL, "folded",
effect, "period", CLUTTER_LINEAR, 1.0,
NULL);
</programlisting>
</informalexample>
<para>To start the animation, warp the <type>ClutterState</type>
into its <emphasis>"unfolded"</emphasis> state, then set it to
<emphasis>"folded"</emphasis>:</para>
<informalexample>
<programlisting>
/* this changes state instantaneously */
clutter_state_warp_to_state (transitions, "unfolded");
/* this starts an animation to the state */
clutter_state_set_state (transitions, "folded");
</programlisting>
</informalexample>
<para>Note that the
<link linkend="effects-custom-deform-example-3">full code
sample</link> is slightly more complex, as it triggers state
changes when a mouse button is pressed on the texture. There is
also a third "partially folded" state (used to create
the screenshot for the
<link linkend="effects-custom-deform-solution">previous
section</link>).</para>
<para>Here's what the resulting animation looks like:</para>
<inlinemediaobject>
<videoobject>
<videodata fileref="videos/effects-custom-deform.ogv"/>
</videoobject>
<alt>
<para>Video showing animation of a custom deform effect
on a texture</para>
</alt>
</inlinemediaobject>
</section>
<section id="effects-custom-deform-discussion-tiles">
<title>Tiles</title>
<para>A <type>ClutterDeformEffect</type> divides the actor
being deformed into a number of tiles: the larger the number
of tiles, the larger the number of vertices to be manipulated
by the effect. Increasing the number of tiles increases the number of
vertex computations required, which can slow down animations;
at the same time, finer-grained tiles can make an effect appear
smoother, particularly when animated.</para>
<para>Most of the time, the default number
of tiles in the <varname>x</varname> and <varname>y</varname>
axes should suffice. You can get the current number of
tiles associated with an effect with:</para>
<informalexample>
<programlisting>
<![CDATA[
guint x_tiles;
guint y_tiles;
/* effect must be a subclass of ClutterDeformEffect */
clutter_deform_effect_get_n_tiles (CLUTTER_DEFORM_EFFECT (effect),
&x_tiles,
&y_tiles);
]]>
</programlisting>
</informalexample>
<para>However, if an effect produces jerky or fragmented output,
you want to tweak the number of tiles. Use the
<function>clutter_deform_effect_set_n_tiles()</function> function
to do this:</para>
<informalexample>
<programlisting>
/* 64 tiles in both axes */
guint x_tiles = 64;
guint y_tiles = 64;
clutter_deform_effect_set_n_tiles (CLUTTER_DEFORM_EFFECT (effect),
x_tiles,
y_tiles);
</programlisting>
</informalexample>
</section>
</section>
<section>
<title>Full example</title>
<para>This example consists of three files:</para>
<itemizedlist>
<listitem>
<para><link linkend="effects-custom-deform-example-1">A header
file</link> for the <type>CbPageFoldEffect</type> GObject.</para>
</listitem>
<listitem>
<para><link linkend="effects-custom-deform-example-2">The
code file</link> implementing <type>CbPageFoldEffect</type>.</para>
</listitem>
<listitem>
<para><link linkend="effects-custom-deform-example-3">A short
sample application</link> which applies a <type>CbPageFoldEffect</type>
instance to an actor and animates the fold when the actor is
clicked.</para>
</listitem>
</itemizedlist>
<para>As Clutter effect subclasses are written using GObject,
you might find <link linkend="actors-composite">this recipe</link>
(which goes into GObject in more detail) a useful introduction.</para>
<example id="effects-custom-deform-example-1">
<title><filename>cb-page-fold-effect.h</filename> (header file)</title>
<programlisting>
<xi:include href="examples/cb-page-fold-effect.h" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="effects-custom-deform-example-2">
<title><filename>cb-page-fold-effect.c</filename> (code file)</title>
<programlisting>
<xi:include href="examples/cb-page-fold-effect.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="effects-custom-deform-example-3">
<title>Application which uses <type>CbPageFoldEffect</type>
to do animated folding of a <type>ClutterTexture</type></title>
<programlisting>
<xi:include href="examples/effects-custom-deform.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
</section>
</chapter>