I have a legacy OpenGL fixed-pipeline app which has been ported from Windows (32-bit) to MacOS 64-bit.
The problem is that if I have a scene with a non-positional light, everything works great. If I add a positional spotlight the two lights interact, and I get incorrect results.
This problem does not occur on X86_64 Macs. It does occur when the app is X86_64 running under Rosetta or native ARM64.
So it's either an Apple Silicon OpenGL driver behaviour my code is triggering, or something with the on-chip Apple Silicon graphics.
Here is the "normal" case: the spotlight is to the right:
Here, I have moved the spotlight down (Y = 1). Notice the black areas on the cube. That's incorrect.
Now, I turn off the spotlight by commenting out the "makeALight" call for the spotlight (light 6). Now, the cube is evenly lit.
Here is the test code I use to generate the lights. You will need to install glfw with brew to build it.
#define GL_SILENCE_DEPRECATION 1 #include #include #include #include #define STB_IMAGE_IMPLEMENTATION // Function to generate a checkerboard texture GLuint generateCheckerboardTexture(int width, int height) { int checkerSize = 8; // Size of each square in the checkerboard GLubyte *data = new GLubyte[width * height * 3]; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { int checkerX = x / checkerSize; int checkerY = y / checkerSize; bool isWhite = (checkerX + checkerY) % 2 == 0; GLubyte color = isWhite ? 255 : 0; data[(y * width + x) * 3 + 0] = color; // Red data[(y * width + x) * 3 + 1] = color; // Green data[(y * width + x) * 3 + 2] = color; // Blue } } GLuint textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); // Set texture parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Upload the texture data glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); // Clean up delete[] data; return textureID; } // Function to create and configure a light source void makeALight(int index, bool positional, GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat dirX, GLfloat dirY, GLfloat dirZ, GLfloat ambR, GLfloat ambG, GLfloat ambB, GLfloat ambA, GLfloat diffR, GLfloat diffG, GLfloat diffB, GLfloat diffA, GLfloat specR, GLfloat specG, GLfloat specB, GLfloat specA, float beamWidth, float constantA, float linearA, float quadA) { GLenum glLightIndex = GL_LIGHT0 + index; glEnable(glLightIndex); GLfloat ambColor[] = {ambR, ambG, ambB, ambA}; GLfloat diffColor[] = {diffR, diffG, diffB, diffA}; GLfloat specColor[] = {specR, specG, specB, specA}; glLightfv(glLightIndex, GL_AMBIENT, ambColor); glLightfv(glLightIndex, GL_DIFFUSE, diffColor); glLightfv(glLightIndex, GL_SPECULAR, specColor); glLightf(glLightIndex, GL_CONSTANT_ATTENUATION, constantA); glLightf(glLightIndex, GL_LINEAR_ATTENUATION, linearA); glLightf(glLightIndex, GL_QUADRATIC_ATTENUATION, quadA); if (positional) { GLfloat pos[] = {posX, posY, posZ, 1.0f}; glLightfv(glLightIndex, GL_POSITION, pos); if (beamWidth != 180.0f) { glLightf(glLightIndex, GL_SPOT_CUTOFF, beamWidth); GLfloat direction[] = {dirX, dirY, dirZ}; glLightfv(glLightIndex, GL_SPOT_DIRECTION, direction); } else { glLightf(glLightIndex, GL_SPOT_CUTOFF, 180.0f); } } else { GLfloat pos[] = {dirX, dirY, dirZ, 0.0f}; glLightfv(glLightIndex, GL_POSITION, pos); } } // Function to render a textured cube void renderTexturedCube(GLuint textureID) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, textureID); glBegin(GL_QUADS); // Front Face glNormal3f(0.0f, 0.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Back Face glNormal3f(0.0f, 0.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Top Face glNormal3f(0.0f, 1.0f, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Bottom Face glNormal3f(0.0f, -1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Right face glNormal3f(1.0f, 0.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Left Face glNormal3f(-1.0f, 0.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glEnd(); glDisable(GL_TEXTURE_2D); } // Function to render a cube with explicit material properties // Function to render a cube with explicit metallic material properties void renderCube() { // Define metallic material properties GLfloat materialAmbient[] = {0.25f, 0.25f, 0.25f, 1.0f}; // Low ambient reflection GLfloat materialDiffuse[] = {0.4f, 0.4f, 0.4f, 1.0f}; // Slightly higher diffuse reflection GLfloat materialSpecular[] = {0.77f, 0.77f, 0.77f, 1.0f}; // High specular reflection GLfloat materialShininess = 76.8f; // High shininess for a metallic look // Apply the material properties to the cube glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient); glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular); glMaterialf(GL_FRONT, GL_SHININESS, materialShininess); glBegin(GL_QUADS); // Front Face glNormal3f(0.0f, 0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Back Face glNormal3f(0.0f, 0.0f, -1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glVertex3f(1.0f, -1.0f, -1.0f); // Top Face glNormal3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); // Bottom Face glNormal3f(0.0f, -1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Right Face glNormal3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, -1.0f, 1.0f); // Left Face glNormal3f(-1.0f, 0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glEnd(); } int main() { // Initialize GLFW if (!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; return -1; } // Create a windowed mode window and its OpenGL context // Set the OpenGL version to 2.1 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Lighting Test", nullptr, nullptr); if (!window) { std::cerr << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } // Make the window's context current glfwMakeContextCurrent(window); // Set viewport glViewport(0, 0, 800, 600); // Set up projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f, 800.0f / 600.0f, 0.1f, 100.0f); // Set up modelview matrix glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0.0f, 0.0f, 5.0f, // Eye position 0.0f, 0.0f, 0.0f, // Look-at point 0.0f, 1.0f, 0.0f); // Up vector // Enable depth testing glEnable(GL_DEPTH_TEST); GLuint textureID = generateCheckerboardTexture(256, 256); // Enable lighting glEnable(GL_LIGHTING); // Set shading model to smooth glShadeModel(GL_SMOOTH); // float amb[] = { 0.70, 0.70, 0.70, 1 }; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb ); // Set up the lights using the provided makeALight calls makeALight(6, true, 7.0f, 1.0f, 0.0f, // Position 0.0f, -1.0f, 0.0f, // Direction 1.0f, 0.00f, 0.00f, 1.0f, // Ambient 1.0f, 0.0f, 0.0f, 1.00f, // Diffuse 1.0f, 0.00f, 0.00f, 1.00f, // Specular 55.0f, // Angle 0.0f, 0.09f, 0.0f); // Constant, Linear, Quad makeALight(0, false, 0.0f, 0.0f, 0.0f, // Position 0.00f, 0.00f, -1.00f, // Direction 0.0f, 1.0f, 0.0f, 1.00f, // Ambient 0.0f, 1.0f, 0.0f, 1.00f, // Diffuse 0.0f, 1.0f, 0.0f, 1.00f, // Specular 180.0f, // Angle 1.0f, 0.0f, 0.0f); // Constant, Linear, Quad // Main loop while (!glfwWindowShouldClose(window)) { // Render here glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Rotate the cube over time glPushMatrix(); glRotatef((float)glfwGetTime() * 50.0f, 1.0f, 1.0f, 0.0f); // renderCube(); renderTexturedCube(textureID); glPopMatrix(); // Swap front and back buffers glfwSwapBuffers(window); // Poll for and process events glfwPollEvents(); } // Clean up and exit glDeleteTextures(1, &textureID); glfwDestroyWindow(window); glfwTerminate(); return 0; }