Alright. It appears the best way to draw an atmosphere layer is to use a triangle fan to create an untextured circle and use the material settings exclusively for color and alpha (as opposed to a textured quad or triangle strip with stitching and a material). I’ve modified my code to draw a 200-layer “sandwich” of triangle fans through the front 60% of the planet model. I’m setting the normal of each vertex of each triangle strip relative to the planet center. Also Z is up/down/elevation and -Y goes into the screen, which is a bit unusual but I’m more comfortable with. I also moved the code to a display list for efficiency, so the atmosphere dimensions are 1x1x1 until scaled by the procedure drawing the planet.

The only remaining issue is that despite setting the normals of the triangle fans as if they were spherical, the shading of the triangle fans appears to operate on 1/4 of them with a hard cutoff. Here’s a screenshot:

At this point I think I’m overlooking something relatively simple. Does anything about my logic or implementation jump out as being boneheaded here? Interestingly this is the same problem when I was setting the vertex normals of quads (rather than triangle fans) for the atmosphere layers, so I don’t think it’s anything new that’s causing the problem. Here’s the code for the procedure drawing the atmosphere layers:

```
Public Sub AtmosphereCreateLists()
' Create atmosphere display lists.
' General declarations.
Dim ScalePosition As Single ' Y coordinate of current atmosphere layer.
Dim ScaleRadius As Single ' Half scale of current atmosphere layer.
Dim Radius As Single ' Radius of atmosphere precalculated for efficiency.
Dim Alpha As Single ' Alpha value of each atmosphere layer.
Dim Layers As Single ' Number of atmosphere layers to render.
Dim LayerStep As Single ' Step value of atmosphere layer For...Next loop.
Dim LayerCoverage As Single ' Percentage of planet atmosphere layers will cover (0 - 1).
Dim Outer As New Single[2] ' Coordinates of outer vertex when drawing triangle fan.
Dim Triangles As Single ' Number of triangles triangle fan is composed of.
Dim Angle As Single ' Current angle of triangle fan vertex.
Dim AngleStep As Single ' Number of degrees Angle is incremented by for triangle fan.
Dim Counter As Short
' Assign initial values to variables.
Radius = 0.5
Layers = 200
LayerCoverage = 0.6
LayerStep = LayerCoverage / Layers
Alpha = 1 / Layers
Triangles = 64
AngleStep = 360 / Triangles
' Create display list.
AtmosphereList[0] = Gl.GenLists(1)
Gl.NewList(AtmosphereList[0], Gl.COMPILE)
' Set up render parameters.
DepthReadOnly
Gl.BlendFunc(Gl.SRC_ALPHA, Gl.ONE)
Gl.BindTexture(Gl.TEXTURE_2D, 0)
'MaterialFull([0, 0, 0, 0], [255 / 255, 185 / 255, 165 / 255, Alpha], [0, 0, 0, 0])
MaterialFull([0, 0, 0, 0], [1, 1, 1, Alpha], [0, 0, 0, 0])
' Cycle through each atmosphere layer.
For ScalePosition = 1 - LayerCoverage To 1 Step LayerStep
' Calculate current atmosphere layer's scale radius.
ScaleRadius = Sqr((2 * ScalePosition * Radius) - (ScalePosition ^ 2))
' Check if scale is positive.
If ScaleRadius > 0 Then
' Reset Angle.
Angle = 0
' Draw center vertex.
Gl.Begin(Gl.TRIANGLE_FAN)
Gl.Normal3fv(NormalLineSegment([0, Radius, 0], [0, ScalePosition - Radius, 0]))
Gl.Vertex3f(0, ScalePosition - Radius, 0)
' Draw outer vertices.
For Counter = 0 To Triangles
' Calculate vertex position (X, Z).
Outer = TranslateInDirection(0, 0, ScaleRadius, Angle)
' Draw vertex.
Gl.Normal3fv(NormalLineSegment([0, Radius, 0], [Outer[0], ScalePosition - Radius, - Outer[1]]))
Gl.Vertex3f(Outer[0], ScalePosition - Radius, Outer[1])
' Increment angle.
Angle -= AngleStep
Next
Gl.End()
Endif
Next
' Restore previous settings.
DepthReadWrite
Gl.BlendFunc(Gl.SRC_ALPHA, Gl.ONE_MINUS_SRC_ALPHA)
' Finish display list.
Gl.EndList()
End
```

Here’s the code for the procedure calculating the vertex normal from two points:

```
Public Function NormalLineSegment(p1 As Single[], p2 As Single[]) As Single[]
' Calculate and return normal of specified points.
' General declarations.
Dim N As New Single[3]
Dim Magnitude As Single
' Calculate normal.
N[0] = p2[0] - p1[0]
N[1] = p2[1] - p1[1]
N[2] = - (p2[2] - p1[2])
' Normalize normal.
Magnitude = Sqr(N[0] ^ 2 + N[1] ^ 2 + N[2] ^ 2)
If Magnitude <> 0 Then
N[0] /= Magnitude
N[1] /= Magnitude
N[2] /= Magnitude
Endif
Return N
End
```

And lastly here’s the code for calculating the X/Y coordinates of a point around an origin at an arbitrary angle (plainly working but I like contributing when I can, so enjoy if you need it):

```
Public Function TranslateInDirection(StartX As Single, StartY As Single, Distance As Single, Degrees As Single) As Single[]
' Translate specified coordinates by specified distance at specified angle in degrees.
' General declarations.
Dim NewXY As New Single[2]
NewXY[0] = StartX + Distance * Cos(Rad(Degrees))
NewXY[1] = StartY + Distance * Sin(Rad(Degrees))
' Return new coordinates.
Return NewXY
End
```