Displaying Photos In X3D
By Ian Hollidae, 2024/05/28
Displaying multimedia items with X3D is practically unlimited. The creation of custom meshes pushes Web 3D far beyond the video, audio and photographic assets you find on the web. If you can dream it up, you can display it.
However, the irony of this limitless capacity is that presenting some of the most basic artifacts can be less than straightforward. If you come from a web background, it might even be puzzling. For example, displaying photos is one thing that's more involved than you'd imagine. In technical terms, it's not possible simply display a photo. There's no X3D equivalent to the HTML <img> tag. So how do you get around this shortcoming.
Photos In X3D Are Not Images
In X3D, understanding the use of images is simple: they're textures for geometric shapes. You create your shape and then position your texture (image) on the shape according to how you want things to look. Displaying photos, where the photo is the primary feature, requires some maneuvering.
Displaying Feature Photos
1. Select your photo. Keep in mind that displaying images in X3D for the web will mostly follow the same rules as displaying images directly on the web (bandwidth considerations, image quality, etc).
Also keep in mind that displaying a photo within X3D will follow X3D coordinate and sizing conventions. Placing a thumbnail designed for the web within a scene with unlimited dimensions may prove to be a disappointing experience.
2. Calculate the ratio of your image (width/height). The photo I want displayed has a ratio of 0.792 (1000 / 1262).
3. Select a geometric shape. For this example, I'll use an IndexedFaceSet. If you are unfamiliar with X3D, you can try a starter tutorial here.
Use the photo ratio from Step #2 to create a rectangular shape. The actual size of your photo in the scene depends on your needs. Keeping things simple, a large photo ten units high and 7.92 units in width will do (10 * 0.792).
4. Create your Scene and apply the photo to the IndexedFaceSet as a texture:
<x3d> <scene> <worldInfo title="Displaying Photos In X3D, Example 1"> </worldInfo> <navigationInfo headlight="true" type="ANY" transitionType="LINEAR"> </navigationInfo> <viewpoint position="0.0 5.0 20.0" centerOfRotation="0.0 0.0 0.0"> </viewpoint> <transform translation="0.0 5.0 -0.25"> <!-- Frame Mesh --> <group DEF="PictureFrame"> <!-- Frame Sides --> <transform translation="-4.96 0.0 0.0"> <shape DEF="longFrameRim"> <appearance> <material diffuseColor="0.153 0.298 0.396"> </material> </appearance> <cylinder top="true" bottom="true" side="true" solid="false" height="9.0" radius="0.5"> </cylinder> </shape> </transform> <transform translation="4.96 0.0 0.0"> <shape USE="longFrameRim"> </shape> </transform> <!-- Frame Back (No Transform needed, sitting at default location) --> <shape> <appearance> <material diffuseColor="0.153 0.298 0.396"> </material> </appearance> <box size="8.92 11.0 0.25"> </box> </shape> <!-- Frame Title --> <transform translation="0.0 -7.0 0.0"> <shape> <appearance> <material diffuseColor="0.373 0.153 0.396"> </material> </appearance> <text length="6.0" maxExtent="6.0" string="Downtown" solid="false"> <fontStyle family="SANS" justify="MIDDLE" size="1.0"> </text> </shape> </transform> </group> </transform> <!-- Photo (No Transform needed. Sitting at default location) --> <shape> <appearance> <imageTexture url="photo_texture.jpeg"> </imageTexture> </appearance> <indexedFaceSet solid="false" coordIndex="0 1 2 3 0"> <coordinate point="-3.96 10.0 0.0, -3.96 0.0 0.0, 3.96 0.0 0.0, 3.96 10.0 0.0"> </coordinate> </indexedFaceSet> </shape> <background skyColor="0.20 0.20 0.20" groundColor="0.20 0.20 0.20"> </background> </scene> </x3d>
Does your photo fit? If the answer is yes, you're done. If the answer is no, it's because as stated above, X3D treats your image as a texture, not a photo. Additionally, each shape has its own rules regarding the application of textures. Since I decided on an IndexedFaceSet, the following texture rules are applied:
...the default texture coordinate mapping is calculated using the local coordinate system bounding box of the shape. The longest dimension of the bounding box defines the S coordinates, and the next longest defines the T coordinates. If two or all three dimensions of the bounding box are equal, ties shall be broken by choosing the X, Y, or Z dimension in that order of preference.
This means that since the photo is in "portrait" dimensions, the long edge of the photo will be set at the bottom left of the shape (S coordinate) making the short edge of the photo the T Coodinate. In other words, the photo will be displayed tilted on its side with no adjustment to the dimensions of the <Shape> object.
It also means that making your photo look right will be different for each shape that you choose. Here's what the example photo looks like:
Adding A TextureCoordinates Node
To correct any problem you may encounter, the next step is to apply a TextureCoordinate to the IndexedFaceSet node.
The TextureCoordinate node defines a 2D coordinate system (S,T). The bottom left corner (0.0, 0.0) moving horizontally is the S-axis. The top right corner (1.0, 1.0) moving vertically is the T-axis.
<x3d> <scene> <worldInfo title="Displaying Photos In X3D, Example 1"> </worldInfo> <navigationInfo headlight="true" type="ANY" transitionType="LINEAR"> </navigationInfo> <viewpoint position="0.0 5.0 20.0" centerOfRotation="0.0 0.0 0.0"> </viewpoint> <transform translation="0.0 5.0 -0.25"> <!-- Frame Mesh --> <group DEF="PictureFrame"> <!-- Frame Sides --> <transform translation="-4.96 0.0 0.0"> <shape DEF="longFrameRim"> <appearance> <material diffuseColor="0.153 0.298 0.396"> </material> </appearance> <cylinder top="true" bottom="true" side="true" solid="false" height="9.0" radius="0.5"> </cylinder> </shape> </transform> <transform translation="4.96 0.0 0.0"> <shape USE="longFrameRim"> </shape> </transform> <!-- Frame Back (No Transform needed, sitting at default location) --> <shape> <appearance> <material diffuseColor="0.153 0.298 0.396"> </material> </appearance> <box size="8.92 11.0 0.25"> </box> </shape> <!-- Frame Title --> <transform translation="0.0 -7.0 0.0"> <shape> <appearance> <material diffuseColor="0.373 0.153 0.396"> </material> </appearance> <text length="6.0" maxExtent="6.0" string="Downtown" solid="false"> <fontStyle family="SANS" justify="MIDDLE" size="1.0"> </text> </shape> </transform> </group> </transform> <!-- Photo (No Transform needed. Sitting at default location) --> <shape> <appearance> <imageTexture url="photo_texture.jpeg"> </imageTexture> </appearance> <indexedFaceSet solid="false" coordIndex="0 1 2 3 0" texCoordIndex="0 1 2 3 0"> <coordinate point="-3.96 10.0 0.0, -3.96 0.0 0.0, 3.96 0.0 0.0, 3.96 10.0 0.0"> </coordinate> <textureCoordinate point="0.0 1.0, 0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0"> </textureCoordinate> </indexedFaceSet> </shape> <background skyColor="0.20 0.20 0.20" groundColor="0.20 0.20 0.20"> </background> </scene> </x3d>
Notice that each point within the TextureCoordinate corresponds to a vertex (coordinate) within the IndexedFaceSet (texCoordIndex). If I had chosen a different shape, or created a custom/irregular shape, I would have to go through the same process of fitting the texture to the geometry. Here is the corrected example:
Task Complete
In some ways, it seems like a lot of work for single picture. Certainly it's more than what you encounter for the web. However, in a working vrignette, there may be an entire room that needs texturing so I guess it's all relative. But now you at least have starting point to tap into the vast display ability of X3D.