Materials and Textures

Introduction

In this chapter we introduce material and texture properties. These concepts are extensively used in CHAI3D to describe how an object shall appear both visually and haptically.

Material Properties

Material properties define how a surface reflects light. They do this by defining color values much the same way that lights do. A material has color values for diffuse (direct) light, ambient (scattered) light, and specular (reflected) light, but these values define how the components of light are reflected off the material surface. The incoming light colors are combined with the corresponding material color and the resulting color is what we see on screen.

These material values have meaning only in relation to the light source(s). For instance if your material reflects ambient light (ie. the material m_ambient setting has a color value), it will still appear as black if there is no ambient light coming from the light source. Similarly, the diffuse material color may be yellow but it will appear greenish if the diffuse light color is green.

The material specular value is usually white, gray or black, because it has no hue of it's own, it is reflecting the hue of the light that is shining on it. Think of this color value as defining the percentage of light that will be reflected.

There is an additional material setting, shininess, that defines the size of the surface reflection. Here is a simple example where we define the ambient, diffuse and specular components of an object.

using namespace chai3d;
// setting material properties
object->m_material.m_ambient.set(0.1, 0.1, 0.6);
object->m_material.m_diffuse.set(0.3, 0.3, 0.9);
object->m_material.m_specular.set(1.0, 1.0, 1.0);

Some important things to remember about material properties and lighting:

  • Once lighting is enabled, the vertex colors are ignored. OpenGL calculates it's own color values for each vertex, based on the color of the light and the degree of illumination.
  • Normals are critical to correct lighting. The vertex normals tell OpenGL what angle a plane is at. Based on the angle of the plane with respect to the light and eye positions, OpenGL calculates the reflected light.
  • Normal lengths matter! They should all be length of 1, or light will fall unevenly. If you are building a mesh and have not defined any vertex normals, simply call function:
using namespace chai3d;
// compute all surface normals
myObject->computeAllNormals(true);

Emissive material property typically overrides diffuse, ambient and specular components (unless the glow is very faint). Emissive light is not radiant and doesn't cast light on other objects. You must use another light source to do that.

The level of transparency of an object can be controlled by calling method:

object->setTransparencyLevel(0.5);

In CHAI3D material properties are defined by class cMaterial. The class contains a number of graphical and physical constants which allow the programmer to define the way the object are rendered both haptically and graphically.

using namespace chai3d;
// GRAPHIC PROPERTIES:
cColorf m_ambient;
cColorf m_diffuse;
cColorf m_specular;
cColorf m_emission;
GLuint m_shininess;
// SURFACE STIFFNESS
double m_stiffness;
// DAMPING
// Damping level.
double m_damping;
// VISCOSITY
double m_viscosity;
// FRICTION
double m_staticFriction;
double m_dynamicFriction;
// TEXTURE
double m_textureLevel;
// VIBRATION
double m_vibrationFrequency;
double m_vibrationAmplitude;
// MAGNET
double m_magnetMaxForce;
double m_magnetMaxDistance;
// STICK AND SLIP
double m_stickSlipForceMax;
double m_stickSlipStiffness;
// HAPTIC MODES
bool m_useHapticFriction;
bool m_useHapticTexture;
bool m_useHapticShading;
bool m_hapticFrontSideOfTriangles;
bool m_hapticBackSideOfTriangles;
// SOUND PROPERTIES
cAudioBuffer* m_audioImpactBuffer;
cAudioBuffer* m_audioFrictionBuffer;
double m_audioImpactGain;
double m_audioFrictionGain;
double m_audioFrictionPitchGain;
double m_audioFrictionPitchOffset;

In you further explore the implementation of this class you will discover that cMaterial is only a data holder and does not actually contain much code to render objects. Other classes such as force models or haptic effects consult the values stored in the material properties and use them to modulate their results.

Material properties can be defined for each node composing a scene. However this does not mean that all material properties will be used during the rendering pass. If no haptic effects have been programmed for a particular object, then these parameters will have no effect.

The purpose for defining most graphic and haptic in a same container (cMaterial) is to facilitate the copying of properties from one object to another.

Texture Properties

Texture mapping is a computer graphics capability in which a separate image, referred to as the texture, is stretched onto a piece of 3D geometry and follows it however it is transformed. This image is also known as a texture map. This can be most any image, but its pixel dimensions must be a power of two. (This restriction has been lifted on some graphics cards such as the latest ATI or NVIDIA models) The x and y dimensions do not need to be the same power of two, just a power of two. So, a 128x512 image would work, but a 129x511 image would not.

fig-mesh.png
Mesh object and mapped Texture properties.
  • (a) 3D mesh object of a can. The object is composed on 8138 vertices and 14513 triangles. Gray material properties (ambient, diffuse and specular) are defined.
  • (b) Texture map image composed of 256 x 128 texels.
  • (c) Projection of the texture map onto the 3D mesh object. Each vertex carries a position in space (x, y, z) as well as a 2D texture coordinate (s, t).
  • (d) 3D mesh of a can rendered in wireframe mode.
  • (e) Combined image illustrating the projection of the texture map onto the mesh.
  • (f) Illustration of a single triangle, its three vertices and projected texture.

Also, to prevent confusion, the texture pixels are not called pixels. A pixel is a dot in the final screen image. A dot in the texture image is called a texture element, or texel. Similarly, to avoid terminology confusion, a texture's width and height dimensions are not called x and y. They are called s and t. A texture map is not generally indexed by its actual resolution coordinates. Instead, it is indexed by a coordinate system that is resolution-independent. The left side is always s = 0, the right side is s = 1, the bottom is t = 0, and the top is t = 1. Thus, you do not need to be aware of the texture's resolution when you are specifying coordinates that point into it. Think of s and t as a measure of what fraction of the way you are into the texture.

In CHAI3D texture properties are defined by classes cTexture1d, cTexture2d, and cTexture3d. Each class contains a number of attributes which allow the programmer to define the exact way the texture is mapped to the object.

In the following example we demonstrate how to load texture in memory and apply it to a square composed of two triangles.

// create a mesh
object = new cMesh();
// add object to world
world->addChild(object);
// create 4 vertices
int vertex0 = object->newVertex();
int vertex1 = object->newVertex();
int vertex2 = object->newVertex();
int vertex3 = object->newVertex();
// assign position, surface normal, and texture coordinates to each vertex
object->m_vertices->setLocalPos(vertex0,-0.1,-0.1, 0.0);
object->m_vertices->setNormal(vertex0, 0.0, 0.0, 1.0);
object->m_vertices->setTexCoord(vertex0, 0.0, 0.0);
object->m_vertices->setLocalPos(vertex1, 0.1,-0.1, 0.0);
object->m_vertices->setNormal(vertex1, 0.0, 0.0, 1.0);
object->m_vertices->setTexCoord(vertex1, 1.0, 0.0);
object->m_vertices->setLocalPos(vertex2, 0.1, 0.1, 0.0);
object->m_vertices->setNormal(vertex2, 0.0, 0.0, 1.0);
object->m_vertices->setTexCoord(vertex2, 1.0, 1.0);
object->m_vertices->setLocalPos(vertex3,-0.1, 0.1, 0.0);
object->m_vertices->setNormal(vertex3, 0.0, 0.0, 1.0);
object->m_vertices->setTexCoord(vertex3, 0.0, 1.0);
// create two triangles by assigning their vertex IDs
object->m_triangles->newTriangle(vertex0, vertex1, vertex2);
object->m_triangles->newTriangle(vertex0, vertex2, vertex3);
// create a texture map
object->m_texture = cTexture2d::create();
// load a texture image file
object->m_texture->loadFromFile(RESOURCE_PATH("myTextureFile.jpg"));
// enable texture mapping
object->setUseTexture(true);
// assign a white material color that is modulated with the texture
object->m_material->setWhite();
// assign some haptic properties
object->m_material->setStiffness(500);
object->m_material->setStaticFriction(0.3);
object->m_material->setDynamicFriction(0.2);
object->m_material->setTextureLevel(1.0);
// create a collision detector for this object
object->createAABBCollisionDetector(toolRadius);
fig-texture-squares.png
Example 14-textures - 4 squares composed each of 4 vertices, 2 triangles, and a texture map.