Sanctimonia: Shading

Kevin Fishburne posted a cryptic tweet last night:

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”.

1 Response

  1. Sanctimonia says:

    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.