LWJGL Lighting problem: All faces have same color

So I’ve been trying to get the lighting code from penguin’s post at http://www.java-gaming.org/index.php?topic=23951.0 to work. (Including the missing GL11.glEnable(GL11.GL_LIGHTING);.

However, what I’m actually seeing is that instead of each face of the pyramid being lit up by an amount depending on its orientation, all faces are lit up by the same amount, which changes as the pyramid rotates. Does anyone have any insight? This is using lwjgl 2.8.3 on Mac OS X.

Show us your code.

Did you add normals to each vertex?

There you go:

And no - I didn’t know vertex normals were a thing! That might explain a lot! (Can you tell I’m new at this OpenGL thing?)

Anyway, here’s the code:

package com;

import com.zarkonnen.lwjgltest.Main;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;
import org.lwjgl.util.glu.*;
import org.lwjgl.input.Keyboard;

import java.nio.FloatBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 *
 * @author penguin
 */
public class main {

	public static void main(String[] args) {
		try {
			Display.setDisplayMode(new DisplayMode(800, 600));
			Display.setTitle("3D Pyramid");
			Display.create();
		} catch (Exception e) {
		}
		initGL();

		float rtri = 0.0f;
		Texture texture = null;
		try {
			texture = TextureLoader.getTexture("png", Main.class.getResourceAsStream("tex.png"));
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		while (!Display.isCloseRequested()) {
			// Draw a Triangle :D

			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

			GL11.glLoadIdentity();

			GL11.glTranslatef(0.0f, 0.0f, -10.0f);

			GL11.glRotatef(rtri, 0.0f, 1.0f, 0.0f);

			texture.bind();

			GL11.glBegin(GL11.GL_TRIANGLES);

			GL11.glTexCoord2f(0.0f, 1.0f);
			GL11.glVertex3f(0.0f, 1.0f, 0.0f);
			GL11.glTexCoord2f(-1.0f, -1.0f);
			GL11.glVertex3f(-1.0f, -1.0f, 1.0f);
			GL11.glTexCoord2f(1.0f, -1.0f);
			GL11.glVertex3f(1.0f, -1.0f, 1.0f);

			GL11.glTexCoord2f(0.0f, 1.0f);
			GL11.glVertex3f(0.0f, 1.0f, 0.0f);
			GL11.glTexCoord2f(-1.0f, -1.0f);
			GL11.glVertex3f(1.0f, -1.0f, 1.0f);
			GL11.glTexCoord2f(1.0f, -1.0f);
			GL11.glVertex3f(1.0f, -1.0f, -1.0f);

			GL11.glTexCoord2f(0.0f, 1.0f);
			GL11.glVertex3f(0.0f, 1.0f, 0.0f);
			GL11.glTexCoord2f(-1.0f, -1.0f);
			GL11.glVertex3f(-1.0f, -1.0f, -1.0f);
			GL11.glTexCoord2f(1.0f, -1.0f);
			GL11.glVertex3f(1.0f, -1.0f, -1.0f);

			GL11.glTexCoord2f(0.0f, 1.0f);
			GL11.glVertex3f(0.0f, 1.0f, 0.0f);
			GL11.glTexCoord2f(-1.0f, -1.0f);
			GL11.glVertex3f(-1.0f, -1.0f, -1.0f);
			GL11.glTexCoord2f(1.0f, -1.0f);
			GL11.glVertex3f(-1.0f, -1.0f, 1.0f);


			GL11.glEnd();

			GL11.glBegin(GL11.GL_QUADS);
			GL11.glVertex3f(1.0f, -1.0f, 1.0f);
			GL11.glVertex3f(1.0f, -1.0f, -1.0f);
			GL11.glVertex3f(-1.0f, -1.0f, -1.0f);
			GL11.glVertex3f(-1.0f, -1.0f, 1.0f);
			GL11.glEnd();

			Display.update();
			rtri += 0.05f;
			// Exit-Key = ESC
			boolean exitPressed = Keyboard.isKeyDown(Keyboard.KEY_ESCAPE);
			if (exitPressed) {
				System.out.println("Escape was pressed!");
				Display.destroy();

			}
		}

		Display.destroy();
	}

	private static void initGL() {
		GL11.glEnable(GL11.GL_LIGHTING);
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GLU.gluPerspective(45.0f, ((float) 800) / ((float) 600), 0.1f, 100.0f);
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		GL11.glLoadIdentity();

		GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glShadeModel(GL11.GL_SMOOTH);
		GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		GL11.glClearDepth(1.0f);
		GL11.glEnable(GL11.GL_DEPTH_TEST);
		GL11.glDepthFunc(GL11.GL_LEQUAL);
		GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
		float lightAmbient[] = {0.5f, 0.5f, 0.5f, 1.0f};  // Ambient Light Values
		float lightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};      // Diffuse Light Values
		float lightPosition[] = {0.0f, 0.0f, 2.0f, 1.0f}; // Light Position

		ByteBuffer temp = ByteBuffer.allocateDirect(16);
		temp.order(ByteOrder.nativeOrder());
		GL11.glLight(GL11.GL_LIGHT1, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());              // Setup The Ambient Light
		GL11.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, (FloatBuffer) temp.asFloatBuffer().put(lightDiffuse).flip());              // Setup The Diffuse Light
		GL11.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, (FloatBuffer) temp.asFloatBuffer().put(lightPosition).flip());         // Position The Light
		GL11.glEnable(GL11.GL_LIGHT1);                          // Enable Light One
	}
}

[quote]All faces have the same color
[/quote]
… that could be the title of a John Lennon song…

Lighting depends on normals. Google will answer most of your questions. :point:

Well, I assumed they’d be calculated from the vertices using the right-hand or left-hand rule. Learning! Trying it out now…

You often don’t want each vertex of a triangle to have to same normal.

Imagine rendering a sphere, or other curved shapes, it would look rather bad as the shade wouldn’t be smooth across the surface.

Aaah, of course. OK, I’m afraid I have another question. (And BTW, thank you so much for your help so far.) So I’m now trying to render a cube, and I’m setting the normals to point straight out of the faces. However, I’m now seeing that the top face of the cube, with a normal call of

glNormal3f(0.0f, 0.0f, 1.0f);

gets the most illumination, independent of what light position I supply using

glLight(GL_LIGHT1, GL_POSITION,(FloatBuffer)temp.asFloatBuffer().put(lightPosition).flip());

Complete code:

package com.zarkonnen.lwjgltest;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.*;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;

public class Main {
	public static final int DISPLAY_HEIGHT = 480;
	public static final int DISPLAY_WIDTH = 640;
	public static final Logger LOGGER = Logger.getLogger(Main.class.getName());

	private float lightAmbient[] = { 0.5f, 0.5f, 0.5f, 1.0f };  // Ambient Light Values ( NEW )
	private float lightDiffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };      // Diffuse Light Values ( NEW )
	private float lightPosition[] = { 0.0f, 1.0f, 0.0f, 1.0f }; // Light Position ( NEW )
	
	static {
		try {
			LOGGER.addHandler(new FileHandler("errors.log", true));
		} catch (IOException ex) {
			LOGGER.log(Level.WARNING, ex.toString(), ex);
		}
	}

	public static void main(String[] args) {
		Main main = null;
		try {
			main = new Main();
			main.create();
			main.run();
		} catch (Exception ex) {
			LOGGER.log(Level.SEVERE, ex.toString(), ex);
		} finally {
			if (main != null) {
				main.destroy();
			}
		}
	}
	
	ArrayList<Box> boxes = new ArrayList<Box>();

	public void create() throws LWJGLException, IOException {
		//Display
		Display.setDisplayMode(new DisplayMode(DISPLAY_WIDTH, DISPLAY_HEIGHT));
		Display.setFullscreen(false);
		Display.setTitle("Hello LWJGL World!");
		Display.create();

		//Keyboard
		Keyboard.create();

		//Mouse
		Mouse.setGrabbed(false);
		Mouse.create();

		//OpenGL
		initGL();
		
		Texture tex = TextureLoader.getTexture("png", Main.class.getResourceAsStream("tex.png"));
		boxes.add(new Box(0, 0, 0, 1, 1, 1, tex));
	}

	public void destroy() {
		//Methods already check if created before destroying.
		Mouse.destroy();
		Keyboard.destroy();
		Display.destroy();
	}
	
	public void initGL() {
		glEnable(GL_LIGHTING);
		glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
        glShadeModel(GL_SMOOTH); // Enable Smooth Shading
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Black Background
        glClearDepth(1.0f); // Depth Buffer Setup
        glEnable(GL_DEPTH_TEST); // Enables Depth Testing
        glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do

        glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
        glLoadIdentity(); // Reset The Projection Matrix

        // Calculate The Aspect Ratio Of The Window
       gluPerspective(
				45.0f,
				DISPLAY_WIDTH * 1.0f / DISPLAY_HEIGHT,
				0.1f,
				100.0f);
        glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix

        // Really Nice Perspective Calculations
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
        ByteBuffer temp = ByteBuffer.allocateDirect(16);
        temp.order(ByteOrder.nativeOrder());
        glLight(GL_LIGHT1, GL_AMBIENT, (FloatBuffer)temp.asFloatBuffer().put(lightAmbient).flip());              // Setup The Ambient Light
        glLight(GL_LIGHT1, GL_DIFFUSE, (FloatBuffer)temp.asFloatBuffer().put(lightDiffuse).flip());              // Setup The Diffuse Light
        glLight(GL_LIGHT1, GL_POSITION,(FloatBuffer)temp.asFloatBuffer().put(lightPosition).flip());         // Position The Light
		glEnable(GL_LIGHT1);                          // Enable Light One
	}

	public void processKeyboard() {
		if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) { cx += 0.03f; }
		if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) { cx -= 0.03f; }
		if (Keyboard.isKeyDown(Keyboard.KEY_UP)) { cy -= 0.03f; }
		if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) { cy += 0.03f; }
	}

	public void processMouse() {
	}
	float cx, cy;
	
	public Main() throws IOException {
		
	}
	
	static class Box {
		float x, y, z, w, d, h;
		Texture tex;

		public Box(float x, float y, float z, float w, float d, float h, Texture tex) {
			this.x = x;
			this.y = y;
			this.z = z;
			this.w = w;
			this.d = d;
			this.h = h;
			this.tex = tex;
		}
	}
	
	public void render() {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);          // Clear The Screen And The Depth Buffer
		for (Box b : boxes) {
			glLoadIdentity();
			// Camera
			glTranslatef(cx, cy, -8);
			glTranslatef(b.x, b.y, b.z);
			b.tex.bind();
			glBegin(GL_QUADS);
			// North face
			glNormal3f(0.0f, -1.0f, 0.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(b.x, b.y + b.h, b.z);			glTexCoord2f(b.tex.getWidth(), 0.0f); glVertex3f(b.x + b.w, b.y + b.h, b.z);
																				glTexCoord2f(b.tex.getWidth(), b.tex.getHeight()); glVertex3f(b.x + b.w, b.y + b.h, b.z + b.d);
			glTexCoord2f(0.0f, b.tex.getHeight()); glVertex3f(b.x, b.y + b.h, b.z + b.d);
			// East face
			glNormal3f(1.0f, 0.0f, 0.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(b.x + b.w, b.y, b.z);			glTexCoord2f(b.tex.getWidth(), 0.0f); glVertex3f(b.x + b.w, b.y, b.z + b.d);
																				glTexCoord2f(b.tex.getWidth(), b.tex.getHeight()); glVertex3f(b.x + b.w, b.y + b.h, b.z + b.d);
			glTexCoord2f(0.0f, b.tex.getHeight()); glVertex3f(b.x + b.w, b.y + b.h, b.z);
			// West face
			glNormal3f(-1.0f, 0.0f, 0.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(b.x, b.y, b.z);				glTexCoord2f(b.tex.getWidth(), 0.0f); glVertex3f(b.x, b.y + b.h, b.z);
																				glTexCoord2f(b.tex.getWidth(), b.tex.getHeight()); glVertex3f(b.x, b.y + b.h, b.z + b.d);
			glTexCoord2f(0.0f, b.tex.getHeight()); glVertex3f(b.x, b.y, b.z + b.d);
			// South face
			glNormal3f(0.0f, 1.0f, 0.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(b.x, b.y, b.z);				glTexCoord2f(b.tex.getWidth(), 0.0f); glVertex3f(b.x, b.y, b.z + b.d);
																				glTexCoord2f(b.tex.getWidth(), b.tex.getHeight()); glVertex3f(b.x + b.w, b.y, b.z + b.d);
			glTexCoord2f(0.0f, b.tex.getHeight()); glVertex3f(b.x + b.w, b.y, b.z);
			// Top face
			glNormal3f(0.0f, 0.0f, 1.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(b.x, b.y, b.z + b.d);			glTexCoord2f(b.tex.getWidth(), 0.0f); glVertex3f(b.x, b.y + b.h, b.z + b.d);
																				glTexCoord2f(b.tex.getWidth(), b.tex.getHeight()); glVertex3f(b.x + b.w, b.y + b.h, b.z + b.d);
			glTexCoord2f(0.0f, b.tex.getHeight()); glVertex3f(b.x + b.w, b.y, b.z + b.d);
			glEnd();
		}
	}

	public void run() {
		while (!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
			if (Display.isVisible()) {
				processKeyboard();
				processMouse();
				update();
				render();
			} else {
				if (Display.isDirty()) {
					render();
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException ex) {
				}
			}
			Display.update();
			Display.sync(60);
		}
	}

	public void update() {
	}
}

Never mind, I finally figured it out: it was just that I was moving the light so little that for all intents and purposes it was always at the top. It works now!

Thank you so much for your help!