PDA

View Full Version : How to shade a grid of quads



kevinfishburne
06-16-2012, 11:44 PM
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:

http://youtu.be/vFan5CKB98c

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

V-man
06-17-2012, 05:06 AM
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/

kevinfishburne
06-18-2012, 11:40 PM
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. :(

V-man
06-19-2012, 05:39 AM
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. http://www.glprogramming.com/red/
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.

kevinfishburne
06-24-2012, 11:18 PM
Thanks for the tips on handling materials. Got those figure out now. :)


1. If you barely know what you are doing, then read the Red Book. http://www.glprogramming.com/red/

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.


2. It would look faceted probably because you are not smoothing the normal vectors between your quads.

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?).


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.

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. :)