More On Colors: Smooth Coloring
(updated in 26/05/2001)

Previous | Index | Next

In the previous lesson, you've learned how draw primitives in HOpenGL. You may have noticed that each primitive drawn had an unique color (we call this flat coloring). Although it is fast, this drawing method is also very "ugly", and it does not show the HOpenGL potential.

In this lesson, you'll learn how to draw primitives in a different way, called smooth coloring. This method works in the following way: each vertex, when created, has an extra information, concerning about its color. Hence, a primitive, which is built from a group of vertices, may have a lot of vertices with different colors. The color of the primitive will be a mix of the color of its vertices, as shown in the following picture:


As you may have observed, the upper vertex of the triangle is blue, the left one is red and the right one is green. The color of regions that are far away from the vertices is a mix of these three colors. Please notice that the above picture doesn't have an excellent quality, beacuse it is a .JPG (with a .GIF, the quality would be even poorer).

The main purpose of this lesson is to develop the code to draw the above triangle. At first, suppose that you would like to draw the same triangle with an unique color, red. If the bottom left corner of the screen is the point (0,0,0), the following code would fit our purposes:

	triangle :: IO ()
	triangle = do
		color (Color3 1.0 0.0 0.0 :: Color3 GLfloat)
		beginEnd Triangles $ mapM_ vertex [
			Vertex3 0.20 0.20 0.0,
			Vertex3 0.80 0.20 0.0,
			Vertex3 0.50 0.80 (0.0 :: GLfloat)]

Now let me introduce you something new. The above code is equivalent to the following one:

	triangle :: IO ()
	triangle = do
		color (Color3 1.0 0.0 0.0 :: Color3 GLfloat)
		beginEnd Triangles $ do
			vertex (Vertex3 0.20 0.20 (0.0 :: GLfloat))
			vertex (Vertex3 0.80 0.20 (0.0 :: GLfloat))
			vertex (Vertex3 0.50 0.80 (0.0 :: GLfloat))

The difference is that, in the first code, we "mapped" the function vertex to a list of vertices. In the second code, we used this function vertex by vertex (no lists were used). The main advantage of this is that we can introduce one or more commands between the vertices declarations. The command we'd like to introduce, in this example, is one that changes the current color. Hence, we'll reach the following code:

	triangle :: IO ()
	triangle = do
		beginEnd Triangles $ do
			color (Color3 1.0 0.0 0.0 :: Color3 GLfloat)
			vertex (Vertex3 0.20 0.20 (0.0 :: GLfloat))
			color (Color3 0.0 1.0 0.0 :: Color3 GLfloat)
			vertex (Vertex3 0.80 0.20 (0.0 :: GLfloat))
			color (Color3 0.0 0.0 1.0 :: Color3 GLfloat)
			vertex (Vertex3 0.50 0.80 (0.0 :: GLfloat))

Notice that each vertex has a different color. Now there's only one thing left to be done: say to our program that we would like to work with smooth coloring. In order words, we'd like our program to remember the color of each vertex when it draws the primitive. This is done by the shadeModel function. It has one parameters, whose values can be:

  • GL.Flat - Use this if you whish to work with flat coloring. The color of a primitive will be the color of its last vertex.
  • GL.Smooth - Use this if you whish to work with smooth coloring. The color of a primitive will be a mix of the colors of its vertices.

As I said before, the first method is the ugliest one, but also the fastest. The second method requires more processing time, but its results are really incomparable.

If we were switching between both flat and smooth methods, the best place to call the shadeModel function would be before you draw something that uses a method different from the current method. In our example, we're always working with smooth coloring, so the only thing we need to do is to include the following code in the myInit function:

		shadeModel GL.Smooth

Keep in mind that the shadeModel function is defined in module GL_Colors, which is automatically imported if your program already imports module GL.

Finally, adding a call to triangle in the display function, we'll get the following code:

	import GLUT
	import GL

	myInit :: IO () 
	myInit = do
		clearColor (Color4 0.0 0.0 0.0 0.0)
		matrixMode Projection
		loadIdentity
		ortho 0.0 1.0 0.0 1.0 (-1.0) 1.0
		shadeModel GL.Smooth
	
	triangle :: IO ()
	triangle = do
		beginEnd Triangles $ do
			color (Color3 1.0 0.0 0.0 :: Color3 GLfloat)
			vertex (Vertex3 0.20 0.20 (0.0 :: GLfloat))
			color (Color3 0.0 1.0 0.0 :: Color3 GLfloat)
			vertex (Vertex3 0.80 0.20 (0.0 :: GLfloat))
			color (Color3 0.0 0.0 1.0 :: Color3 GLfloat)
			vertex (Vertex3 0.50 0.80 (0.0 :: GLfloat))
			
	display :: DisplayAction
	display = do 
		clear [ColorBufferBit]
		triangle
		flush

	main :: IO ()
	main = do
		GLUT.init Nothing
		createWindow "Smooth" (return ()) [ Single, GLUT.Rgb ]
				(Just (WindowPosition 100 100))
				(Just (WindowSize     300 250))
		myInit
		displayFunc (display)
		mainLoop

If you change GL.Smooth by GL.Flat, in myInit, you'll get a program that opens the following window:





Hints and Tips:
  • Smooth coloring is the default drawing method of (H)OpenGL. Hence, if your program only works with primitives that have an unique color, do not hesitate in calling shadeModel GL.Flat to improve your program speed.




Downloads:


HOpenGL Tutorial - Andre W B Furtado
More On Colors: Smooth Coloring
www.cin.ufpe.br/~haskell/hopengl/smooth.html
Last updated in 26/05/2001
Informatics Center (CIn) - UFPE
Recife - PE - Brazil