How to shade a grid of quads

I am rendering a grid of quads to represent a section of a landscape. Each quad has its own separate texture (required) and elevation. I use an averaging algorithm so the vertices of a given quad line up with those of its eight adjacent quads (imperfect but fix is known). How can I shade the grid of quads with respect to an OpenGL light? My code looks like this:

Public Sub Tile_Grid()

  ' Draw tile grid texture array in render window.

  ' General declarations.
  Dim TileGridX As Short      ' Tile in tile grid texture array being rendered.
  Dim TileGridY As Short      ' Tile in tile grid texture array being rendered.
  Dim TileGridX1 As Short     ' Starting tile in tile grid texture array to be rendered.
  Dim TileGridY1 As Short     ' Starting tile in tile grid texture array to be rendered.
  Dim TileGridX2 As Short     ' Ending tile in tile grid texture array to be rendered.
  Dim TileGridY2 As Short     ' Ending tile in tile grid texture array to be rendered.
  Dim OffsetX As Short        ' Tile offset relative to tile grid position.
  Dim OffsetY As Short        ' Tile offset relative to tile grid position.
  Dim StepX As Short          ' Direction to step through tile grid texture array loop.
  Dim StepY As Short          ' Direction to step through tile grid texture array loop.
  Dim InverseX As Single
  Dim InverseY As Single
  Dim ScreenOffsetX As Single
  Dim ScreenOffsetY As Single
  Dim Elevation As Single
  Dim Elevation1 As Single
  Dim Elevation2 As Single
  Dim Elevation3 As Single
  Dim Elevation4 As Single
  Dim Elevation5 As Single
  Dim Elevation6 As Single
  Dim Elevation7 As Single
  Dim Elevation8 As Single
  Dim Elevation9 As Single
  Dim Counter As Single

  ' Assign initial values to variables.
  TileGridX1 = TileGrid.CenterX
  TileGridY1 = TileGrid.CenterY
  TileGridX2 = TileGrid.CenterX + TileGrid.Size - 1
  TileGridY2 = TileGrid.CenterY + TileGrid.Size - 1
  If TileGridX1 < TileGridX2 Then
    StepX = 1
  Else
    StepX = -1
  Endif
  If TileGridY1 < TileGridY2 Then
    StepY = 1
  Else
    StepY = -1
  Endif

  ' Enable depth testing.
  Gl.Enable(Gl.DEPTH_TEST)

  ' Render tile grid texture array.
  For TileGridY = TileGridY1 To TileGridY2 Step StepY
    For TileGridX = TileGridX1 To TileGridX2 Step StepX
      ' Calculate diagonally bordering tiles' elevations.
      Elevation1 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX - 1, Camera.CellGridY - TileGrid.Offset + OffsetY - 1] / 12 * ElevationScale
      Elevation2 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 0, Camera.CellGridY - TileGrid.Offset + OffsetY - 1] / 12 * ElevationScale
      Elevation3 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 1, Camera.CellGridY - TileGrid.Offset + OffsetY - 1] / 12 * ElevationScale
      Elevation4 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX - 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 0] / 12 * ElevationScale
      Elevation5 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 0, Camera.CellGridY - TileGrid.Offset + OffsetY + 0] / 12 * ElevationScale
      Elevation6 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 0] / 12 * ElevationScale
      Elevation7 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX - 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 1] / 12 * ElevationScale
      Elevation8 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 0, Camera.CellGridY - TileGrid.Offset + OffsetY + 1] / 12 * ElevationScale
      Elevation9 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 1] / 12 * ElevationScale
      ' Select texture using its ID.
      Gl.BindTexture(Gl.TEXTURE_2D, tTileGrid[Convert.Wrap_Short(0, TileGrid.Size - 1, TileGridX), Convert.Wrap_Short(0, TileGrid.Size - 1, TileGridY)][0])
      ' Create the quad the texture is drawn on.
      Gl.Begin(Gl.QUADS)
        ' Top-left vertex.
        Gl.TexCoord2i(0, 0)
        Gl.Vertex3f(TileGrid.WorldX + OffsetX, TileGrid.WorldY + OffsetY, (Elevation1 + Elevation2 + Elevation4 + Elevation5) / 4)
        ' Top-right vertex.
        Gl.TexCoord2i(1, 0)
        Gl.Vertex3f(TileGrid.WorldX + OffsetX + 1, TileGrid.WorldY + OffsetY, (Elevation2 + Elevation3 + Elevation6 + Elevation5) / 4)
        ' Bottom-right vertex.
        Gl.TexCoord2i(1, 1)
        Gl.Vertex3f(TileGrid.WorldX + OffsetX + 1, TileGrid.WorldY + OffsetY + 1, (Elevation6 + Elevation8 + Elevation9 + Elevation5) / 4)
        ' Bottom-left vertex.
        Gl.TexCoord2i(0, 1)
        Gl.Vertex3f(TileGrid.WorldX + OffsetX, TileGrid.WorldY + OffsetY + 1, (Elevation4 + Elevation7 + Elevation8 + Elevation5) / 4)
      Gl.End()
      ' Adjust tile position.
      OffsetX = OffsetX + 1
      If OffsetX = TileGrid.Size Then OffsetX = 0
    Next
    ' Adjust tile positions.
    OffsetX = 0
    OffsetY = OffsetY + 1
    If OffsetY = TileGrid.Size Then OffsetY = 0
  Next

End

The render looks like this:

This is a solo project, so the “simplest” way is preferred to the “right” way unfortunately. Any insight will be greatly appreciated!

Set your texture environment mode to GL_MODULATE. See glTexEnvi.
Set the material states for your quad. See docs for glMaterialf.
http://www.opengl.org/sdk/docs/man/

I made some progress in that I created a function to calculate the surface normal of a triangle:

Public Function Normal(p1 As Single[], p2 As Single[], p3 As Single[]) As Single[]

  ' Calculate and return surface normal of specified triangle.

  ' General declarations.
  Dim U As New Single[3]
  Dim V As New Single[3]
  Dim N As New Single[3]

  ' Calculate normal.
  U[0] = p2[0] - p1[0]
  U[1] = p2[1] - p1[1]
  U[2] = p2[2] - p1[2]
  V[0] = p3[0] - p1[0]
  V[1] = p3[1] - p1[1]
  V[2] = p3[2] - p1[2]
  N[0] = U[1] * V[2] - U[2] * V[1]
  N[1] = U[2] * V[0] - U[0] * V[2]
  N[2] = U[0] * V[1] - U[1] * V[0]
  Return N

End

I also followed your advice as best I could and burned through several dozen tutorials and reference docs to little avail. I have this in my init code:

  ' Set texture environment mode.
  Gl.TexEnvi(Gl.TEXTURE_ENV, Gl.TEXTURE_ENV_MODE, Gl.MODULATE)

  ' Enable backface culling.
  Gl.Enable(Gl.CULL_FACE)

  ' Set shading model.
  Gl.ShadeModel(Gl.SMOOTH)

  ' Enable lighting.
  Gl.Enable(Gl.LIGHTING)
  Gl.Enable(Gl.LIGHT0)
  Gl.Enable(Gl.COLOR_MATERIAL)

I updated the tile grid render procedure to look like this:

Public Sub Tile_Grid()

  ' Draw tile grid texture array in render window.

  ' General declarations.
  Dim TileGridX As Short      ' Tile in tile grid texture array being rendered.
  Dim TileGridY As Short      ' Tile in tile grid texture array being rendered.
  Dim TileGridX1 As Short     ' Starting tile in tile grid texture array to be rendered.
  Dim TileGridY1 As Short     ' Starting tile in tile grid texture array to be rendered.
  Dim TileGridX2 As Short     ' Ending tile in tile grid texture array to be rendered.
  Dim TileGridY2 As Short     ' Ending tile in tile grid texture array to be rendered.
  Dim OffsetX As Short        ' Tile offset relative to tile grid position.
  Dim OffsetY As Short        ' Tile offset relative to tile grid position.
  Dim StepX As Short          ' Direction to step through tile grid texture array loop.
  Dim StepY As Short          ' Direction to step through tile grid texture array loop.
  Dim InverseX As Single
  Dim InverseY As Single
  Dim ScreenOffsetX As Single
  Dim ScreenOffsetY As Single
  Dim Elevation As Single
  Dim Elevation1 As Single
  Dim Elevation2 As Single
  Dim Elevation3 As Single
  Dim Elevation4 As Single
  Dim Elevation5 As Single
  Dim Elevation6 As Single
  Dim Elevation7 As Single
  Dim Elevation8 As Single
  Dim Elevation9 As Single
  Dim TopLeft As New Single[3]
  Dim TopRight As New Single[3]
  Dim BottomRight As New Single[3]
  Dim BottomLeft As New Single[3]
  Dim Counter As Single

  ' Assign initial values to variables.
  TileGridX1 = TileGrid.CenterX
  TileGridY1 = TileGrid.CenterY
  TileGridX2 = TileGrid.CenterX + TileGrid.Size - 1
  TileGridY2 = TileGrid.CenterY + TileGrid.Size - 1
  If TileGridX1 < TileGridX2 Then
    StepX = 1
  Else
    StepX = -1
  Endif
  If TileGridY1 < TileGridY2 Then
    StepY = 1
  Else
    StepY = -1
  Endif

  ' Enable depth testing.
  Gl.Enable(Gl.DEPTH_TEST)

  ' Set material properties.
  Gl.ColorMaterial(Gl.FRONT, Gl.DIFFUSE)
  Gl.Materialf(Gl.FRONT, Gl.AMBIENT, 0)
  Gl.Materialf(Gl.FRONT, Gl.SPECULAR, 0)

  ' Render tile grid texture array.
  For TileGridY = TileGridY1 To TileGridY2 Step StepY

    For TileGridX = TileGridX1 To TileGridX2 Step StepX

      ' Calculate adjacent tiles' elevations.
      Elevation1 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX - 1, Camera.CellGridY - TileGrid.Offset + OffsetY - 1] / 12 * ElevationScale
      Elevation2 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 0, Camera.CellGridY - TileGrid.Offset + OffsetY - 1] / 12 * ElevationScale
      Elevation3 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 1, Camera.CellGridY - TileGrid.Offset + OffsetY - 1] / 12 * ElevationScale
      Elevation4 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX - 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 0] / 12 * ElevationScale
      Elevation5 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 0, Camera.CellGridY - TileGrid.Offset + OffsetY + 0] / 12 * ElevationScale
      Elevation6 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 0] / 12 * ElevationScale
      Elevation7 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX - 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 1] / 12 * ElevationScale
      Elevation8 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 0, Camera.CellGridY - TileGrid.Offset + OffsetY + 1] / 12 * ElevationScale
      Elevation9 = dElevation[Camera.CellGridX - TileGrid.Offset + OffsetX + 1, Camera.CellGridY - TileGrid.Offset + OffsetY + 1] / 12 * ElevationScale

      ' Calculate tile's vertex coordinates.
      BottomLeft[0] = TileGrid.WorldX + OffsetX
      BottomLeft[1] = TileGrid.WorldY + OffsetY + 1
      BottomLeft[2] = (Elevation4 + Elevation7 + Elevation8 + Elevation5) / 4
      BottomRight[0] = TileGrid.WorldX + OffsetX + 1
      BottomRight[1] = TileGrid.WorldY + OffsetY + 1
      BottomRight[2] = (Elevation6 + Elevation8 + Elevation9 + Elevation5) / 4
      TopRight[0] = TileGrid.WorldX + OffsetX + 1
      TopRight[1] = TileGrid.WorldY + OffsetY
      TopRight[2] = (Elevation2 + Elevation3 + Elevation6 + Elevation5) / 4
      TopLeft[0] = TileGrid.WorldX + OffsetX
      TopLeft[1] = TileGrid.WorldY + OffsetY
      TopLeft[2] = (Elevation1 + Elevation2 + Elevation4 + Elevation5) / 4

      ' Select texture using its ID.
      Gl.BindTexture(Gl.TEXTURE_2D, tTileGrid[Convert.Wrap_Short(0, TileGrid.Size - 1, TileGridX), Convert.Wrap_Short(0, TileGrid.Size - 1, TileGridY)][0])

      ' Create the quad the texture is drawn on.
      Gl.Begin(Gl.QUADS)
        ' Calculate surface normal.
        Gl.Normal3fv(Convert.Normal(BottomLeft, BottomRight, TopRight)) ' No idea how to apply proper normal to the two triangles of the quad, per OP.
        ' Bottom-left vertex.
        Gl.TexCoord2i(0, 1)
        Gl.Vertex3f(BottomLeft[0], BottomLeft[1], BottomLeft[2])
        ' Bottom-right vertex.
        Gl.TexCoord2i(1, 1)
        Gl.Vertex3f(BottomRight[0], BottomRight[1], BottomRight[2])
        ' Top-right vertex.
        Gl.TexCoord2i(1, 0)
        Gl.Vertex3f(TopRight[0], TopRight[1], TopRight[2])
        ' Top-left vertex.
        Gl.TexCoord2i(0, 0)
        Gl.Vertex3f(TopLeft[0], TopLeft[1], TopLeft[2])
      Gl.End()

      ' Adjust tile position.
      OffsetX = OffsetX + 1
      If OffsetX = TileGrid.Size Then OffsetX = 0

    Next

    ' Adjust tile positions.
    OffsetX = 0
    OffsetY = OffsetY + 1
    If OffsetY = TileGrid.Size Then OffsetY = 0

  Next

End

To be clear, I barely know what I’m doing, thus the post in the Beginners forum. I just need to know how to shade a quad, side by side with other quads (as in smooth rather than faceted), or if it’s possible. I’m not real clear on the logic (order of operations with respect to OpenGL calls) or even why normals aren’t automatically calculated for a quad or other primitive. Shading’s determined by defined light sources and calculated vectors perpendicular to each face/triangle; I get that. How to tell OpenGL to do its job in code is another matter entirely. I figured it’d be ultra simple to do this for a grid of quads, but it looks like the only simple thing is me. :frowning:

I noticed you called Gl.Enable(Gl.COLOR_MATERIAL). If you call that, then calls to glMaterial will have no effect.

I noticed you called
Gl.Materialf(Gl.FRONT, Gl.AMBIENT, 0)
Gl.Materialf(Gl.FRONT, Gl.SPECULAR, 0)

but I have no idea what that is suppose to be.
I suggest that you use glMaterialfv and pass an RGBA value.

In VB6, it looks like this
DIM Ambient(4) as Single
Ambient(0)=0.0
Ambient(1)=0.0
Ambient(2)=0.0
Ambient(3)=0.0
Call glMaterialfv(GL_FRONT, GL_AMBIENT, Ambient(0))

To be clear, I barely know what I’m doing, thus the post in the Beginners forum. I just need to know how to shade a quad, side by side with other quads (as in smooth rather than faceted), or if it’s possible. I’m not real clear on the logic (order of operations with respect to OpenGL calls) or even why normals aren’t automatically calculated for a quad or other primitive. Shading’s determined by defined light sources and calculated vectors perpendicular to each face/triangle; I get that. How to tell OpenGL to do its job in code is another matter entirely. I figured it’d be ultra simple to do this for a grid of quads, but it looks like the only simple thing is me.

  1. If you barely know what you are doing, then read the Red Book. OpenGL Programming Guide : Table of Contents
  2. It would look faceted probably because you are not smoothing the normal vectors between your quads.
  3. Your order of GL calls is fine.
  4. Normals are not calculated automatically by GL except in the case of glMap2d and glMap2f. These are for bezier surfaces.
  5. “I figured it’d be ultra simple to do this for a grid of quads, but it looks like the only simple thing is me”. That depends on how motivated you are. If you want to solve the problem yourself. 3D graphics is probably the hardest part of the application programming branch.

Thanks for the tips on handling materials. Got those figure out now. :slight_smile:

I’ll be ordering a hard copy from Amazon, although the online version didn’t have as much information as I thought in the sections I looked at.

What I’ve figured out so far is that I must calculate the normal of each triangle in the grid, then assign a normal to each triangle’s vertex calculated from those values. To get blending I’ll need to average the normals for triangle points which overlap. I think I also figured out how to normalize the magnitude of a normal (does OpenGL need them to be normalized?).

Motivation isn’t a problem. I’ve been making this game for over two years and it’s everything to me. It’s just maddening when my typical hacker mentality isn’t enough (and it usually is). And OpenGL so far has been a bit ball busting, yes.

This is my code for normal calculation (I’m using GAMBAS 3):

Public Function Normal(p1 As Single[], p2 As Single[], p3 As Single[]) As Single[]

  ' Calculate and return surface normal of specified triangle.

  ' General declarations.
  Dim N As New Single[3]
  Dim Magnitude As Single

  ' Calculate normal.
  N[0] = (p2[1] - p1[1]) * (p3[2] - p1[2]) - (p2[2] - p1[2]) * (p3[1] - p1[1])
  N[1] = (p2[2] - p1[2]) * (p3[0] - p1[0]) - (p2[0] - p1[0]) * (p3[2] - p1[2])
  N[2] = (p2[0] - p1[0]) * (p3[1] - p1[1]) - (p2[1] - p1[1]) * (p3[0] - p1[0])

  ' Normalize normal.
  Magnitude = Sqr(N[0] ^ 2 + N[1] ^ 2 + N[2] ^ 2)
  N[0] = N[0] / Magnitude
  N[1] = N[1] / Magnitude
  N[2] = N[2] / Magnitude

  Return N

End

Hopefully that’s correct. My other question would be what is a good reference for how a quad is drawn, exactly, based on the point specification order? I searched to no end on that one. Since I need to specify averaged normals for two triangles (six vertices) per quad, I don’t quite understand how the triangles manifest based on the point order. Maybe I should just draw two triangles per tile and not use quads at all? Confused on that point obviously… I did at least graph out the ideal geometry for a height field on paper:

http://eightvirtues.com/sanctimonia/misc/Ideal%20Height%20Field%20Geometry.png

Representing that with smooth shading is my ultimate goal. And thanks. :slight_smile: