Reputation: 860
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:
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 <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 :
Maybe this clue will help solve this!
** 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
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