In Chapter 25, you learned about creating, placing, and manipulating primitives in VRML. In this chapter, you'll take that knowledge and build on it to create more convincing VRML worlds. Aside from appearance and color issues, you'll look deeper into how to create efficient VRML worlds, and how to add hyperlinks that make them useful for maneuvering on the World Wide Web.
You have two basic alternatives for creating different effects and appearances on primitives in VRML: the Material and Texture2 nodes. Material is used to control the colors assigned to the shapes. Texture2 is used to add graphics files as textures to your shapes.
The Material node accepts a number of basic properties: diffuseColor, ambientColor, emissiveColor, specularColor, shininess, and transparency. All of the numbers involved have values from zero to one. The following is the format:
Material {
diffuseColor red_num green_num blue_num
ambientColor red_num green_num blue_num
emissiveColor red_num green_num blue_num
specularColor red_num green_num blue_num
shininess number
transparency number
}
The first four properties accept values for each of the red, green, and blue channels for the color desired. The values can be any decimal between zero and one (although using a decimal past the "hundredths" place, like .507, is fairly useless).
Most important among the color values is probably diffuseColor, which is essentially the basic color of your primitive. The value ambientColor is sometimes described as "how dark the color is" and it's generally a slightly darker version of the same color as diffuseColor. Look at the following example:
Material {
diffuseColor 0 0 1
ambientColor 0 0 .8
}
This sets the basic color to blue, with a slightly darker blue used for the ambientColor. The emissiveColor property determines what color your shape will be as it fades into the background. Generally, you'll want this to be darker-meaning you use a smaller number.
The property specularColor is used to represent the color of light bouncing off of the shape. Depending on how surreal your world is, you'll probably want this to be a white/yellowish color. An example of both these properties is the following:
Material {
diffuseColor 0 1 0
emissiveColor 0 .2 0
specularColor .8 .6 .8
}
This basically translates to "bright" green in the foreground and darker green in the background, with a yellow/white (with hints of green) as the "light-bouncing" color.
Note |
Remember with these red, green, and blue values that as you approach one with all values, you get closer to white. 0,0,0 is black. Everything in between is a spectrum-each color is at its "brightest" at one while the other colors remain zero. |
The last two Material properties are simply levels from zero to one. Both are fairly self-explanatory. shininess suggests how much light bounces off the object; transparency affects how solid the material appears. The default value for shininess is 0.2 (a little shiny); default for transparency is 0, or completely solid.
Let's work a little with the last example from Chapter 24, adding a little color to your ice cream cone for Kong. Notice that the Material node affects all other shapes in a particular Separator.
Create a new VRML document (or add the Material nodes to your work from last chapter), save it with a .wrl extension, and then enter Listing 25.1.
Listing 25.1 color.wrl Changing the Colors of VRML Objects
#VRML V1.0 ascii
#
# Moving and flipping
# VRML primitives
Separator {
Transform {
translation 0 0 0
rotation 1 0 0 3.14
}
Material {
ambientColor .6 .4 .2
diffuseColor .7 .5 .3
emissiveColor .6 .4 .2
}
Cone {
height .5
bottomRadius .12
}
}
Separator {
Transform {
translation 0 .25 0
}
Material {
ambientColor .9 .9 .8
diffuseColor 1 1 .5
shininess .9
}
Sphere {radius .20}
}
The best advice for the red, green, and blue (RGB) values is simply to experiment with them until you get what you feel is close to the color you wanted. If you have a graphics program available to you, you might use its color palette to try different RGB levels to achieve the desired colors, then test them in your VRML browser.
In the example, I'm basically going for a light-brown cone and a yellowish sphere, which is meant to suggest a sugar cone and vanilla ice cream. I've also altered the translation values to try to line the ice cream up on top of the cone (so you can see the contrast). It loses something in this screenshot, but figure 25.1 will give you an idea of how this looks.
Figure 25.1 : Kong's cone in color. (The picture is black and white not the cone).
The basic point of the Texture2 node is to allow you to wrap a graphic around a primitive. (And no, I don't know what happened to Texture1.) Texture2 takes the properties filename, wrapS, and wrapT. The basic format is the following:
Texture2 {
filename "image URL"
wrapS REPEAT/CLAMP
wrapT REPEAT/CLAMP
}
Now, honestly, there's a lot more to the wrapS and wrapT, but it's rather confusing to me. Here's the scoop: many browsers tend to implement Texture2 in only the most basic ways. If your browser happens to support these two properties, then setting wrapS and wrapT to CLAMP forces just one instance of your graphic to be pasted on the primitive. Using REPEAT for both will tile the graphic all over the primitive. An example of this is as follows:
Texture2 {
filename "earth.gif"
wrapS CLAMP
wrapT CLAMP
}
REPEAT is the default value for both, so there's no need to include the properties if you want the image to tile onto your primitive.
Here's a good example of how images will cover different primitives. Just about any graphics file will do-just make sure you have it in the same directory as the VRML file. Create a new .wrl file and enter Listing 25.2.
Listing 25.2 texture.wrl Adding Textures to VRML Objects
#VRML V1.0 ascii
#
#adding Texture
#to VRML primitives
#
Separator {
Separator {
Texture2 {
filename "wood.gif"
wrapS CLAMP
wrapT CLAMP
}
Translation { translation -3.5 0 0 }
Sphere { }
}
Separator {
Texture2 { filename "wood.gif" }
Translation { translation -1.25 0 0 }
Cone { }
}
Separator {
Texture2 { filename "wood.gif" }
Translation { translation 1 0 0 }
Cylinder { }
}
Separator {
Texture2 { filename "wood.gif" }
Translation { translation 3.5 0 0 }
Cube { }
}
}
Use whatever graphics file you'd like in place of wood.gif. You're probably better off with a texture, but it can be just about as much fun with the picture of a cartoon character or politician. In fact, you can make your VRML world look a little like some of the popular movies that have included VR by creating a flat cube for the face and pasting a graphic to it using the CLAMP values.
I'd also recommend that you experiment with different values, graphics, and primitives using this example.
Links in VRML just require another node, the WWWAnchor node. This one accepts two basic properties, name and description as in the following example:
WWWAnchor {
name "URL"
description "Alternate text"
}
The WWWAnchor node works a lot like a Separator node in that it actually includes the primitive and whatever descriptive nodes you've used to affect that node. An example might be:
WWWAnchor {
name "http://www.fakecorp.com/worlds/world2.wrl"
description "Into the next world"
Separator {
Texture2 { filename "wood.gif" }
Translation { translation -1.25 0 0 }
Cone { }
}
}
The name property can accept any sort of URL, whether it's another VRML world, a regular HTML document, or a hypermedia link. The description text is similar to ALT text for the <IMG> tag. Some VRML browsers will allow the ALT text to pop-up on-screen to help the user decide if this is a useful link for them.
In this example, you'll create some basic primitives and see how different links react when clicked in your VRML world. Create a new VRML document and enter Listing 25.3.
Listing 25.3 links.wrl Creating HTML Links for Your VRML Objects
#VRML V1.0 ascii
#
#adding links
#to VRML primitives
#
WWWAnchor {
name "index.html" #A regular HTML page
description "To Our Index Page"
Separator {
Translation { translation -3.5 0 0 }
Sphere { }
}
}
WWWAnchor {
name "demo.moov" #A hypermedia link
description "See the Presentation (QT 1.4mb)"
Separator {
Translation { translation -1.25 0 0 }
Cone { }
}
}
WWWAnchor {
name "office.wrl" #Another VRML world
description "Move to the Office"
Separator {
Translation { translation 1.25 0 0 }
Cylinder { }
}
}
You'll probably want to change the names of the different files
(in the links) above so you can use files hanging around on your
hard drive (make sure they're all in the same directory as your
VRML document). You should also experiment with different types
of files to see how things are loaded and passed between the HTML
browser, the VRML browser, and other helper applications.
Note |
Some VRML browsers download the .wrl file to the user's hard drive and then access it from there. That means that relative links in the .wrl file will break, since the links will now be "relative" to the user's hard drive. For this reason, it's a good idea to use absolute URLs (even for your texture images) if you add VRML worlds to a real Web site. |
Back in your VRML world, things really haven't changed much. In some browsers, primitives will be highlighted when they're clickable. In others (like mine), you'll just get a slightly different cursor (see fig. 25.2).
Figure 25.2 : The cursor will change from this arrow to a crosshair for links.
So far, you've been dealing with the built-in primitives of VRML, and you've completely passed over the possibility of creating your own shapes. Is it possible? Sure. But it'll take some thinking. It's also possible, and timesaving, to use special commands to give your shapes "nicknames" for referring to shapes you can create. The advantage is that it then takes one line of VRML code to create another one!
Creating your own shapes takes two steps, and two different nodes.
The first node, Coordinate3,
is used to layout the coordinates for your new shape. This doesn't
actually create anything in the VRML world. It's more of a template
for the next node, IndexedFaceSet.
Using this second node, you actually draw the faces of your shape
by specifying the points for each.
Tip |
Draw your object in as close to 3D as possible (or make it in clay or origami), and label the points (starting with zero). This will help you create it in VRML. |
The Coordinate3 node is used with the point property in the following format:
Coordinate3 {
point [
x1-coord y1-coord z1-coord, #point 0
x2-coord y2-coord z2-coord, #point 1
...,
]
}
Each coordinate for your shape requires an X, Y, and Z coordinate. This creates a point in your VRML world. Get enough points together, and you'll have a shape. But you won't be able to see anything.
The next step is to add the IndexedFaceSet node. The order in which you assign points in the Coordinate3 node is noticed by VRML, and you can use that to determine what points make up each "side" of your shape. The number -1 is used to tell IndexedFaceSet that you're done with that side. IndexedFaceSet uses the property coordIndex for the listing of sides, as in the following format:
IndexedFaceSet {
coordIndex [
point_num, point_num, point_num, -1, #side1
point_num, point_num, point_num, -1, #side2
...
]
}
You should probably also consider that not every side necessarily has three points-in fact, many won't. That's why you use -1 to represent the end of a shape. Depending on your mood and the number of advanced degrees in mathematics you have, the sides of your shapes could have many, many points to connect.
Here's a shape you might want to use in your VRML world-a rooftop. It takes six points and five sides to create this particular rooftop. Fortunately, you can limit the number of dimensions and triangular hypotenuses you're working with.
Figure 25.3 shows you a sketch of the rooftop, including the coordinates you'll use. It doesn't look like it, but the bottom points of this roof all sit at the same Y coordinate. It's tilted to show 3D on this 2D page.
Figure 25.3 : Here's your shape and the coordinates for each point.
Actually, it's not that bad, is it? Architects could learn from the symmetry of your rooftop. Now look again and see which sides you're going to need to draw with the IndexedFaceSet node. Figure 25.4 shows those sides.
Figure 25.4 : Here's your shape with the sides you need to draw.
Now, armed with all this information, you're ready to code this roof! Create a new VRML document and enter Listing 25.4.
Listing 25.4 rooftop.wrl Creating the Rooftop Shape
#VRML V1.0 ascii
#
#Creating our own
#rooftop shape
#
Separator {
Coordinate3 {
point [
5 0 0, #0
5 -5 -5, #1
5 -5 5, #2
-5 0 0, #3
-5 -5 -5, #4
-5 -5 5, #5
]
}
IndexedFaceSet {
coordIndex [
0, 1, 2, -1, #Side A
0, 1, 4, 3, -1, #Side B
3, 4, 5, -1, #Side C
0, 2, 5, 3, -1, #Side D
5, 2, 1, 4, -1, #Side E
]
}
}
Notice in IndexedFaceSet that you're able to create the different sides required for this shape-both the triangles for the ends and the four-pointed rectangles for the slopes (and bottom) of the roof. You can see this roof in figure 25.5.
Figure 25.5: The rooftop complete.
One of the major concerns with VRML worlds, especially as their popularity begins to grow, is the size of the world files. Currently, low bandwidth connections make using large VRML worlds more of a "cool toy" than a reasonable alternative to HTML. Higher bandwidth may change that in a future, and it's reasonably easy to see a time when VRML will make navigating the Web very interesting.
VRML itself addresses this problem with file size by noticing that many of the shapes you'll use to create your world happen to be rather similar to one another. You might want to create a world, for instance, with a number of houses in it. Creating a complete house every time can be a little intimidating for the designer, as well as expensive in terms of file size. (Look how much code it took just to create the rooftop!) So, VRML gives you something called instancing.
This is a little like creating an object in JavaScript and similar programming/scripting languages. Basically, you just assign a "nickname" to a particular node or group of nodes. When you want to use that node again, you just type the keyword USE, followed by the nickname, as in the following example:
DEF beach_ball Sphere { radius .5 }
USE beach_ball
This is a simple example, but notice how powerful this ability is. Now, instead of using all of the code back in Listing 25.4 to create another rooftop, you could use the DEF keyword to create a nickname for the entire process-like my_roof-and you could duplicate them to your hearts' content.
DEF needs to be used with a node, but that node needn't stand on its own. You can easily assign a DEF name to a Separator node, which could encompass an entire defined "object" in your world. You can even assign DEF to non-drawing nodes, as in the following example:
DEF make_red Material {
ambientColor .9 0 0
diffuseColor 1 0 0
emissiveColor .9 0 0
}
Now the command USE make_red can be used as a one-line statement to add red to subsequent nodes within your VRML world.
Using instancing, you can take your rooftop, add a house for it, instance the house, and create a complete neighborhood in short order. Create a new VRML world document and enter Listing 25.5.
Listing 25.5 nbr_hood.wrl Using DEF for Cloning
#VRML V1.0 ascii
#
#Creating our own
#neighborhood
#
Transform { #move whole world away and below 1
translation 0 -1 -50
}
Separator { #create the ground
Material {
ambientColor 0 .9 0
diffuseColor 0 1 0
emissiveColor 0 .5 0
}
Cube {
height .01
width 100
depth 100
}
}
DEF my_house Separator { #define this as a my_house instance
Material { #add color to main house
ambientColor 0 0 .9
diffuseColor 0 0 1
emissiveColor 0 0 .5
}
Separator { #move cube up above ground
Transform {
translation 0 2.5 0
}
Cube { #create house
height 5
width 8
depth 8
}
}
Material { #add color to roof
ambientColor .4 .9 .4
diffuseColor .5 1 .5
emissiveColor .5 .5 .5
}
Coordinate3 { #create roof points
point [
5 10 0, #0
5 5 -5, #1
5 5 5, #2
-5 10 0, #3
-5 5 -5, #4
-5 5 5, #5
]
}
IndexedFaceSet { #draw sections of roof
coordIndex [
0, 1, 2, -1, #Side A
0, 1, 4, 3, -1, #Side B
3, 4, 5, -1, #Side C
0, 2, 5, 3, -1, #Side D
5, 2, 1, 4, -1, #Side E
]
}
} #bracket ends this DEF instance
Separator { #new house
Transform {
translation 15 0 15
rotation 0 1 0 1.57
}
USE my_house
}
Separator { #another new house
Transform {
translation -25 0 -25
rotation 0 1 0 1.57
}
USE my_house
}
So you define an instance for the entire house, and then simply type the USE command to add another instance of it. Of course, they're all the same color, but at least you can use Transform to put the house in another part of your world and rotate it.
If you did want to change the colors of your house, you'd probably want to break out the parts of my_house, perhaps creating my_roof and my_house so you could use different Material nodes for each. Of course, you could always have different DEF statements for Material, so that eventually USE had houses like the following:
Separator {
USE make_red
USE my_roof
USE make_green
USE my_house
}
That creates an entire house in four lines! Plus, once you get a glimpse of your little VRML neighborhood, you'll probably want to figure out how to change house colors quickly (see fig. 25.6).
Figure 25.6 : Here's you, uh, smutty little village.
Like our discussion of JavaScript, there's a lot more to VRML that can't be covered in this book. But, you've got a great start. For more VRML info, check out the following Web sites:
After you've learned to create the basic shapes in VRML, you can move on to making things feel more like a "world." Using the nodes Material and Texture2, for instance, you can add color, images, and light properties to your shapes.
The next step is to make your world useful for the Web-so you need to add hyperlinks. The WWWAnchor can be used to make any primitive or other shape a hyperlink to just about anything: another VRML world, an HTML document, or even a hypermedia file.
You can also create your own shapes. Using the Coordinate3 and IndexedFaceSet nodes, you can tell your VRML browser where the coordinates for your shape are-and then you can use those points to tell the browser where to draw the sides of your shape. These two may be among the most powerful nodes for serious VRML world creators.
Instancing, however, is easily the most powerful node for the lazy creator. Not to mention that it's good for low bandwidth connections to your VRML world. With instancing, you can create "nicknames" for your VRML objects-even from something as big as a house-and create another like it with a one-line command.
There's more to it than that, and the end of this chapter details some Web sites for learning more about VRML. Hopefully, you've got a good enough start to have some fun, though.