Professional Documents
Culture Documents
return to milkdrop.html
* * *
Note that there is another, quite comprehensive, Preset Authoring Guide
available on the web at http://www.milkdrop.co.uk/, which is continually
updated and expanded through the hard work of a few dedicated preset
authors. Whereas this guide (the one you are currently viewing) gives the bare
technical specifications for writing your own presets, the guide at milkdrop.co.uk
'starts at the beginning' and walks you through all of the mathematics and subtleties
of 'rolling your own', explaining things in great detail. The guide at milkdrop.co.uk
is very highly recommended to anyone who wishes to learn more about creating their
own presets.
* * *
Section Listing
----------------------1. about presets
2. preset authoring - basic
3. preset authoring - advanced
a. per-frame equations
b. per-vertex equations
c. variable pools, declaring your own variables, persistence of values
d. preset init code; carrying values between variable pools, using q1-q32
e. custom shapes & waves
f. pixel shaders
conceptual overview
the WARP shader
the COMPOSITE shader
pixel shader reference
intrinsic instructions
per-vertex shader inputs
per-frame shader inputs
texture sampling
milkdrop's built-in textures - main, blur, and noise
blur1, blur2, blur3
noise textures
reading textures from disk
random texture selection
misc. cool shader tricks
quality assurance for shaders
g. quality assurance
h. debugging
i. function reference (for expressions, not shaders)
1. About Presets
----------------------When you watch MilkDrop, you are watching a series of Presets. Each
one has its own look and feel, draws the sound waves in a particular
way, and has certain motions to it. After some time, you will see
a short blend transition, and then you will be watching a new preset.
A single 'preset' is a collection of parameters that tell MilkDrop how
to draw the wave, how to warp the image around, and so on. MilkDrop
ships with over 100 built-in presets, each one having a distinct
look and feel to it.
Using MilkDrop's built-in "preset-editing menu" (the M key), you can
edit presets on the fly, on-screen, from within the program. You can
make slight adjustments to existing presets, then save over them;
or you can change lots of things, so the preset doesn't look anything
like the original, and then save it under a new name. You can even
write insane new mathematical equations, of your own imagination,
into your preset files and come up with things that MilkDrop has never
done before!
Each preset is saved as a file with the ".milk" extension, so you can
easily send them to your friends or post them on the web. You can also
go to http://www.nullsoft.com/free/milkdrop and then jump to the
"preset sharing forum" to see what other people have come up with,
or post your own cool, new presets. milkdrop.co.uk/ is another great
place to download collections of presets made by others like yourself.
]
}
>
-
zoom in/out
- push motion to the left/right (dx)
- push motion up/down (dy)
- rotate left/right (rot)
shrink/grow the amplitude of the warp effect
WAVEFORM
W
- cycle through waveforms
a. PER-FRAME EQUATIONS
---------------------When you hit 'm' to show the preset-editing menu, several items
show up. If you explore the sub-menus, you'll see that
all of the properties that make up the preset you're currently
viewing are there. The values you can specify here (such as
zoom amount, rotation amount, wave color, etc.) are all static
values, meaning that they don't change in time. For example,
take the 'zoom amount' option under the 'motion' submenu.
If this value is 1.0, there is no zoom. If the value is 1.01,
the image zooms in 1% every frame. If the value is 1.10, the
image zooms in 10% every frame. If the value is 0.9, the image
zooms out 10% every frame; and so on.
However, presets get far more interesting if you can take these
parameters (such as the zoom amount) and animate them (make them
change over time). For example, if you could take the 'zoom
amount' parameter and make it oscillate (vary) between 0.9 and
1.1 over time, the image would cyclically zoom in and out, in
time.
You can do this - by writing 'per-frame' and 'per-vertex'
equations. Let's start with 'per-frame' equations. These are
executed once per frame. So, if you were to type the following
equation in:
zoom = zoom + 0.1*sin(time);
...then the zoom amount would oscillate between 0.9 and 1.1
over time. (Recall from your geometry classes that sin()
returns a value between -1 and 1.) The equation says: "take
the static value of 'zoom', then replace it with that value,
plus some variation." This particular equation would oscillate
(cycle) every 6.28 seconds, since the sin() function's
period is 6.28 (PI*2) seconds. If you wanted it to make it
cycle every 2 seconds, you could use:
zoom = zoom + 0.1*sin(time*3.14);
Now, let's say you wanted to make the color of the waveform
(sound wave) that gets plotted on the screen vary through time.
The color is defined by three values, one for each of the main
color components (red, green, and blue), each in the range 0 to 1
(0 is dark, 1 is full intensity). You could use something like this:
wave_r = wave_r + 0.5*sin(time*1.13);
wave_g = wave_g + 0.5*sin(time*1.23);
wave_b = wave_b + 0.5*sin(time*1.33);
It's nice to stagger the frequencies (1.13, 1.23, and 1.33) of
the sine functions for the red, green, and blue color components
of the wave so that they cycle at different rates, to avoid them
always being all the same (which would create a greyscale wave).
Here is a full list of the variables available for writing per-frame
equations:
NAME
WRITABLE? RANGE DESCRIPTION
------------ ----- ----------zoom
yes
>0
controls inward/outward motion. 0.9=zoom out 10% per
frame, 1.0=no zoom, 1.1=zoom in 10%
zoomexp
yes
>0
controls the curvature of the zoom; 1=normal
rot
yes
controls the amount of rotation. 0=none, 0.1=slightly
right, -0.1=slightly clockwise, 0.1=CCW
warp
yes
>0
controls the magnitude of the warping; 0=none,
1=normal, 2=major warping...
cx
yes
0..1
controls where the center of rotation and stretching
is, horizontally. 0=left, 0.5=center, 1=right
cy
yes
0..1
controls where the center of rotation and stretching
is, vertically. 0=top, 0.5=center, 1=bottom
dx
yes
controls amount of constant horizontal motion; -0.01 =
move left 1% per frame, 0=none, 0.01 = move right 1%
dy
yes
controls amount of constant vertical motion; -0.01 =
move up 1% per frame, 0=none, 0.01 = move down 1%
sx
yes
>0
controls amount of constant horizontal stretching;
0.99=shrink 1%, 1=normal, 1.01=stretch 1%
sy
yes
>0
controls amount of constant vertical stretching;
0.99=shrink 1%, 1=normal, 1.01=stretch 1%
wave_mode
yes
0,1,2,3,4,5,6,7 controls which of the 8 types of waveform is
drawn
wave_x
yes
0..1
position of the waveform: 0 = far left edge of screen,
0.5 = center, 1 = far right
wave_y
yes
0..1
position of the waveform: 0 = very bottom of screen,
0.5 = center, 1 = top
wave_r
yes
0..1
amount of red color in the wave (0..1),
wave_g
yes
0..1
amount of green color in the wave (0..1)
wave_b
yes
0..1
amount of blue color in the wave (0..1)
wave_a
yes
0..1
opacity of the wave (0..1) [0=transparent, 1=opaque]
wave_mystery
yes
-1..1 what this parameter does is a mystery. (honestly,
though, this value does different things for each waveform; for example, it could
control angle at which the waveform was drawn.)
wave_usedots
yes
0/1
if 1, the waveform is drawn as dots (instead of lines)
wave_thick
yes
0/1
if 1, the waveform's lines (or dots) are drawn with
double thickness
wave_additive yes
0/1
if 1, the wave is drawn additively, saturating the
image at white
wave_brighten yes
0/1
if 1, all 3 r/g/b colors will be scaled up until at
least one reaches 1.0
ob_size
yes
0..0.5 thickness of the outer border drawn at the edges of
the screen every frame
ob_r
yes
0..1
amount of red color in the outer border
ob_g
yes
0..1
amount of green color in the outer border
ob_b
yes
0..1
amount of blue color in the outer border
ob_a
yes
0..1
opacity of the outer border (0=transparent, 1=opaque)
ib_size
yes
0..0.5 thickness of the inner border drawn at the edges of
the screen every frame
ib_r
yes
0..1
amount of red color in the inner border
ib_g
yes
0..1
amount of green color in the inner border
ib_b
yes
0..1
amount of blue color in the inner border
ib_a
yes
0..1
opacity of the inner border (0=transparent, 1=opaque)
mv_r
yes
0..1
amount of red color in the motion vectors
mv_g
yes
0..1
amount of green color in the motion vectors
mv_b
yes
0..1
amount of blue color in the motion vectors
mv_a
yes
0..1
opacity of the motion vectors (0=transparent,
1=opaque)
mv_x
yes
0..64 the number of motion vectors in the X direction
mv_y
yes
0..48 the number of motion vectors in the Y direction
mv_l
yes
0..5
the length of the motion vectors (0=no trail,
1=normal, 2=double...)
mv_dx
yes
-1..1 horizontal placement offset of the motion vectors
mv_dy
yes
-1..1 vertical placement offset of the motion vectors
decay
yes
0..1
controls the eventual fade to black; 1=no fade,
0.9=strong fade, 0.98=recommended
gamma
yes
>0
controls display brightness; 1=normal, 2=double,
3=triple, etc.
echo_zoom
yes
>0
controls the size of the second graphics layer
echo_alpha
yes
>0
controls the opacity of the second graphics layer;
0=transparent (off), 0.5=half-mix, 1=opaque
echo_orient
yes
0,1,2,3 selects an orientation for the second graphics layer.
0=normal, 1=flip on x, 2=flip on y, 3=flip on both
darken_center yes
0/1
if 1, help keeps the image from getting too bright by
continually dimming the center point
wrap
yes
0/1
sets whether or not screen elements can drift off of
one side and onto the other
invert
yes
0/1
inverts the colors in the image
brighten
yes
0/1
brightens the darker parts of the image (nonlinear;
square root filter)
darken
yes
0/1
darkens the brighter parts of the image (nonlinear;
squaring filter)
solarize
yes
0/1
emphasizes mid-range colors
monitor
yes
any
set this value for debugging your preset code; if you
hit the 'N' key,
the value of 'monitor' will be posted in the upperright corner of milkdrop.
for example, setting "monitor = q3;" would let you
keep an eye on q3's value.
time
NO
>0
retrieves the current time, in seconds, since MilkDrop
started running
fps
NO
>0
retrieves the current framerate, in frames per second.
frame
NO
retrieves the number of frames of animation elapsed
since the program started
progress
NO
0..1
progress through the current preset; if preset was
just loaded, this is closer to 0; if preset is about to end, this is closer to 1.
-note that if Scroll Lock is on, 'progress' will
freeze!
bass
NO
>0
retrieves the current amount of bass. 1 is normal;
below ~0.7 is quiet; above ~1.3 is loud bass
mid
NO
>0
-same, but for mids (middle frequencies)
treb
NO
>0
-same, but for treble (high) frequencies
bass_att
NO
>0
retrieves an attenuated reading on the bass, meaning
that it is damped in time and doesn't change so rapidly.
mid_att
NO
>0
-same, but for mids (middle frequencies)
treb_att
NO
>0
-same, but for treble (high) frequencies
meshx
NO
8-128 tells you the user's mesh size in the X direction.
always an integer value.
meshy
NO
6-96
tells you the user's mesh size in the Y direction.
always an integer value.
pixelsx
NO
16-4096 width of the viz window, in pixels. If Canvas
Stretch is on, this is the pre-stretched size. (same as "texsize.x" for shaders)
pixelsy
NO
16-4096 height of the viz window, in pixels. If Canvas
Stretch is on, this is the pre-stretched size. (same as "texsize.y" for shaders)
aspectx
NO
>0
multiply an x-coordinate by this to make the preset
look the same at any aspect (window height:width) ratio.
-value: if widescreen, 1; if window is tall, h/w.
aspecty
NO
>0
multiply a y-coordinate by this to make the preset
look the same at any aspect (window height:width) ratio.
-value: if widescreen, w/h; if window is tall, 1.
blur1_min
yes
blur2_min
yes
tighter
blur3_min
yes
blur1_max
yes
blur2_max
yes
min/max.
blur3_max
yes
sample
blur1_edge_darken yes
the
q1
q2
q3
q4
q5
q6
q7
...
q31
q32
0..1
0..1
0..1
0..1
0..1
range, though.
This will increase the precision in the blur textures,
but you run the risk of clamping values to your
0..1
0..1
yes
yes
yes
yes
yes
yes
yes
any
any
any
any
any
any
any
yes
yes
any
any
Some of the variables are read-only, meaning that you shouldn't change
their values them through the equations. You can; it won't stop you;
but the results are unpredictable.
You can also make up to 30 of your own variables.
For example:
b. PER-VERTEX EQUATIONS
----------------------So far we've discussed only how to change parameters based on
time. What if you wanted to also vary a parameter, such as the
zoom amount, in different ways, for different locations on the
screen? For example, normally, the result of the 'zoom' parameter
is to just do a flat zoom. This doesn't look very realistic,
because you don't see any perspective in the zoom. It would be
progress
NO
0..1
progress through the current preset; if preset was just
loaded, this is closer to 0; if preset is about to end, this is closer to 1.
-note that if Scroll Lock is on, 'progress' will
freeze!
bass
NO
>0
retrieves the current amount of bass. 1 is normal;
below ~0.7 is quiet; above ~1.3 is loud bass
mid
NO
>0
-same, but for mids (middle frequencies)
treb
NO
>0
-same, but for treble (high) frequencies
bass_att
NO
>0
retrieves an attenuated reading on the bass, meaning
that it is damped in time and doesn't change so rapidly.
mid_att
NO
>0
-same, but for mids (middle frequencies)
treb_att
NO
>0
-same, but for treble (high) frequencies
meshx
NO
8-192
tells you the user's mesh size in the X direction.
always an integer value.
meshy
NO
6-144
tells you the user's mesh size in the Y direction.
always an integer value.
pixelsx
NO
16-4096 width of the viz window, in pixels. If Canvas Stretch
is on, this is the pre-stretched size. (same as "texsize.x" for shaders)
pixelsy
NO
16-4096 height of the viz window, in pixels. If Canvas Stretch
is on, this is the pre-stretched size. (same as "texsize.y" for shaders)
aspectx
NO
>0
multiply an x-coordinate by this to make the preset look
the same at any aspect (window height:width) ratio.
-value: if widescreen, 1; if window is tall, h/w.
aspecty
NO
>0
multiply a y-coordinate by this to make the preset look
the same at any aspect (window height:width) ratio.
-value: if widescreen, w/h; if window is tall, 1.
q1
q2
q3
q4
q5
q6
q7
...
q31
q32
yes
yes
yes
yes
yes
yes
yes
any
any
any
any
any
any
any
yes
yes
any
any
The main reason for distinction between per-frame and per-vertex equations
is simple: SPEED. If you have a per-vertex equation that doesn't make use
of the x, y, rad, or ang variables, then there's no reason for it to be
executed per-vertex; it could be executed once per frame, and the result
would be the same. So, here's a maxim to write on the wall:
"If a per-vertex equation doesn't use at least one of the variables
{ x, y, rad, ang }, then it should be actually be a per-frame
equation."
You might be wondering how on earth all these formulas could be computed
for every pixel on the screen, every frame, and still yield a high frame
rate. Well, that's the magic of the hamster. And the fact that it really
does the processing only at certain points on the screen, then interpolates
the results across the space between the points. In the config panel,
the "mesh size" option defines how many points (in X and Y) there are at
which the per-vertex equations are actually computed. When you crank this
option up, you start eating up CPU cycles rather quickly.
the following:
billy = 5.3;
This creates a variable called 'billy' and sets its value to 5.3. You can
then freely read and/or modify the value of 'billy' within that section
of code.
However, sometimes it is desireable to create (really, initialize) a variable
in an "init" equations, then use and/or update it in the "per-frame" equations.
You can always do this, because paired init and per-frame equations
share the same variable pool. In addition, the values of user-defined
variables will persist from frame to frame.
There are three variable "pools" in MilkDrop:
1. preset init code + preset per-frame code
2. custom wave init + custom wave per-frame code
3. custom shape init + custom shape per-frame code
So, you can probably guess that if you declare a variable in the preset
init code, you can then read it in the preset per-frame code. You can
also write to it (update it), and its value will persist to the next
frame. All three pools work this way.
As explained, though, you can't read the value of 'billy' in when in another
variable pool. (This is intentional, and keeps MilkDrop running nice and
fast.) If you want to pass values around between variable pools, you need
to use a set of special variables: q1, q2, q3, etc. on up to q32. See
the next section for details on how they work and how to properly use them.
Just remember: the Q variables (and later, the T variables) are the only ones
that you can use to "jump" between (carry values between) variable pools.
You might notice that there are two other types of equations that weren't
listed above. They are:
* preset per-vertex code
* custom wave per-point code
For these two code sections, persistent values don't really make sense,
because there is no way to properly initialize them. Any user-defined
variables in these code sections should just be treated as scratch
variables, not persisting from frame to frame, from vertex to vertex,
or from point to point (even though technically, they will... but it
probably won't be what you want). The only thing that really makes sense
here is when you want to carry values along from point to point as
you run the custom wave per-point code; to do this, use q1-q32. (See
the next section for a more detailed explanation.)
d. PRESET INIT CODE; CARRYING VALUES BETWEEN VARIABLE POOLS, USING q1-q32
----------------------As we've just seen, you can't normally pass values around between variable
pools. However, there is one mechanism for bridging this gap: the 'Q'
variables. They are named q1, q2, q3, and so on, through q32. Their
main function is to bridge the gap between various variable pools.
In MilkDrop 1.03 and later, you can write code that is executed only once,
when a preset is loaded (switched to). This 'preset initialization' code
does two useful things:
1. It allows you to set the initial value of your own (user-defined)
variables (such as 'my_variable'), as just explained.
2. It allows you to write the default ("sticky") values for q1, q2, q3...
through q32. Whatever these values end up at after the init code,
those are the values that q1-q32 will be reset to at the start of
each frame (...the input to the per-frame equations). If the
per-frame equations change the values of q1-q32, those new values will
propagate on to other variable pools (see the diagram below), but on
the next frame, the values will be reset to the original "sticky"
defaults.
See the flow chart below for a brief, and complete, glance at how the values
of the Q variables flow throughout MilkDrop.
into the per-frame wave/shape equations come from the preset per-frame
equations, as you can see. But, just to humor you: in the wave/shape init code,
the Q values coming in are the results from the preset init code. Any Q values
you write to there (in the wave/shape init code) will be meaningless; although
you can write to (initialize) your own custom variables, and read those in
later, in the wave/shape per-frame equations! So, really, you can still route
data that way, if you really want to.
Side note: when you edit the preset init code and apply it (by hitting
CTRL+ENTER), the init code will re-execute immediately. However, when you
edit the regular per-frame/per-vertex code and hit CTRL+ENTER, the preset init
code will NOT be re-executed; the results of the last execution will persist.
If you change per-frame/per-vertex code and want to re-execute the initialization
code (i.e. to randomize it or reset the preset), you'll have to save the preset
and then re-load it.
(Historical note: nothing here has changed since MilkDrop 1; these diagrams were
just re-designed to be much simpler to read. Actually, there was a bug in
the old diagrams that is now fixed: on frame 0, they showed the Q values
going straight from the (frame 0!?) per-frame code, into the custom
wave/shape init code. On frame 0, those Q values actually come straight from
the preset init code. HOWEVER, they are virtually useless, as discussed above.)
waveform. The initial values of q1-q32 coming in (for the first point)
are the values that stood at the end of the custom wave per-frame code,
this frame. If you then modify q1-q32 in the per-point code (or even if you
don't), the values will pass on to the next point. You could, for example,
smooth out a waveform using this.
THE 'T' VARIABLES
---------------------There are 8 additional variables available for custom waves and shapes:
t1-t8. These are very similar to the Q variables, but they exist only
for custom waves & shapes. To see how the data flows from variable pool
to variable pool for the T vars, take a look at the diagram below. Like
the Q variables, they exist to help you bridge some gaps between variable
pools. However, the T variables are a bit simpler to understand than the
Q's. The diagram below should explain it all.
ang
yes
0..6.28 default rotation angle of the shape (0...2*pi)
textured
yes
0/1
if ON, the shape will be textured with the image
from the previous frame
tex_zoom
yes
>0
the portion of the previous frame's image to use
with the shape
tex_ang
yes
0..6.28 the angle at which to rotate the previous frame's
image before applying it to the shape
r
yes
0..1
default amount of red color toward the center of the
shape (0..1)
g
yes
0..1
default amount of green color toward the center of
the shape (0..1)
b
yes
0..1
default amount of blue color toward the center of
the shape (0..1)
a
yes
0..1
default opacity of the center of the shape;
0=transparent, 1=opaque
r2
yes
0..1
default amount of red color toward the outer edge of
the shape (0..1)
g2
yes
0..1
default amount of green color toward the outer edge
of the shape (0..1)
b2
yes
0..1
default amount of blue color toward the outer edge
of the shape (0..1)
a2
yes
0..1
default opacity of the outer edge of the shape;
0=transparent, 1=opaque
border_r
yes
0..1
default amount of red color in the shape's border
(0..1)
border_g
yes
0..1
default amount of green color in the shape's border
(0..1)
border_b
yes
0..1
default amount of blue color in the shape's border
(0..1)
border_a
yes
0..1
default opacity of the shape's border;
0=transparent, 1=opaque
time
NO
>0
retrieves the current time, in seconds, since
MilkDrop started running
fps
NO
>0
retrieves the current framerate, in frames per
second.
frame
NO
retrieves the number of frames of animation elapsed
since the program started
progress
NO
0..1
progress through the current preset; if preset was
just loaded, this is closer to 0; if preset is about to end, this is closer to 1.
-note that if Scroll Lock is on, 'progress' will
freeze!
bass
NO
>0
retrieves the current amount of bass. 1 is normal;
below ~0.7 is quiet; above ~1.3 is loud bass
mid
NO
>0
-same, but for mids (middle frequencies)
treb
NO
>0
-same, but for treble (high) frequencies
bass_att
NO
>0
retrieves an attenuated reading on the bass, meaning
that it is damped in time and doesn't change so rapidly.
mid_att
NO
>0
-same, but for mids (middle frequencies)
treb_att
NO
>0
-same, but for treble (high) frequencies
q1
q2
q3
q4
q5
q6
q7
...
q31
q32
yes
yes
yes
yes
yes
yes
yes
any
any
any
any
any
any
any
yes
yes
any
any
t1
t2
t3
t4
yes
yes
yes
yes
any
any
any
any
t5
t6
t7
t8
yes
yes
yes
yes
any
any
any
any
}
}
}
}
yes
yes
yes
yes
yes
yes
yes
any
any
any
any
any
any
any
yes
yes
any
any
t1
t2
t3
t4
t5
t6
t7
t8
yes
yes
yes
yes
yes
yes
yes
yes
any
any
any
any
any
any
any
any
DESCRIPTION
----------the x position of this point that makes up the wave
y
yes
0..1
the y position of this point that makes up the wave
(0=bottom, 1=top)
sample
no
0..1
how far along we are, through the samples that make
up the waveform: 0=first sample, 0.5 = half-way through; 1=last sample.
value1
no
any
the value of the Left audio channel sample at this
point in the waveform (or freq. spectrum).
value2
no
any
the value of the Right audio channel sample at this
point in the waveform (or freq. spectrum).
r
yes
0..1
amount of red color in this point of the wave (0..1)
g
yes
0..1
amount of green color in this point of the wave
(0..1)
b
yes
0..1
amount of blue color in this point of the wave
(0..1)
a
yes
0..1
opacity of this point of the waveform;
0=transparent, 1=opaque
time
NO
>0
retrieves the current time, in seconds, since
MilkDrop started running
fps
NO
>0
retrieves the current framerate, in frames per
second.
frame
NO
retrieves the number of frames of animation elapsed
since the program started
progress
NO
0..1
progress through the current preset; if preset was
just loaded, this is closer to 0; if preset is about to end, this is closer to 1.
-note that if Scroll Lock is on, 'progress' will
freeze!
bass
NO
>0
retrieves the current amount of bass. 1 is normal;
below ~0.7 is quiet; above ~1.3 is loud bass
mid
NO
>0
-same, but for mids (middle frequencies)
treb
NO
>0
-same, but for treble (high) frequencies
bass_att
NO
>0
retrieves an attenuated reading on the bass, meaning
that it is damped in time and doesn't change so rapidly.
mid_att
NO
>0
-same, but for mids (middle frequencies)
treb_att
NO
>0
-same, but for treble (high) frequencies
q1
q2
q3
q4
q5
q6
q7
...
q31
q32
yes
yes
yes
yes
yes
yes
yes
any
any
any
any
any
any
any
yes
yes
any
any
t1
t2
t3
t4
t5
t6
t7
t8
yes
yes
yes
yes
yes
yes
yes
yes
any
any
any
any
any
any
any
any
f. PIXEL SHADERS
---------------------The world of realtime computer graphics made a huge stride around 2002-2003,
with the advent of pixel shaders. Lots of people want to learn how to
use pixel shaders; writing presets for MilkDrop is a great way
to learn them, because you get to see the effects of your code instantly,
on the screen.
Each new
(video echo, gamma, etc. - all things that are now folded into the
composite shader) are all gone. You'll also notice that two nice little
default shaders (warp and composite) have been written for you, and that
the relevant values and options from the old preset (gamma, decay, video
echo, texture wrap, etc.) have all been set correctly in the new shaders,
so that the preset does exactly what it did before. The only difference
is that now, the preset takes advantage of the full programmability of
pixel shaders (and you have a lot of freedom to tweak it), instead of
being restricted by the highly restrictive DX8 fixed-function graphics
pipeline.
Some of the mash-up functions (discussed later) will mix old and new
presets together. In this case, the newly-created preset file will only
look correct on MilkDrop 1.xx if it uses neither a warp nor composite shader.
It will still run in MilkDrop 1, but without shaders, so whatever random
values gamma, video echo, etc. were left at, will all kick back in.
One last note: keep in mind that MilkDrop 2 is smart enough to not show
you any presets that your GPU can't support. MilkDrop 1, though, isn't
so smart - it will let you look at MilkDrop 2 presets. It will
ignore all the shader stuff, and probably not display correctly, though.
toward the bottom of the menu. Keep in mind that if you upgrade
a preset's pixel shader version and then save it to disk, it might
not be usable anymore on other computers with older graphics chips.
Now go edit one of the two shaders. Once you're in there, editing,
hit F9 - this will toggle the onscreen quick reference for writing
shaders. It's very handy. Press F9 again to hide it.
WARP SHADER
---------------Here is an example of a simple WARP shader. It is run over every pixel of
the internal canvas, with the output being back to the canvas itself (it's
a double-buffered texture). Any special effects that happen here get "baked"
into the image, and will persist into the next frame.
shader_body
{
// sample a pixel from the previous frame.
// uv coord is slightly warped (driven by the per-vertex equations),
//
and is what creates the main "movement" in our preset.
ret = tex2D( sampler_main, uv ).xyz;
// darken over time
ret *= 0.97;
}
There are only two instructions here... sample the old frame, and
darken the old color value (color values are always in the 0..1 range)
to prevent the screen from turning white over time.
This code is run on every pixel on the screen. If the UV's coming in
were just [0..1] on X and Y, corresponding exactly to the location of
the pixel on the screen, there would be no movement (or warp).
What creates the warp is that the UV coordinates are slightly "off".
Each frame, MilkDrop executes the per-vertex equations for the current
preset at all the vertices on a grid covering the screen. The resulting
UV coordinates are then interpolated (by the GPU) between the vertices,
and this shader code is executed at each pixel, with the UV coordinates
smoothly interpolated for you to do your sampling. Note that the
original, un-distorted UV coordinates are always available in uv_orig.
If the preset had no motion in it, or if we used uv_orig instead of uv,
we would just see pixels getting darker over time, with no apparent motion.
Note that MilkDrop's internal canvas (texture) can only store colors
in the [0..1] range, so if your shader outputs values beyond that range,
the values will be clipped to 0 or 1. Within the body of the shader,
you can go nuts, using any number ranges you want; this restriction only
applies to the final output.
Note that there are several ways to darken pixels over time, and the
color precision (8 bits per color channel, or 256 shades, or [0..1]
in increments of 0.004) means you have to be careful about darkening
the color over time. If you're going to darken using this:
ret *= 0.97;
then you shouldn't use a multiplier above 0.98, because, due to precision,
dark-ish pixels will never become fully dark. Another way to do it
is this:
ret -= 0.004;
The above darkening method will make the pixels go dark, although,
sometimes too quickly. One way around this is to use error diffusion
dithering (discussed later in this guide).
Probably the best thing is to combine the two:
ret = (ret - 0.002)*0.99;
This gives you a partially constant, partially linear darkening effect,
and it tends to look the best. Tweak the values as needed.
COMPOSITE SHADER
---------------Here is an example of a simple COMPOSITE shader. It is run over every
pixel in the visualizer window, the output being the actual screen that
you see. Anything you do here will NOT affect the subsequent frame it will only affect the display of the current frame.
shader_body
{
// sample the corresponding pixel from the internal rendering canvas
// note that, here, 'uv' is undistorted.
// in the warp shader, 'uv' is warped, and 'uv_orig' is undistorted!
ret = tex2D(sampler_main, uv).xyz;
float2
float3
float4
half
half2
half3
half4
float2x2
float3x2
float3x3
float4x3
2d
2d
3d
3d
Operators
---------+ - * /
transformation
transformation
transformation
transformation
matrix.
matrix.
matrix.
matrix.
a += b
Also valid:
-=
*=
/=
==
<
<=
>
>=
equality test.
less than.
less than or equal to.
greater than.
your mom is soo fat.
var.x
var.y
var.z
var.w
var.xy
var.wzxy
etc.
Preprocessor
-----------If you're familiar with C/C++, you can use simple things like
#define, #if (condition) / #endif, #if / #elif/#else / #endif, and so on.
Intrinsic Instructions
---------------------Unless otherwise noted, these instructions all work on float, float2, float3,
or float4 operands.
math operations
--------------abs(a)
Absolute value. Returns max(a, -a).
frac(a)
Fractional value. Returns (a - (int)a). (the part after the
decimal)
floor(a)
Floor. Returns ((int)a). (the part before the decimal)
Only works on single floats.
saturate(a)
Clamps a to the [0..1] range. Often FREE (costs no extra
instructions).
max(a,b)
Returns the greater of each component between a and b.
min(a,b)
Returns the lesser of each component between a and b.
sqrt(a)
always positive.
pow(a,b)
float).
exp(a)
log(a)
lerp(a,b,c)
c[0..1].
Output
Returns 2^a.
Returns log2(a).
Linear interpolate... blends from a to b based on the value of
(Or extrapolates, if c is outside [0..1] range.)
a and b must be same type; can can be that same type, or just
float.
dot(a,b)
a.w*b.w.
lum(a)
eye.
length(a)
vector.
normalize(a)
length (1.0).
texture operations
-----------------tex2D(sampler_name, uv)
Samples a 2D texture at the coordinates 'uv', where UV is a float2.
Returns a float4 (r,g,b,alpha).
tex3D(sampler_name, uvw)
Samples a volume (3D) texture at the coordinates 'uvw', where UVW is
a float3.
You could use this to sample a built-in "noise volume" or a volume
texture
from a .DDS texture (that holds a 3D texture).
Returns a float4 (r,g,b,alpha).
GetBlur1(uv)
GetBlur2(uv)
GetBlur3(uv)
mega-slow operations
-------------------sin(a)
Returns cos(a), where a is in radians. Output is in -1..1 range.
SLOW - use with care.
cos(a)
Returns sin(a), where a is in radians. Output is in -1..1 range.
SLOW - use with care.
atan2(y,x)
Returns the arctangent of y/x. In english, this means that if you
give
and y
range
origin.
mul(a,b)
cross(a,b)
if (a == b)
{
not.
...
}
else
{
...
}
Keep in mind that cos(), sin(), and atan2() are incredibly slow (~8 instructions).
Almost everything else (even divide, taking a reciprocal square root, etc.) is 1
or maybe, at most, 2 instructions.
Note that the saturate() instruction, as well as multiplying by 2, 4, or 8,
or dividing by 2, 4, or 8, is a free operation on many GPUs. And the ALUs
inside a GPU almost always do a multiply + add (both) in a single instruction.
Also, you can divide by an integer constant without suffixing it with ".0";
in C/C++, "float x = 1/5;" will give you ZERO; but in shader language, it
will give you what you expect: 0.2.
uv;
uv_orig;
rad;
ang;
//
//
//
//
//
//
//
//
//
Composite shader:
float2
float
float
float3
uv;
rad;
ang;
hue_shader;
Note that for both shaders, the vertex-interpolated angle value (ang)
gets a bit wonky near the center of the screen, where it is very difficult to
interpolate well (because it wraps suddenly from 0 to PI*2 at 9 o'clock on your
screen). If you see artifacts due to this, just use
float better_ang = atan2(uv.y - 0.5, uv.x - 0.5);
It's very slow, but will give you perfect results.
higher-quality value for the radius, use:
bass;
mid;
treb;
vol;
bass_att;
mid_att;
treb_att;
vol_att;
float4 aspect
// .xy: multiplier to use on UV's to paste an image
fullscreen, *aspect-aware*; .zw = inverse.
float4 texsize
// info about the size of the internal canvas, in pixels.
//
.xy = (width,height); .zw = (1/(float)w, 1/(float)h)
// here are some values that roam around in the [0..1] range at varying speeds.
float4 slow_roam_cos // .xyzw ~= 0.5 + 0.5*cos(time * float4(~0.005, ~0.008,
~0.013, ~0.022))
float4 roam_cos
// .xyzw ~= 0.5 + 0.5*cos(time * float4(~0.3, ~1.3, ~5,
~20))
// here are the corresponding sine values, in case you want them.
// pick a cos/sin pair and use the same accessor on it (.x, .z, etc.)
// to get plot a point making a circle over time.
float4 slow_roam_sin // .xyzw ~= same, but using sin()
float4 roam_sin
// .xyzw ~= same, but using sin()
// of course, if you want anything more complicated, just generate it
// yourself in the per-frame equations, save it in q1-q32, and it will
// be available to your shaders!
float
float
//...
float
float
q1;
q2;
q31;
q32;
float4
float4
float4
float4
float4
float4
float4
float4
_qa;
_qb;
_qc;
_qd;
_qe;
_qf;
_qg;
_qh;
//
//
//
//
//
//
//
//
float
float
float
float
float
blur1_min
blur1_max
blur2_min
blur2_max
blur3_min
q1-q4
q5-q8
q9-q12
q13-q16
q17-q20
q21-q24
q25-q28
q29-q32
float
blur3_max
//
rot_d1;
rot_d2;
rot_d3;
rot_d4;
float4x3
float4x3
float4x3
float4x3
rot_f1;
rot_f2;
rot_f3;
rot_f4;
// faster-changing.
float4x3
float4x3
float4x3
float4x3
rot_vf1;
rot_vf2;
rot_vf3;
rot_vf4;
// very-fast-changing.
float4x3
float4x3
float4x3
float4x3
rot_uf1;
rot_uf2;
rot_uf3;
rot_uf4;
// ultra-fast-changing.
float4x3
float4x3
float4x3
float4x3
TEXTURE SAMPLING
---------------We've already used one texture: the internal canvas, also called "Main".
Because it's always being used, you don't have to declare it. You can
just sample it. However, you have some options for how to sample it.
There are four samplers tied to the Main canvas:
SAMPLER NAME
-----------sampler_fw_main*
sampler_fc_main
sampler_pw_main
sampler_pc_main
FILTERING METHOD
---------------bilinear filtering
bilinear filtering
point sampling
point sampling
BEHAVIOR OUTSIDE
[0..1] UV RANGE
---------------wrap
clamp
wrap
clamp
tends to create tiled images, while clamp mode takes the border
color and extends it out infinitely.
In general, other textures can be sampled similarly, using these
same two-letter prefixes ("_fw", "_pc", etc.). Or, you can
always just leave off the prefix, and MilkDrop will assume you
want to do "_fw" - bilinear filtering and wrap mode - the defaults.
However, because you were only using half the range of possible
values, the precision of these values will be twice as good.
That's the purpose of the min/max values. Watch out, though having your values clipped to a minimum of 0.5 would look bad
if you actually had colors that are over 0.5, and you're not
subtracting that 0.5 off.
However, if you do set a min and then subtract it off, you can
also get some great glow effects, where only really
bright pixels contribute to the "glow" If you set the min to
0.7, for example, and then sample like this:
ret += (GetBlur1(uv) - blur1_min)*2;
It will subtract off the 0.7 minimum threshold, but because
of the clipping, you will basically just see the bright
pixels "glowing". The *2 is just for a little extra glow.
NOISE TEXTURES
-------------There are also "noise" (random value) textures built in to MilkDrop.
They are generated when MilkDrop starts, but only so the large amount
of (random) data wouldn't bloat the size of the MilkDrop download.
They vary in the quality (smoothness) of the noise, as well as
how often the pattern repeats itself. Always use the smallest
possible noise texture (_lite or _lq versions) when possible.
Here are the details on the six textures:
NAME
---noise_lq
noise_lq_lite
noise_mq
noise_hq
noisevol_lq
noisevol_hq
DIMS
---2D
2D
2D
2D
3D
3D
PIXELS
-----256x256
32x32
64x64
32x32
32x32x32
8x8x8
QUALITY
--------low
low
medium
high
low
high
texsize_noise_lq;
Also, recall that the size of the Main canvas is universally available to
all shaders, and looks like this: (this is auto-declared for you, by the way)
float4 texsize
Important: Note that the medium- and high-quality textures should never be
used for 1:1 mapping! - it is a huge waste. You will only benefit from their
higher quality if you are *zoomed in* on these textures, seeing them
magnified, sampling them at a low frequency. If they are minified
(sampled at a high frequency / zoomed out of) or even displayed at 1:1,
you will thrash your video memory cache and the preset will run very
slow.
Let's imagine
(great compression)
(a microsoft/directx format - very flexible - can even do 3D)
(portable network graphics; can give you compress w/an alpha channel)
(truevision Targa - 1, 3, or 4 channels)
(puke)
(puke)
Now that you've declared the texture, you can sample it like this,
from within the shader_body section:
float3 mypixel = tex2D(sampler_billy, uv2).xyz;
So first it will try to find billy.jpg; then billy.dds; and so
on, until it finds a valid texture. If the texture can not be
found in the "milkdrop2\textures" directory, it will then also try
to find it **in the current preset directory**; this is done so that
preset downloaders can be lazy and just put the presets, along
with the textures that come with them, into the same directory.
If your shader wants to know how big the texture is, declare this
(also above the shader_body section):
float4 texsize_billy;
MilkDrop will see the "texsize_" prefix and automatically know what
to do. (You don't have to include the //comment, of course.)
To stretch this texture to cover the screen, do this (in the shader
body):
ret = tex2D(sampler_billy, uv).xyz;
Or to map it fitted to the screen, aspect-aware:
ret = tex2D(sampler_billy, uv * aspect.xy).xyz;
Or to tile it so the pixels are represented 1:1:
ret = tex2D(sampler_billy, uv * texsize.xy * texsize_billy.zw).xyz;
Or to map it tiled exactly 5 times:
ret = tex2D(sampler_billy, uv * 5).xyz;
Or to zoom into the center 20% of the image:
ret = tex2D(sampler_billy, (uv-0.5)*0.2 + 0.5 ).xyz;
Of course, you could also declare sampler_pw_billy, to do point
sampling, or sampler_fc_billy, for clamping, and so on.
framerate like mad. Sample things near 1:1, or feel free to zoom
in close on them, but avoid extreme zoom-outs.
-avoid sin() and cos() functions if you can. If their inputs don't
vary from pixel to pixel, calculate the sin/cos values in
the per-frame equations, then store them in q1-q32, and read
them into your shader from there.
-any calculation that results in the same value for all pixels
on the screen should be offloaded into MilkDrop's per-frame
equations, then passed to the shader via the q1-q32 variables.
These variables are directly accessible from all shaders (q1,
q2, etc.) and can also be read in as float4's for convenience
(q1-q4 make up a float4 called _qa; q5-q8 come together in _qb;
etc.).
-also avoid doing motion/warping calculations in the warp shader,
that you could do in the per-vertex equations. Those run on the
CPU, which is a huge resource that is almost never completely
used; the GPU, although processing 1,000 times as much math
because it works per-pixel instead of per-vertex, can use as
much of a break as it can get. Any low-frequency effects (values
that vary slowly over the screen) should go in the per-vertex
equations, and only the high-frequency component of the motion
or warping should come from the pixel shader.
-keep in mind that the DirectX shader compiler is superb at
optimizing; anything that can be thrown out, will be. Things like
ret *= 1.0;
ret += 0;
ret += tex2D(mytex, uv).xyz * 0;
will completely disappear. If you sample a texture and then the
sample doesn't end up making it into the final output color value,
the texture will never even get bound (or loaded from disk),
let alone sampled. And so on.
-you can use the 'half' type wherever you don't need full 'float'
precision. Generally use 'float' for UVs and time values, and
'half' for almost everything else. However, don't stress about it
too much, because most GPUs run
everything at full-precision & full-speed nowadays - and for the
older GPUs that don't, the driver is probably very smart (if it's
an Nvidia or ATI card) about auto-substituting halfs for floats
wherever possible.
3. before sharing your presets, please make sure they look good in a
SQUARE or WIDESCREEN window. If they don't, scan these guidelines
and you will probably be able to easily fix it.
The overall design goal in MilkDrop, concerning aspect ratio, is to
fit the preset to the long axis of the window, and to crop the rest,
but to do all of this without any stretching or zooming (so all internal
canvas pixels map 1:1 to screen pixels).
-per-frame/per-vertex equations:
* multiply XY coords by the values "aspectx" and "aspecty", respectively.
-shader code:
* multiply UV coordinates by 'aspect.xy', prior to using them
to sample a texture, to make the texture fit on the screen properly.
(For example, if the screen is wide, the image will be fitted to cover
the width of the screen, and it will be cropped at the top and bottom.)
* multiply by 'aspect.zw' to make it fit the other way (it will fit
the image to be completely visible in one dimension, and tiled in the
other direction).
* any time you perturb the UV coordinates in the warp shader, prior to
sampling the Main texture, you should multiply the "delta" you are applying
by aspect.xy. Otherwise, in a widescreen window, the "delta" will actually
be dramatically squished, or in a tall window, the change would be
elongated very vertically.
g. QUALITY ASSURANCE
---------------------When designing presets, please adhere to the pixel shader 'quality assurance'
guidelines in the above section, as they are very important. But, in order
to make sure the presets you create work well on other systems, please
also keep in mind:
1. Keep your presets fast. There's nothing to spoil the mood like
a preset popping up that chokes at 10 fps. Since division is 11
times slower than multiplication (or addition/subtraction), if you
divide a bunch of values by one other value, pre-divide that value
("inv = 1/myval;") and then multiply those other values by that
inverse. Also, never put computations in the per-vertex code that
are the same for every pixel; move these into the per-frame code,
and carry the results to the per-vertex code using the q1-q32 variables.
Remember that maxim: "If a per-vertex equation doesn't use at least
one of the variables { x, y, rad, ang }, then it should be actually
be a per-frame equation."
2. Design your presets using the default mesh size option
from the config panel, or at least check, before you distribute them,
to make sure they look correct at the default mesh size. If your
mesh is too coarse (small), then a viewer with the default mesh size
might see unexpected "bonus" effects that you might not have intended,
and might mess up your preset. If your mesh is too fine, then a
viewer with the default might not see all the detail you intended,
and it might look bad.
2. Try to design your presets in a 32-bit video mode, so that its
brightness levels are standard. The thing to really watch out
for is designing your presets in 16-bit color when the "fix pink/
white color saturation artifact" checkbox is checked. This
checkbox keeps the image extra dark to avoid color saturation,
which is only necessary on some cards, in 16-bit color. If this
is the case for you, and you write a preset, then when you run
it on another machine, it might appear insanely bright.
3. Don't underestimate the power of the 'dx' and 'dy' parameters
(in the per-vertex equations). Some of the best presets are based
on using these. If you strip everything out of a preset so that
there's no motion at all, then you can use the dx and dy parameters
to have precise manual control over the motion. Basically, all the
other effects (zoom, warp, rot, etc.) are just complicated
abstractions; they could all be simulated by using only { x, y,
rad, ang } and { dx, dy }.
4. If you use the 'progress' variable in a preset, make sure you
try the preset out with several values for 'Time Between Auto
Preset Changes'. The biggest thing to avoid is using something
like sin(progress), since the rate at which 'progress' increases
can vary drastically from system to system, dependong on the user's
h. DEBUGGING
----------------------One feature that preset authors should definitely be aware of is the
variable monitoring feature, which lets you monitor (watch) the value
of any per-frame variable you like. First, hit the 'N' key to show
the monitor value, which will probably display zero. Then all you
have to do is add a line like this to the per-frame equations:
monitor = x;
where 'x' is the variable or expression you want to monitor. Once you
hit CTRL+ENTER to accept the changes, you should see the value of the
per-frame variable or expression in the upper-right corner of the
screen!
Once again, note that it only works for *per-frame* equations, and NOT
for per-vertex equations.
i. FUNCTION REFERENCE
----------------------Following is a list of the functions supported by the expression evaluator
(for preset init, per-frame, and per-vertex equations; NOT for pixel shaders).
The list was blatently ripped from the help box of Justin Frankels' AVS
plug-in, since MilkDrop uses the expression evaluator that he wrote.
Format your expressions using a semicolon (;) to delimit between statements.
Use parenthesis ['(' and ')'] to denote precedence if you are unsure.
The following operators are available:
= : assign
+,-,/,* : plus, minus, divide, multiply
| : convert to integer, and do bitwise or
& : convert to integer, and do bitwise and
% : convert to integer, and get remainder
The following functions are available:
int(var)
: returns the integer value of 'var' (rounds toward zero)
abs(var)
: returns the absolute value of var
sin(var)
: returns the sine of the angle var (expressed in radians)
cos(var)
: returns the cosine of the angle var
tan(var)
: returns the tangent of the angle var
asin(var) : returns the arcsine of var
acos(var) : returns the arccosine of var
atan(var) : returns the arctangent of var
sqr(var)
: returns the square of var
sqrt(var) : returns the square root of var
pow(var,var2) : returns var to the power of var2
log(var)
: returns the log base e of var
log10(var)
: returns the log base 10 of var
sign(var)
: returns the sign of var or 0
min(var,var2) : returns the smalest value
max(var,var2) : returns the greatest value
sigmoid(var,var2) : returns sigmoid function value of x=var
(var2=constraint)
rand(var)
: returns a random integer modulo 'var'; e.g. rand(4) will
return 0, 1, 2, or 3.
bor(var,var2) : boolean or, returns 1 if var or var2 is != 0
bnot(var) : boolean not, returns 1 if var == 0 or 0 if var != 0
if(cond,vartrue,varfalse)
otherwise returns valfalse
equal(var,var2) : returns
above(var,var2) : returns
below(var,var2) : returns
return to top
return to milkdrop.html