Environment mapping - or: a better "shade"-operator?
Posted: 2012-09-21T04:25:10-07:00
Let's do something magic with ImageMagick and execute the following command:
Or to let the pictures speak...
+ =
OK, that was a little fast! So let's explore the details step by step:
The first part of the script turns the heart shape into a heightfield: a grayscale image, whose pixels are interpreted as height coordinates in 3D space (this is well described on the IM Example pages):
The next step is to turn the straight border profile into a nice rounded edge. This is also discussed on the example pages but I repeat it here for convenience:
=> =>
We now have almost all ingredients together and can swing the magic wand:
The usage is very similar to the -shade operator and in fact: the cryptic -fx script does nearly the same thing: it reflects the surrounding environment on the surface of a 3D-shape (defined by the heightfield). -shade uses one directional light (a vector defined by angle and azimut) whereas the script uses a special circular texture (a "photograph" of a reflecting sphere) to lookup the color. This is called "Spherical Environment Mapping" and can be accomplished by some simple vector calculations (find yourself detailed explanations on the internet, if nessecary).
The fully commented script (hint: all user variables start with "O" = own):
There's also a second version here, which works with 4 averaged normals.
The script contains two adjustable variables (examples see below):
The influence of the camera height (OIz). Values from left to right: 100, 300, 10000
Environment mapping is able to simulate any thinkable lighting compared to the single distant light of the -shade operator. Here some examples (outline and shadow added for visual impact):
2 spotlights on a black sphere (map):
It's not all gold that glitters (map):
Piano finish (map):
For those who like it kitschy (map):
Kind of glass with sharp highlights and subtle reflections (compositing with colored font using Vivid_Light; map):
Just for comparison: shade operator enhanced with sigmoidal-contrast and level coloring:
Unfortunately there are also some drawbacks:
Code: Select all
convert -background none -bordercolor none -virtual-pixel transparent -interpolate Bicubic -alpha Set "heart.png" ( +clone -alpha Extract +level 0,3276 -white-threshold 3275 -morphology Distance Euclidean:7,20! -blur 1.5 -negate -evaluate pow 2 -negate -evaluate pow 0.5 "emaps/gold.jpg" -interpolate Bicubic -fx "Oh=8;ONx=Oh*(u-p[1,0]);ONy=Oh*(u-p[0,1]);ONz=1;OIx=i-w/2;OIy=j-h/2;OIz=-500;ONI=2*(ONx*OIx+ONy*OIy+ONz*OIz);ORx=OIx-ONx*ONI;ORy=OIy-ONy*ONI;ORz=OIz-ONz*ONI;OnR=sqrt(ORx*ORx+ORy*ORy+ORz*ORz);Om=v.w/2;v.p{Om*ORx/OnR+Om,Om*ORy/OnR+Om}" ) -compose In -composite "heart_gold_500_8_blur1.5.png"
+ =
OK, that was a little fast! So let's explore the details step by step:
The first part of the script turns the heart shape into a heightfield: a grayscale image, whose pixels are interpreted as height coordinates in 3D space (this is well described on the IM Example pages):
Code: Select all
"heart.png" ( +clone // we first make a copy so we can restore the transparency later
-alpha Extract // turns the alpha channel into a grayscale image
+level 0,3276 -white-threshold 3275 // the semitransparent pixel must be given the right distance value to avoid
aliasing effects along the border profile. The number is calculated as
follows: QuantumRage / profile width (here: 20). The second one is 1 less
-morphology Distance Euclidean:7,20! // creates the border profile, 20 pixels wide, as a linear gradient
-blur 1.5 // a little smoothing at this point improves the final result
Code: Select all
-negate -evaluate pow 2 -negate -evaluate pow 0.5
// Some other formulas, that may be useful here:
-evaluate pow 2 -negate -evaluate pow 0.5 -negate // groove (the inverse of round)
-function ArcSin 1 // S-shape lying
-evaluate cos 0.5 -negate // S-shape upright
-function Polynomial -4,4,0 -evaluate Pow 0.5 // ridge (half circle)
We now have almost all ingredients together and can swing the magic wand:
Code: Select all
"emaps/gold.jpg" // put the emap on the stack. It will be referenced by "u" (heightfield = "v")
-fx "..." ) // render the reflections (details below)
-compose In -composite "goldenheart.png" // restore the initial transparency and save
The fully commented script (hint: all user variables start with "O" = own):
Code: Select all
Oh = 8; // Height of the 3D-shape
// N = normal vector of the triangle, built by the current pixel and his neighboring pixels right / bottom
ONx = Oh * (u - p[1,0]);
ONy = Oh * (u - p[0,1]);
ONz = 1;
// I = Incoming ray (camera vector)
OIx = i - w / 2;
OIy = j - h / 2;
OIz = -500; // Height of the camera over ground
ONI = 2 * (ONx * OIx + ONy * OIy + ONz * OIz); // Dotproduct of I and N, multiplied by 2
// R = Reflected ray
ORx = OIx - ONx * ONI;
ORy = OIy - ONy * ONI;
ORz = OIz - ONz * ONI;
// Texture lookup
OnR = sqrt(ORx * ORx + ORy * ORy + ORz * ORz); // Normalizing factor for R
Om = v.w / 2; // Radius of environment map
v.p{Om * ORx / OnR + Om, Om * ORy / OnR + Om} // Assign color to current pixel (ATTENTION: no ";"" after last command)
The script contains two adjustable variables (examples see below):
- The height of the 3D-shape (Oh). The higher the value the steeper the profile and more distortion along the profile.
- The height of the camera over ground (OIz). The smaller the value the larger the area of the map that is reflected on the horizontal top face. Vice versa the top face is only reflected by the center pixel, if the camera is at infinite distance (which doesn't look good). Good values are 3 to 10 times the larger dimension of the shape.
The influence of the camera height (OIz). Values from left to right: 100, 300, 10000
Environment mapping is able to simulate any thinkable lighting compared to the single distant light of the -shade operator. Here some examples (outline and shadow added for visual impact):
2 spotlights on a black sphere (map):
It's not all gold that glitters (map):
Piano finish (map):
For those who like it kitschy (map):
Kind of glass with sharp highlights and subtle reflections (compositing with colored font using Vivid_Light; map):
Just for comparison: shade operator enhanced with sigmoidal-contrast and level coloring:
Unfortunately there are also some drawbacks:
- The biggest one is the lack of speed of the -fx operator: the golden heart from above takes approx. 70 seconds to render on a 2x1.4GHz Netbook with 4GB Ram (8.8 ms/pixel). Without coding the script as standalone operator it remains experimental and can hardly be used in production environment.
- The antialiasing is quite poor. This becomes visible when applying emaps with sharp transitions or fine details: broken lines and noise are the result (see below). The reason is the low number of surface normals, which point to not connected parts of the map. Maybe some subsampling (examining the reflected area with multiple normals) could improve quality.