Michael Wilson
Michael Wilson

Reputation: 860

Why is an OpenGL positional (spot) light interfering with non-positional light on MacOS

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.

Here is the "normal" case: the spotlight is to the right:

Spotlight to right

Here, I have moved the spotlight down (Y = 1). Notice the black areas on the cube. That's incorrect.

Spotlight right, but low

Now, I turn off the spotlight by commenting out the "makeALight" call for the spotlight (light 6). Now, the cube is evenly lit.

Spotlight off!

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 <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLFW/glfw3.h>
#include <iostream>
#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, 5.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;
}

UPDATE

So I got a hold of an Intel Mac (failing case was on a Apple Silicon Mac) and the problem does not occur. These images show a different scene, but they demonstrate the problem. The red arrows in the X86 Mac show the positional lights being on, and the textures outside of their illumination cone showing up just fine, whereas in the Apple Silicon case they're dark/black.

So this implies the problem is :

  1. A Math issue brought on by my Rosetta app running in an apple silicon mac.
  2. A difference in Open Gl drivers between Apple Silicon and Intel Macs.

Maybe this clue will help solve this!

Apple Silicon Mac Apple Silicon Case (fail)

Intel X86 Mac Intel X86 case (works!)

** UPDATE 2 **

I created a pure ARM64 build of my test program (the cube) and tested it. It also failed.

This eliminates option #1 above - the problem can not be cause by Rosetta translating X86-64 to ARM64.

So it's either an Apple Silicon OpenGL driver behaviour my code is triggering, or something with the on-chip Apple Silicon graphics.

Upvotes: 4

Views: 181

Answers (1)

Molanda
Molanda

Reputation: 671

I tried your test code and was able to reproduce the same results.

Looking at the drivers, the Intel Mac uses AppleIntelKBLGraphicsGLDriver which is the legacy OGL driver.

However, the M1 Mac uses AppleMetalOpenGLRenderer, which I assume based on the name is a wrapper around the Metal API.

Since these are different driver implementation, I can only guess that you found a bug. I recommend reporting it to Apple if you haven't already.

https://developer.apple.com/bug-reporting/

Perhaps add a comment in the test code so they know how to reproduce the issue.

    makeALight(6, true,
#if 0
               7.0f, 5.0f, 0.0f,            // Position (Normal light)
#else
               7.0f, 1.0f, 0.0f,            // Position (BUG: Incorrect shading with AppleMetalOpenGLRenderer)
#endif
               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

Upvotes: 1

Related Questions