Sanctimonia: Shading
Kevin Fishburne posted a cryptic tweet last night:
This: eightvirtues.com/sanctimonia/vi… Next time at 10 FPS.
— Eight Virtues (@eightvirtues) July 5, 2012
In it, he links to this video, which is in the OGG Vorbis Video format, which runs for about a minute and shows off…well, primarily it shows off the excavation and terrain deformation system in the game, but with the addition of shadows and other shader features.
The video is a bit choppy as well, which is…probably what Kevin means when he talks about a particular framerate for “next time”.
This is the current procedure that includes the shading madness (and it is madness; I can’t believe I wrote it and am still writing it):
Public Sub Tile_Grid()
‘ Render tile grid texture array.
‘ General declarations.
‘ Tile and normals grid coordinate data.
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 CellGridX As Short ‘ Tile in cell grid whose normals are being calculated.
Dim CellGridY As Short ‘ Tile in cell grid whose normals are being calculated.
Dim CellGridX1 As Short ‘ Starting tile in cell grid to calculate normals.
Dim CellGridY1 As Short ‘ Starting tile in cell grid to calculate normals.
Dim CellGridX2 As Short ‘ Ending tile in cell grid to calculate normals.
Dim CellGridY2 As Short ‘ Ending tile in cell grid to calculate normals.
Dim OffsetX As Short ‘ Tile offset relative to tile/normals grid position.
Dim OffsetY As Short ‘ Tile offset relative to tile/normals 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 IsEven As Boolean ‘ Whether or not tile whose normals are being calculated is even or odd relative to the world map.
‘ Tile vertex coordinates.
Dim TopLeft As New Single[3] ‘ Coordinates of top left vertex.
Dim TopRight As New Single[3] ‘ Coordinates of top right vertex.
Dim BottomLeft As New Single[3] ‘ Coordinates of bottom left vertex.
Dim BottomRight As New Single[3] ‘ Coordinates of bottom right vertex.
‘ Averaged normals.
Dim FinalNormal As New Single[3] ‘ Averaged normals for current vertex.
‘ Material property arrays.
Dim MatAmbient As New Single[4] ‘ Ambient color of tile being rendered.
Dim MatDiffuse As New Single[4] ‘ Diffuse color of tile being rendered.
Dim MatSpecular As New Single[4] ‘ Specular color of tile being rendered.
‘ Misc.
Dim Counter As Single ‘ Generic counter.
‘ *** DEBUG ***
‘Gl.PolygonMode(Gl.FRONT, Gl.LINE)
‘ Assign initial values to variables.
CellGridX1 = Convert.World2CellGrid(TileGrid.WorldX – 1, CellGrid.WorldX)
CellGridY1 = Convert.World2CellGrid(TileGrid.WorldY – 1, CellGrid.WorldY)
CellGridX2 = CellGridX1 + TileGrid.Size + 1
CellGridY2 = CellGridY1 + TileGrid.Size + 1
OffsetX = 0
OffsetY = 0
‘ Calculate parity of first tile to be processed.
If Even(TileGrid.WorldX + TileGrid.WorldY) Then
IsEven = True
Else
IsEven = False
Endif
‘ Loop through each tile in tile grid plus bordering tiles.
For CellGridY = CellGridY1 To CellGridY2
For CellGridX = CellGridX1 To CellGridX2
‘ Calculate four vertex coordinates and elevations.
TopLeft[0] = 0
TopLeft[1] = 0
TopLeft[2] = dElevation[CellGridX + 0, CellGridY + 0] / 12 * ElevationScale
BottomLeft[0] = 0
BottomLeft[1] = 1
BottomLeft[2] = dElevation[CellGridX + 0, CellGridY + 1] / 12 * ElevationScale
BottomRight[0] = 1
BottomRight[1] = 1
BottomRight[2] = dElevation[CellGridX + 1, CellGridY + 1] / 12 * ElevationScale
TopRight[0] = 1
TopRight[1] = 0
TopRight[2] = dElevation[CellGridX + 1, CellGridY + 0] / 12 * ElevationScale
‘ Check tile’s parity.
If IsEven Then
‘ Calculate even tile’s surface normals.
NormalsGrid[OffsetX, OffsetY].A = Convert.Normal(TopLeft, BottomLeft, BottomRight)
NormalsGrid[OffsetX, OffsetY].B = Convert.Normal(TopLeft, BottomRight, TopRight)
Else
‘ Calculate odd tile’s surface normals.
NormalsGrid[OffsetX, OffsetY].A = Convert.Normal(TopLeft, BottomLeft, TopRight)
NormalsGrid[OffsetX, OffsetY].B = Convert.Normal(BottomLeft, BottomRight, TopRight)
Endif
‘ Adjust normal grid position.
OffsetX = OffsetX + 1
If OffsetX = TileGrid.Size + 2 Then OffsetX = 0
‘ Invert tile parity.
If IsEven = True Then
IsEven = False
Else
IsEven = True
Endif
Next
‘ Adjust normal grid position.
OffsetX = 0
OffsetY = OffsetY + 1
If OffsetY = TileGrid.Size + 2 Then OffsetY = 0
Next
‘ Assign initial values to variables.
TileGridX1 = TileGrid.StartX
TileGridY1 = TileGrid.StartY
TileGridX2 = TileGrid.StartX + TileGrid.Size – 1
TileGridY2 = TileGrid.StartY + TileGrid.Size – 1
OffsetX = 0
OffsetY = 0
If TileGridX1 < TileGridX2 Then
StepX = 1
Else
StepX = -1
Endif
If TileGridY1 < TileGridY2 Then
StepY = 1
Else
StepY = -1
Endif
' Set tile grid material properties.
MatAmbient[0] = 0
MatAmbient[1] = 0
MatAmbient[2] = 0
MatAmbient[3] = 0
MatDiffuse[0] = 1
MatDiffuse[1] = 1
MatDiffuse[2] = 1
MatDiffuse[3] = 1
MatSpecular[0] = 0
MatSpecular[1] = 0
MatSpecular[2] = 0
MatSpecular[3] = 0
Gl.Materialfv(Gl.FRONT, Gl.AMBIENT, MatAmbient)
Gl.Materialfv(Gl.FRONT, Gl.DIFFUSE, MatDiffuse)
Gl.Materialfv(Gl.FRONT, Gl.SPECULAR, MatSpecular)
' Enable depth testing.
Gl.Enable(Gl.DEPTH_TEST)
' Loop through each tile in tile grid.
For TileGridY = TileGridY1 To TileGridY2 Step StepY
For TileGridX = TileGridX1 To TileGridX2 Step StepX
' 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])
' Calculate cell grid coordinates.
CellGridX = Camera.CellGridX – TileGrid.Offset + OffsetX
CellGridY = Camera.CellGridY – TileGrid.Offset + OffsetY
' Calculate tile's vertex coordinates.
TopLeft[0] = TileGrid.WorldX + OffsetX + 0.5
TopLeft[1] = TileGrid.WorldY + OffsetY + 0.5
TopLeft[2] = dElevation[CellGridX + 0, CellGridY + 0] / 12 * ElevationScale
TopRight[0] = TileGrid.WorldX + OffsetX + 1 + 0.5
TopRight[1] = TileGrid.WorldY + OffsetY + 0.5
TopRight[2] = dElevation[CellGridX + 1, CellGridY + 0] / 12 * ElevationScale
BottomLeft[0] = TileGrid.WorldX + OffsetX + 0.5
BottomLeft[1] = TileGrid.WorldY + OffsetY + 1 + 0.5
BottomLeft[2] = dElevation[CellGridX + 0, CellGridY + 1] / 12 * ElevationScale
BottomRight[0] = TileGrid.WorldX + OffsetX + 1 + 0.5
BottomRight[1] = TileGrid.WorldY + OffsetY + 1 + 0.5
BottomRight[2] = dElevation[CellGridX + 1, CellGridY + 1] / 12 * ElevationScale
' Temporarily increment offsets so they'll match normals grid.
Inc OffsetX
Inc OffsetY
' Check tile's parity.
If IsEven Then
' Create the quad the texture is drawn on.
Gl.Begin(Gl.QUADS)
' Calculate top-left vertex normal by averaging surrounding eight surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX – 1, OffsetY – 1].A[0] + NormalsGrid[OffsetX – 1, OffsetY – 1].B[0] + NormalsGrid[OffsetX, OffsetY – 1].A[0] + NormalsGrid[OffsetX, OffsetY – 1].B[0] + NormalsGrid[OffsetX – 1, OffsetY].A[0] + NormalsGrid[OffsetX – 1, OffsetY].B[0] + NormalsGrid[OffsetX, OffsetY].A[0] + NormalsGrid[OffsetX, OffsetY].B[0]) / 8
FinalNormal[1] = (NormalsGrid[OffsetX – 1, OffsetY – 1].A[1] + NormalsGrid[OffsetX – 1, OffsetY – 1].B[1] + NormalsGrid[OffsetX, OffsetY – 1].A[1] + NormalsGrid[OffsetX, OffsetY – 1].B[1] + NormalsGrid[OffsetX – 1, OffsetY].A[1] + NormalsGrid[OffsetX – 1, OffsetY].B[1] + NormalsGrid[OffsetX, OffsetY].A[1] + NormalsGrid[OffsetX, OffsetY].B[1]) / 8
FinalNormal[2] = (NormalsGrid[OffsetX – 1, OffsetY – 1].A[2] + NormalsGrid[OffsetX – 1, OffsetY – 1].B[2] + NormalsGrid[OffsetX, OffsetY – 1].A[2] + NormalsGrid[OffsetX, OffsetY – 1].B[2] + NormalsGrid[OffsetX – 1, OffsetY].A[2] + NormalsGrid[OffsetX – 1, OffsetY].B[2] + NormalsGrid[OffsetX, OffsetY].A[2] + NormalsGrid[OffsetX, OffsetY].B[2]) / 8
' Set top-left normal.
Gl.Normal3fv(FinalNormal)
' Set top-left vertex.
Gl.TexCoord2i(0, 0)
Gl.Vertex3f(TopLeft[0], TopLeft[1], TopLeft[2])
' Calculate bottom-left vertex normal by averaging surrounding four surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX – 1, OffsetY].B[0] + NormalsGrid[OffsetX, OffsetY].A[0] + NormalsGrid[OffsetX – 1, OffsetY + 1].B[0] + NormalsGrid[OffsetX, OffsetY + 1].A[0]) / 4
FinalNormal[1] = (NormalsGrid[OffsetX – 1, OffsetY].B[1] + NormalsGrid[OffsetX, OffsetY].A[1] + NormalsGrid[OffsetX – 1, OffsetY + 1].B[1] + NormalsGrid[OffsetX, OffsetY + 1].A[1]) / 4
FinalNormal[2] = (NormalsGrid[OffsetX – 1, OffsetY].B[2] + NormalsGrid[OffsetX, OffsetY].A[2] + NormalsGrid[OffsetX – 1, OffsetY + 1].B[2] + NormalsGrid[OffsetX, OffsetY + 1].A[2]) / 4
' Set bottom-left normal.
Gl.Normal3fv(FinalNormal)
' Set bottom-left vertex.
Gl.TexCoord2i(0, 1)
Gl.Vertex3f(BottomLeft[0], BottomLeft[1], BottomLeft[2])
' Calculate bottom-right vertex normal by averaging surrounding eight surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX, OffsetY].A[0] + NormalsGrid[OffsetX, OffsetY].B[0] + NormalsGrid[OffsetX + 1, OffsetY].A[0] + NormalsGrid[OffsetX + 1, OffsetY].B[0] + NormalsGrid[OffsetX, OffsetY + 1].A[0] + NormalsGrid[OffsetX, OffsetY + 1].B[0] + NormalsGrid[OffsetX + 1, OffsetY + 1].A[0] + NormalsGrid[OffsetX + 1, OffsetY + 1].B[0]) / 8
FinalNormal[1] = (NormalsGrid[OffsetX, OffsetY].A[1] + NormalsGrid[OffsetX, OffsetY].B[1] + NormalsGrid[OffsetX + 1, OffsetY].A[1] + NormalsGrid[OffsetX + 1, OffsetY].B[1] + NormalsGrid[OffsetX, OffsetY + 1].A[1] + NormalsGrid[OffsetX, OffsetY + 1].B[1] + NormalsGrid[OffsetX + 1, OffsetY + 1].A[1] + NormalsGrid[OffsetX + 1, OffsetY + 1].B[1]) / 8
FinalNormal[2] = (NormalsGrid[OffsetX, OffsetY].A[2] + NormalsGrid[OffsetX, OffsetY].B[2] + NormalsGrid[OffsetX + 1, OffsetY].A[2] + NormalsGrid[OffsetX + 1, OffsetY].B[2] + NormalsGrid[OffsetX, OffsetY + 1].A[2] + NormalsGrid[OffsetX, OffsetY + 1].B[2] + NormalsGrid[OffsetX + 1, OffsetY + 1].A[2] + NormalsGrid[OffsetX + 1, OffsetY + 1].B[2]) / 8
' Set bottom-right normal.
Gl.Normal3fv(FinalNormal)
' Set bottom-right vertex.
Gl.TexCoord2i(1, 1)
Gl.Vertex3f(BottomRight[0], BottomRight[1], BottomRight[2])
' Calculate top-right vertex normal by averaging surrounding four surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX, OffsetY – 1].B[0] + NormalsGrid[OffsetX + 1, OffsetY – 1].A[0] + NormalsGrid[OffsetX, OffsetY].B[0] + NormalsGrid[OffsetX + 1, OffsetY].A[0]) / 4
FinalNormal[1] = (NormalsGrid[OffsetX, OffsetY – 1].B[1] + NormalsGrid[OffsetX + 1, OffsetY – 1].A[1] + NormalsGrid[OffsetX, OffsetY].B[1] + NormalsGrid[OffsetX + 1, OffsetY].A[1]) / 4
FinalNormal[2] = (NormalsGrid[OffsetX, OffsetY – 1].B[2] + NormalsGrid[OffsetX + 1, OffsetY – 1].A[2] + NormalsGrid[OffsetX, OffsetY].B[2] + NormalsGrid[OffsetX + 1, OffsetY].A[2]) / 4
' Set top-right normal.
Gl.Normal3fv(FinalNormal)
' Set top-right vertex.
Gl.TexCoord2i(1, 0)
Gl.Vertex3f(TopRight[0], TopRight[1], TopRight[2])
Gl.End()
Else
' Create the quad the texture is drawn on.
Gl.Begin(Gl.QUADS)
' Calculate top-right vertex normal by averaging surrounding eight surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX, OffsetY – 1].A[0] + NormalsGrid[OffsetX, OffsetY – 1].B[0] + NormalsGrid[OffsetX + 1, OffsetY – 1].A[0] + NormalsGrid[OffsetX + 1, OffsetY – 1].B[0] + NormalsGrid[OffsetX, OffsetY].A[0] + NormalsGrid[OffsetX, OffsetY].B[0] + NormalsGrid[OffsetX + 1, OffsetY].A[0] + NormalsGrid[OffsetX + 1, OffsetY].B[0]) / 8
FinalNormal[1] = (NormalsGrid[OffsetX, OffsetY – 1].A[1] + NormalsGrid[OffsetX, OffsetY – 1].B[1] + NormalsGrid[OffsetX + 1, OffsetY – 1].A[1] + NormalsGrid[OffsetX + 1, OffsetY – 1].B[1] + NormalsGrid[OffsetX, OffsetY].A[1] + NormalsGrid[OffsetX, OffsetY].B[1] + NormalsGrid[OffsetX + 1, OffsetY].A[1] + NormalsGrid[OffsetX + 1, OffsetY].B[1]) / 8
FinalNormal[2] = (NormalsGrid[OffsetX, OffsetY – 1].A[2] + NormalsGrid[OffsetX, OffsetY – 1].B[2] + NormalsGrid[OffsetX + 1, OffsetY – 1].A[2] + NormalsGrid[OffsetX + 1, OffsetY – 1].B[2] + NormalsGrid[OffsetX, OffsetY].A[2] + NormalsGrid[OffsetX, OffsetY].B[2] + NormalsGrid[OffsetX + 1, OffsetY].A[2] + NormalsGrid[OffsetX + 1, OffsetY].B[2]) / 8
' Set top-right normal.
Gl.Normal3fv(FinalNormal)
' Top-right vertex.
Gl.TexCoord2i(1, 0)
Gl.Vertex3f(TopRight[0], TopRight[1], TopRight[2])
' Calculate top-left vertex normal by averaging surrounding four surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX – 1, OffsetY – 1].B[0] + NormalsGrid[OffsetX, OffsetY – 1].A[0] + NormalsGrid[OffsetX – 1, OffsetY].B[0] + NormalsGrid[OffsetX, OffsetY].A[0]) / 4
FinalNormal[1] = (NormalsGrid[OffsetX – 1, OffsetY – 1].B[1] + NormalsGrid[OffsetX, OffsetY – 1].A[1] + NormalsGrid[OffsetX – 1, OffsetY].B[1] + NormalsGrid[OffsetX, OffsetY].A[1]) / 4
FinalNormal[2] = (NormalsGrid[OffsetX – 1, OffsetY – 1].B[2] + NormalsGrid[OffsetX, OffsetY – 1].A[2] + NormalsGrid[OffsetX – 1, OffsetY].B[2] + NormalsGrid[OffsetX, OffsetY].A[2]) / 4
' Set top-left normal.
Gl.Normal3fv(FinalNormal)
' Top-left vertex.
Gl.TexCoord2i(0, 0)
Gl.Vertex3f(TopLeft[0], TopLeft[1], TopLeft[2])
' Calculate bottom-left vertex normal by averaging surrounding eight surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX – 1, OffsetY].A[0] + NormalsGrid[OffsetX – 1, OffsetY].B[0] + NormalsGrid[OffsetX, OffsetY].A[0] + NormalsGrid[OffsetX, OffsetY].B[0] + NormalsGrid[OffsetX – 1, OffsetY + 1].A[0] + NormalsGrid[OffsetX – 1, OffsetY + 1].B[0] + NormalsGrid[OffsetX, OffsetY + 1].A[0] + NormalsGrid[OffsetX, OffsetY + 1].B[0]) / 8
FinalNormal[1] = (NormalsGrid[OffsetX – 1, OffsetY].A[1] + NormalsGrid[OffsetX – 1, OffsetY].B[1] + NormalsGrid[OffsetX, OffsetY].A[1] + NormalsGrid[OffsetX, OffsetY].B[1] + NormalsGrid[OffsetX – 1, OffsetY + 1].A[1] + NormalsGrid[OffsetX – 1, OffsetY + 1].B[1] + NormalsGrid[OffsetX, OffsetY + 1].A[1] + NormalsGrid[OffsetX, OffsetY + 1].B[1]) / 8
FinalNormal[2] = (NormalsGrid[OffsetX – 1, OffsetY].A[2] + NormalsGrid[OffsetX – 1, OffsetY].B[2] + NormalsGrid[OffsetX, OffsetY].A[2] + NormalsGrid[OffsetX, OffsetY].B[2] + NormalsGrid[OffsetX – 1, OffsetY + 1].A[2] + NormalsGrid[OffsetX – 1, OffsetY + 1].B[2] + NormalsGrid[OffsetX, OffsetY + 1].A[2] + NormalsGrid[OffsetX, OffsetY + 1].B[2]) / 8
' Set bottom-left normal.
Gl.Normal3fv(FinalNormal)
' Bottom-left vertex.
Gl.TexCoord2i(0, 1)
Gl.Vertex3f(BottomLeft[0], BottomLeft[1], BottomLeft[2])
' Calculate bottom-right vertex normal by averaging surrounding four surface normals.
FinalNormal[0] = (NormalsGrid[OffsetX, OffsetY].B[0] + NormalsGrid[OffsetX + 1, OffsetY].A[0] + NormalsGrid[OffsetX, OffsetY + 1].B[0] + NormalsGrid[OffsetX + 1, OffsetY + 1].A[0]) / 4
FinalNormal[1] = (NormalsGrid[OffsetX, OffsetY].B[1] + NormalsGrid[OffsetX + 1, OffsetY].A[1] + NormalsGrid[OffsetX, OffsetY + 1].B[1] + NormalsGrid[OffsetX + 1, OffsetY + 1].A[1]) / 4
FinalNormal[2] = (NormalsGrid[OffsetX, OffsetY].B[2] + NormalsGrid[OffsetX + 1, OffsetY].A[2] + NormalsGrid[OffsetX, OffsetY + 1].B[2] + NormalsGrid[OffsetX + 1, OffsetY + 1].A[2]) / 4
' Set bottom-right normal.
Gl.Normal3fv(FinalNormal)
' Bottom-right vertex.
Gl.TexCoord2i(1, 1)
Gl.Vertex3f(BottomRight[0], BottomRight[1], BottomRight[2])
Gl.End()
Endif
' Decrement offsets to return them to their previous values.
Dec OffsetX
Dec OffsetY
' Adjust tile position.
OffsetX = OffsetX + 1
If OffsetX = TileGrid.Size Then OffsetX = 0
' Invert tile parity.
If IsEven = True Then
IsEven = False
Else
IsEven = True
Endif
Next
' Adjust tile positions.
OffsetX = 0
OffsetY = OffsetY + 1
If OffsetY = TileGrid.Size Then OffsetY = 0
Next
' *** DEBUG ***
'Gl.PolygonMode(Gl.FRONT, Gl.FILL)
End
The only thing I can't figure out yet is how to properly position the primary light source. I keep getting seemingly unpredictable results when I test different positions. Doesn't make sense.
If anyone has any basic advice on positioning an OpenGL light source with respect to Glu.LookAt-induced matrix transformations, please help me. I position the light accurately, but bullshit results ensue. Not good.