OpenGL Fixed Function mit Shadern emulieren

Vor Jahren hatte ich da mal für Swogl was angefangen, und für https://github.com/javagl/Rendering weitergemacht, und weil ich in https://github.com/javagl/JglTF gerne auch die https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common unerstützen würde, habe ich es jetzt nochmal ausgegraben:

Der Versuch, die OpenGL fixed function pipeline mit Shadern zu emulieren.

Viel von dem Shadercode hatte ich mühsam zusammengefrickelt, ausgehend von dem, was mit einem Tool generiert war, das “ShaderGen” hieß (sehr ähnlich zu aber wohl nicht genau das gleiche wie https://github.com/mojocorp/ShaderGen ).

Jetzt dachte ich, mal ein “KSKB” zu basteln, wo die beiden Methoden verglichen werden. Das ist noch nicht fertig. Fixed-Function-Texturen fehlen noch. Aber vielleicht interessiert’s ja schon jemanden.

Erstmal ein Screenshot: Links Fixed Function, rechts shader (natürlich mit Per-Fragment lighting).

[ATTACH=CONFIG]2766[/ATTACH]

import static org.lwjgl.opengl.GL11.GL_AMBIENT;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_CONSTANT_ATTENUATION;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_DIFFUSE;
import static org.lwjgl.opengl.GL11.GL_EMISSION;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_FRONT_AND_BACK;
import static org.lwjgl.opengl.GL11.GL_LIGHT0;
import static org.lwjgl.opengl.GL11.GL_LIGHTING;
import static org.lwjgl.opengl.GL11.GL_LINEAR_ATTENUATION;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_POSITION;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
import static org.lwjgl.opengl.GL11.GL_QUADRATIC_ATTENUATION;
import static org.lwjgl.opengl.GL11.GL_SHININESS;
import static org.lwjgl.opengl.GL11.GL_SPECULAR;
import static org.lwjgl.opengl.GL11.GL_SPOT_CUTOFF;
import static org.lwjgl.opengl.GL11.GL_SPOT_DIRECTION;
import static org.lwjgl.opengl.GL11.GL_SPOT_EXPONENT;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL11.glBegin;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glDrawElements;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glLight;
import static org.lwjgl.opengl.GL11.glLightf;
import static org.lwjgl.opengl.GL11.glLoadMatrix;
import static org.lwjgl.opengl.GL11.glMaterial;
import static org.lwjgl.opengl.GL11.glMaterialf;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.glNormal3f;
import static org.lwjgl.opengl.GL11.glTexCoord2f;
import static org.lwjgl.opengl.GL11.glVertex3f;
import static org.lwjgl.opengl.GL11.glViewport;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_DYNAMIC_DRAW;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER;
import static org.lwjgl.opengl.GL20.GL_INFO_LOG_LENGTH;
import static org.lwjgl.opengl.GL20.GL_VERTEX_SHADER;
import static org.lwjgl.opengl.GL20.glAttachShader;
import static org.lwjgl.opengl.GL20.glCompileShader;
import static org.lwjgl.opengl.GL20.glCreateProgram;
import static org.lwjgl.opengl.GL20.glCreateShader;
import static org.lwjgl.opengl.GL20.glDeleteShader;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glGetAttribLocation;
import static org.lwjgl.opengl.GL20.glGetProgram;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetShader;
import static org.lwjgl.opengl.GL20.glGetShaderInfoLog;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import static org.lwjgl.opengl.GL20.glShaderSource;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniform3;
import static org.lwjgl.opengl.GL20.glUniform4;
import static org.lwjgl.opengl.GL20.glUniformMatrix4;
import static org.lwjgl.opengl.GL20.glUseProgram;
import static org.lwjgl.opengl.GL20.glValidateProgram;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;

import java.awt.Canvas;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.AWTGLCanvas;

public class SimpleRenderer
{
    
    public static void main(String[] args)
    {
        System.setProperty("sun.awt.noerasebackground", "true");
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        SimpleRenderer simpleRenderer = new SimpleRenderer();
        f.getContentPane().add(simpleRenderer.getCanvas());
                
        f.setSize(800,800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static final int DIRECTIONAL_LIGHT_TYPE_ID = 0;
    private static final int POINT_LIGHT_TYPE_ID = 1;
    private static final int SPOT_LIGHT_TYPE_ID = 2;
    
    private Canvas canvas;
    private boolean initialized = false;
    
    // The geometry data
    private int indices[];
    private float positions[];
    private float normals[];
    private float texCoords0[];
    
    // The rendered data as a VAO and its VBOs
    private int vao;
    private int numIndices;
    private int indicesVbo;

    // The program and its attribute locations
    private int program;
    private int positionsAttributeLocation;
    private int normalsAttributeLocation;
    private int texCoords0AttributeLocation;
    
    private int numTextures;
    private int hasVertexColors;

    // The matrices
    private float modelMatrix[] = identity4x4();
    private float normalMatrix[] = identity4x4();
    private float viewMatrix[] = identity4x4();
    private float projectionMatrix[] = identity4x4();
    
    // The light data
    private static class Light
    {
        int type;
        float ambient4D[] = { 0.0f, 0.0f, 0.0f, 1.0f };
        float diffuse4D[] = { 1.0f, 1.0f, 1.0f, 1.0f };
        float specular4D[] = { 1.0f, 1.0f, 1.0f, 1.0f };
        float position4D[] = { 0.0f, 0.0f, 1.0f, 0.0f };
        
        float spotDirection3D[] = { 0.0f, 0.0f, -1.0f };
        float spotExponent = 0.0f;
        float spotCutoff = 90.0f;
       
        float constantAttenuation = 1.0f;
        float linearAttenuation = 0.0f;
        float quadraticAttenuation = 0.0f;
    }
    private float globalAmbient4D[] = new float[4];
    private List<Light> lights = new ArrayList<Light>();
    
    //The material data
    private static class Material 
    {
        float ambient4D[] = { 0.2f, 0.2f, 0.2f, 1.0f };
        float diffuse4D[] = { 0.8f, 0.8f, 0.8f, 1.0f };
        float specular4D[] = { 0.0f, 0.0f, 0.0f, 1.0f };
        float emission4D[] = { 0.0f, 0.0f, 0.0f, 1.0f };
        float shininess = 0.0f;
    }
    private Material material;
    
    
    // The matrices that are modified via mouse interaction, and
    // from which the view matrix will be computed.
    private float currentRotationMatrix[] = identity4x4();
    private float currentTranslationMatrix[] = translation4x4(0,0,-5);
    

    @SuppressWarnings("serial")
    SimpleRenderer()
    {
        try
        {
            canvas = new AWTGLCanvas()
            {
                @Override
                public void paintGL()
                {
                    render();
                    try
                    {
                        swapBuffers();
                    }
                    catch (LWJGLException e)
                    {
                        throw new RuntimeException(
                            "Could not swap buffers", e);
                    }
                }
            };
            
            MouseControl mouseControl = new MouseControl();
            canvas.addMouseMotionListener(mouseControl);
            canvas.addMouseWheelListener(mouseControl);
        }
        catch (LWJGLException e)
        {
            throw new RuntimeException(
                "Could not create canvas", e);
        }
    }
    
    private Component getCanvas()
    {
        return canvas;
    }
    
    
    private void render()
    {
        int width = canvas.getWidth();
        int height = canvas.getHeight();
        glViewport(0, 0, width, height);
        
        glEnable(GL_DEPTH_TEST);
        glClearColor(0,0,0,0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        if (!initialized)
        {
            initialize();
        }
        renderFixed();
        renderProgrammable();
    }
    
    private void initialize()
    {
        initProgram();
        initObject();
        initLights();
        initMaterial();
        
        initialized = true;
    }
    
    private void initProgram()
    {
        String vertexShaderSource = 
            readFileAsStringUnchecked("shaders/ffShader.vs");
        String fragmentShaderSource = 
            readFileAsStringUnchecked("shaders/ffShader.fs");
        
        program = glCreateProgram();

        int vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, vertexShaderSource);
        glCompileShader(vertexShader);     
        printShaderLogInfo(vertexShader);
        glAttachShader(program, vertexShader);
        glDeleteShader(vertexShader);

        int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, fragmentShaderSource);
        glCompileShader(fragmentShader);     
        printShaderLogInfo(fragmentShader);
        glAttachShader(program, fragmentShader);
        glDeleteShader(fragmentShader);
        
        glLinkProgram(program);
        printProgramLogInfo(program);
        glValidateProgram(program);
        printProgramLogInfo(program);
        
        glUseProgram(program);
        
        positionsAttributeLocation = 
            glGetAttribLocation(program, "vertexPosition");
        normalsAttributeLocation = 
            glGetAttribLocation(program, "vertexNormal");
        texCoords0AttributeLocation = 
            glGetAttribLocation(program, "vertexTexCoord0");
    }

    private void initObject()
    {
        createData(2);
        
        numIndices = indices.length;
        
        indicesVbo = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVbo);
        IntBuffer indicesBuffer = createDirectBuffer(indices);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        
        int positionsVbo = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, positionsVbo);
        FloatBuffer positionsBuffer = createDirectBuffer(positions);
        glBufferData(GL_ARRAY_BUFFER, positionsBuffer, GL_DYNAMIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        
        int normalsVbo = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, normalsVbo);
        FloatBuffer normalsBuffer = createDirectBuffer(normals);
        glBufferData(GL_ARRAY_BUFFER, normalsBuffer, GL_DYNAMIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        int texCoords0Vbo = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, texCoords0Vbo);
        FloatBuffer texCoordsBuffer = createDirectBuffer(texCoords0);
        glBufferData(GL_ARRAY_BUFFER, texCoordsBuffer, GL_DYNAMIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        
        vao = glGenVertexArrays();
        glBindVertexArray(vao);

        glBindBuffer(GL_ARRAY_BUFFER, positionsVbo);
        glVertexAttribPointer(
            positionsAttributeLocation, 3, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(positionsAttributeLocation);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glBindBuffer(GL_ARRAY_BUFFER, normalsVbo);
        glVertexAttribPointer(
            normalsAttributeLocation, 3, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(normalsAttributeLocation);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glBindBuffer(GL_ARRAY_BUFFER, texCoords0Vbo);
        glVertexAttribPointer(
            texCoords0AttributeLocation, 2, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(texCoords0AttributeLocation);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glBindVertexArray(0);
        
    }
    
    private void initLights()
    {
        Light light = new Light();
        light.type = POINT_LIGHT_TYPE_ID;
        light.ambient4D = new float[] { 0.1f, 0.1f, 0.1f, 1.0f };
        light.diffuse4D = new float[] { 0.8f, 0.8f, 0.8f, 1.0f };
        light.specular4D = new float[] { 0.9f, 0.9f, 0.9f, 1.0f };
        light.position4D = new float[] { 3f, 3f, 3f, 1.0f };
        light.constantAttenuation = 1.0f;
        light.linearAttenuation = 0.0f;
        light.quadraticAttenuation = 0.0f;
        
        lights.add(light);
    }

    private void initMaterial()
    {
        material = new Material();
        
        material.ambient4D = new float[] { 0.2f, 0.2f, 0.2f, 1.0f };
        material.diffuse4D = new float[] { 1.0f, 0.0f, 0.0f, 1.0f };
        material.specular4D = new float[] { 0.9f, 0.9f, 0.9f, 1.0f };
        material.emission4D = new float[] { 0.1f, 0.1f, 0.1f, 1.0f };
        material.shininess = 10.0f;
    }

    
    private void renderFixed()
    {
        int width = canvas.getWidth();
        int height = canvas.getHeight();
        float aspect = (float)width / height;
        projectionMatrix = perspective4x4(50, aspect, 0.1f, 10000.0f);

        viewMatrix = multiply4x4(
            currentRotationMatrix, currentTranslationMatrix);
        
        modelMatrix = translation4x4(-1.0f, 0.0f, 0.0f);

        float[] modelViewMatrix = multiply4x4(viewMatrix, modelMatrix);
        float[] invModelViewMatrix = invert4x4(modelViewMatrix);
        normalMatrix = transpose4x4(invModelViewMatrix);
        
        setMatrices();
        setLights();
        setMaterial();
        
        glBegin(GL_TRIANGLES);
        int n = indices.length / 3;
        for (int i = 0; i < n; i++)
        {
            for (int j=0; j<3; j++)
            {
                int index = indices[i * 3 + j];
                
                float x = positions[index * 3 + 0];
                float y = positions[index * 3 + 1];
                float z = positions[index * 3 + 2];
                
                float nx = normals[index * 3 + 0];
                float ny = normals[index * 3 + 1];
                float nz = normals[index * 3 + 2];
                
                float s = texCoords0[index * 2 + 0];
                float t = texCoords0[index * 2 + 1];
                
                glNormal3f(nx, ny, nz);
                glTexCoord2f(s, t);
                glVertex3f(x, y, z);
            }
        }
        glEnd();
        
    }
    
    private void setMatrices()
    {
        glMatrixMode(GL_PROJECTION);
        glLoadMatrix(wrapTemp(projectionMatrix));
        
        float[] modelViewMatrix = multiply4x4(viewMatrix, modelMatrix);
        glMatrixMode(GL_MODELVIEW);
        glLoadMatrix(wrapTemp(modelViewMatrix));
    }
    
    private void setLights()
    {
        glEnable(GL_LIGHTING);
        int numLights = lights.size();
        
        for (int i=0; i<numLights; i++)
        {
            Light light = lights.get(i);
            setLight(i, light);
        }
    }
    
    private void setLight(int index, Light light)
    {
        int id = GL_LIGHT0 + index;
        
        glEnable(id);
        
        glLight(id, GL_AMBIENT, wrapTemp(light.ambient4D));
        glLight(id, GL_DIFFUSE, wrapTemp(light.diffuse4D));
        glLight(id, GL_SPECULAR, wrapTemp(light.specular4D));
        glLight(id, GL_POSITION, wrapTemp(light.position4D));

        glLight(id, GL_SPOT_DIRECTION, wrapTemp(
            Arrays.copyOfRange(light.spotDirection3D, 0, 4)));
        glLightf(id, GL_SPOT_EXPONENT, light.spotExponent);
        glLightf(id, GL_SPOT_CUTOFF, light.spotCutoff);

        glLightf(id, GL_CONSTANT_ATTENUATION, light.constantAttenuation);
        glLightf(id, GL_LINEAR_ATTENUATION, light.linearAttenuation);
        glLightf(id, GL_QUADRATIC_ATTENUATION, light.quadraticAttenuation);
    }
    
    private void setMaterial()
    {
        int f = GL_FRONT_AND_BACK;
        
        glMaterial(f, GL_AMBIENT, wrapTemp(material.ambient4D));
        glMaterial(f, GL_DIFFUSE, wrapTemp(material.diffuse4D));
        glMaterial(f, GL_SPECULAR, wrapTemp(material.specular4D));
        glMaterial(f, GL_EMISSION, wrapTemp(material.emission4D));
        glMaterialf(f, GL_SHININESS, material.shininess);
    }
    
    

    private void renderProgrammable()
    {
        int width = canvas.getWidth();
        int height = canvas.getHeight();
        float aspect = (float)width / height;
        projectionMatrix = perspective4x4(50, aspect, 0.1f, 10000.0f);

        viewMatrix = multiply4x4(
            currentRotationMatrix, currentTranslationMatrix);
        
        modelMatrix = translation4x4(1.0f, 0.0f, 0.0f);

        float[] modelViewMatrix = multiply4x4(viewMatrix, modelMatrix);
        float[] invModelViewMatrix = invert4x4(modelViewMatrix);
        normalMatrix = transpose4x4(invModelViewMatrix);
        
        glUseProgram(program);

        setUniforms();
        
        glBindVertexArray(vao);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVbo);
        glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glUseProgram(0);
    }
    
    private void setUniforms()
    {
        setMatrixUniforms();
        setLightUniforms();
        setMaterialUniforms();
    }
    
    private void setMatrixUniforms()
    {
        int modelMatrixLocation = 
            glGetUniformLocation(program, "modelMatrix");
        glUniformMatrix4(modelMatrixLocation, false, 
            wrapTemp(modelMatrix));

        int normalMatrixLocation = 
            glGetUniformLocation(program, "normalMatrix");
        glUniformMatrix4(normalMatrixLocation, false, 
            wrapTemp(normalMatrix));

        int viewMatrixLocation = 
            glGetUniformLocation(program, "viewMatrix");
        glUniformMatrix4(viewMatrixLocation, false, 
            wrapTemp(viewMatrix));
        
        int projectionMatrixLocation = 
            glGetUniformLocation(program, "projectionMatrix");
        glUniformMatrix4(projectionMatrixLocation, false, 
            wrapTemp(projectionMatrix));
    }

    private void setLightUniforms()
    {
        int numLights = lights.size();
        
        int globalAmbientLocation = glGetUniformLocation(
            program, "globalAmbient");
        glUniform4(globalAmbientLocation, 
            wrapTemp(globalAmbient4D));
        
        int numLightsLocation = 
            glGetUniformLocation(program, "numLights");
        glUniform1i(numLightsLocation, numLights);
        
        for (int i=0; i<numLights; i++)
        {
            Light light = lights.get(i);
            setLightUniforms(i, light);
        }
    }

    private void setLightUniforms(int index, Light light)
    {
        int lightTypeLocation = glGetUniformLocation(
            program, "lights["+index+"].type");
        glUniform1i(lightTypeLocation, light.type);
        
        
        int lightAmbientLocation = glGetUniformLocation(
            program, "lights["+index+"].ambient");
        glUniform4(lightAmbientLocation, 
            wrapTemp(light.ambient4D));
        
        int lightDiffuseLocation = glGetUniformLocation(
            program, "lights["+index+"].diffuse");
        glUniform4(lightDiffuseLocation, 
            wrapTemp(light.diffuse4D));

        int lightSpecularLocation = glGetUniformLocation(
            program, "lights["+index+"].specular");
        glUniform4(lightSpecularLocation, 
            wrapTemp(light.specular4D));

        
        int lightPositionLocation = glGetUniformLocation(
            program, "lights["+index+"].position");
        float[] lightPosition4D = 
            transformPoint4D(viewMatrix, light.position4D);
        glUniform4(lightPositionLocation, 
            wrapTemp(lightPosition4D));
        
        
        int lightConstantAttenuationLocation = glGetUniformLocation(
            program, "lights["+index+"].constantAttenuation");
        glUniform1f(lightConstantAttenuationLocation, 
            light.constantAttenuation);

        int lightLinearAttenuationLocation = glGetUniformLocation(
            program, "lights["+index+"].linearAttenuation");
        glUniform1f(lightLinearAttenuationLocation, 
            light.linearAttenuation);

        int lightQuadraticAttenuationLocation = glGetUniformLocation(
            program, "lights["+index+"].quadraticAttenuation");
        glUniform1f(lightQuadraticAttenuationLocation, 
            light.quadraticAttenuation);

        
        int lightSpotDirectionLocation = glGetUniformLocation(
            program, "lights["+index+"].spotDirection");
        float[] lightSpotDirection3D = 
            transformPoint3D(viewMatrix, light.spotDirection3D);
        glUniform3(lightSpotDirectionLocation, 
            wrapTemp(lightSpotDirection3D));

        int lightSpotCutoffLocation = glGetUniformLocation(
            program, "lights["+index+"].spotCutoff");
        glUniform1f(lightSpotCutoffLocation, light.spotCutoff);

        int lightSpotExponentLocation = glGetUniformLocation(
            program, "lights["+index+"].spotExponent");
        glUniform1f(lightSpotExponentLocation, light.spotExponent);
    }
    
    private void setMaterialUniforms()
    {
        int materialAmbientLocation = glGetUniformLocation(
            program, "material.ambient");
        glUniform4(materialAmbientLocation, 
            wrapTemp(material.ambient4D));
        
        int materialDiffuseLocation = glGetUniformLocation(
            program, "material.diffuse");
        glUniform4(materialDiffuseLocation, 
            wrapTemp(material.diffuse4D));
        
        int materialSpecularLocation = glGetUniformLocation(
            program, "material.specular");
        glUniform4(materialSpecularLocation, 
            wrapTemp(material.specular4D));
        
        int materialEmissionLocation = glGetUniformLocation(
            program, "material.emission");
        glUniform4(materialEmissionLocation, 
            wrapTemp(material.emission4D));
        
        int materialShininessLocation = glGetUniformLocation(
            program, "material.shininess");
        glUniform1f(materialShininessLocation, material.shininess);
    }

    
    
    
    

    
    
    //==========================================================================
    // Utility class for the mouse interaction
    
    private class MouseControl 
        implements MouseMotionListener, MouseWheelListener
    {
        private Point previousMousePosition = new Point();

        @Override
        public void mouseDragged(MouseEvent e)
        {
            int dx = e.getX() - previousMousePosition.x;
            int dy = e.getY() - previousMousePosition.y;

            if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 
                MouseEvent.BUTTON3_DOWN_MASK)
            {
                currentTranslationMatrix = multiply4x4(
                    translation4x4(dx / 150.0f, -dy / 150.0f, 0), 
                    currentTranslationMatrix);
            }

            else if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 
                MouseEvent.BUTTON1_DOWN_MASK)
            {
                currentRotationMatrix = multiply4x4(
                    currentRotationMatrix, rotationX4x4(dy));
                currentRotationMatrix = multiply4x4(
                    currentRotationMatrix, rotationY4x4(dx));
            }
            previousMousePosition = e.getPoint();
            canvas.repaint();
        }

        @Override
        public void mouseMoved(MouseEvent e)
        {
            previousMousePosition = e.getPoint();
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e)
        {
            currentTranslationMatrix = multiply4x4(
                translation4x4(0, 0, e.getWheelRotation() * 0.25f),                
                currentTranslationMatrix);
            previousMousePosition = e.getPoint();
            canvas.repaint();
        }
    }
    
 
    //==========================================================================
    // IO utility methods
    
    private static String readFileAsStringUnchecked(String fileName)
    {
        try
        {
            return readFileAsString(fileName);
        } 
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }
    
    private static String readFileAsString(String fileName) throws IOException
    {
        try (FileInputStream inputStream = new FileInputStream(fileName))
        {
            return readStreamAsString(inputStream);
        }
    }
    
    private static String readStreamAsString(InputStream inputStream) 
        throws IOException
    {
        try (BufferedReader br = 
            new BufferedReader(new InputStreamReader(inputStream)))
        {
            return br.lines().collect(Collectors.joining("
"));
        } 
    }
    
    //==========================================================================
    // Buffer utility methods
    
    private static ByteBuffer createByteBuffer(int size)
    {
        return ByteBuffer.allocateDirect(size).
            order(ByteOrder.nativeOrder());
    }
    private static FloatBuffer createFloatBuffer(int size)
    {
        return createByteBuffer(size * 4).asFloatBuffer();
    }
    private static IntBuffer createIntBuffer(int size)
    {
        return createByteBuffer(size * 4).asIntBuffer();
    }
    private static IntBuffer createDirectBuffer(int a[])
    {
        IntBuffer b = createIntBuffer(a.length);
        b.put(a);
        b.rewind();
        return b;
    }
    private static FloatBuffer createDirectBuffer(float a[])
    {
        FloatBuffer b = createFloatBuffer(a.length);
        b.put(a);
        b.rewind();
        return b;
    }
    
    private static FloatBuffer tempFloatBuffer = null;
    
    private static FloatBuffer wrapTemp(float array[])
    {
        if (tempFloatBuffer == null || 
            tempFloatBuffer.capacity() < array.length)
        {
            tempFloatBuffer = ByteBuffer
                .allocateDirect(array.length * Float.BYTES)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        }
        tempFloatBuffer.position(0);
        tempFloatBuffer.limit(tempFloatBuffer.capacity());
        tempFloatBuffer.put(array);
        tempFloatBuffer.flip();
        return tempFloatBuffer;
    }
    
    //==========================================================================
    // Matrix utility methods
    
    private static float[] perspective4x4(
        float fovyDeg, float aspect, float zNear, float zFar)
    {
        // Adapted from The Mesa 3-D graphics library. 
        // Copyright (C) 1999-2007  Brian Paul   All Rights Reserved.
        // Published under the MIT license (see the header of this file)
        float radians = (float)Math.toRadians(fovyDeg / 2);
        float deltaZ = zFar - zNear;
        float sine = (float)Math.sin(radians);
        if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) 
        {
            return identity4x4();
        }
        float cotangent = (float)Math.cos(radians) / sine;

        float m[] = identity4x4();
        m[0] = cotangent / aspect;
        m[5] = cotangent;
        m[10] = -(zFar + zNear) / deltaZ;
        m[11] = -1;
        m[14] = -2 * zNear * zFar / deltaZ;
        m[15] = 0.0f;
        return m;
    }
    
    private static float[] identity4x4()
    {
        float m[] = new float[16];
        m[0] = 1.0f;
        m[5] = 1.0f;
        m[10] = 1.0f;
        m[15] = 1.0f;
        return m;
    }
    
    private static float[] multiply4x4(float m0[], float m1[])
    {
        float m[] = new float[16];
        for (int x = 0; x < 16; x += 4)
        {
            for (int y = 0; y < 4; y++)
            {
                m[x+y] = 
                    m0[x+0] * m1[y+ 0] +
                    m0[x+1] * m1[y+ 4] +
                    m0[x+2] * m1[y+ 8] +
                    m0[x+3] * m1[y+12];
            }
        }
        return m;
    }
    
    private static float[] translation4x4(float x, float y, float z)
    {
        float m[] = identity4x4();
        m[12] = x;
        m[13] = y;
        m[14] = z;
        return m;
    }

    private static float[] rotationX4x4(float angleDeg)
    {
        float m[] = identity4x4();
        float angleRad = (float)Math.toRadians(angleDeg);
        float ca = (float)Math.cos(angleRad);
        float sa = (float)Math.sin(angleRad);
        m[ 5] =  ca;
        m[ 6] =  sa;
        m[ 9] = -sa;
        m[10] =  ca;
        return m;
    }

    private static float[] rotationY4x4(float angleDeg)
    {
        float m[] = identity4x4();
        float angleRad = (float)Math.toRadians(angleDeg);
        float ca = (float)Math.cos(angleRad);
        float sa = (float)Math.sin(angleRad);
        m[ 0] =  ca;
        m[ 2] = -sa;
        m[ 8] =  sa;
        m[10] =  ca;
        return m;
    }
    
    static float[] invert4x4(float m[])
    {
        // Adapted from The Mesa 3-D graphics library. 
        // Copyright (C) 1999-2007  Brian Paul   All Rights Reserved.
        // Published under the MIT license (see the header of this file)

        float FLOAT_EPSILON = 1e-6f;
        
        float m0 = m[ 0];
        float m1 = m[ 1];
        float m2 = m[ 2];
        float m3 = m[ 3];
        float m4 = m[ 4];
        float m5 = m[ 5];
        float m6 = m[ 6];
        float m7 = m[ 7];
        float m8 = m[ 8];
        float m9 = m[ 9];
        float mA = m[10];
        float mB = m[11];
        float mC = m[12];
        float mD = m[13];
        float mE = m[14];
        float mF = m[15];

        float inv[] = new float[4*4];
        
        inv[ 0] =  m5 * mA * mF - m5 * mB * mE - m9 * m6 * mF + 
                   m9 * m7 * mE + mD * m6 * mB - mD * m7 * mA;
        inv[ 4] = -m4 * mA * mF + m4 * mB * mE + m8 * m6 * mF - 
                   m8 * m7 * mE - mC * m6 * mB + mC * m7 * mA;
        inv[ 8] =  m4 * m9 * mF - m4 * mB * mD - m8 * m5 * mF + 
                   m8 * m7 * mD + mC * m5 * mB - mC * m7 * m9;
        inv[12] = -m4 * m9 * mE + m4 * mA * mD + m8 * m5 * mE - 
                   m8 * m6 * mD - mC * m5 * mA + mC * m6 * m9;
        inv[ 1] = -m1 * mA * mF + m1 * mB * mE + m9 * m2 * mF - 
                   m9 * m3 * mE - mD * m2 * mB + mD * m3 * mA;
        inv[ 5] =  m0 * mA * mF - m0 * mB * mE - m8 * m2 * mF + 
                   m8 * m3 * mE + mC * m2 * mB - mC * m3 * mA;
        inv[ 9] = -m0 * m9 * mF + m0 * mB * mD + m8 * m1 * mF - 
                   m8 * m3 * mD - mC * m1 * mB + mC * m3 * m9;
        inv[13] =  m0 * m9 * mE - m0 * mA * mD - m8 * m1 * mE + 
                   m8 * m2 * mD + mC * m1 * mA - mC * m2 * m9;
        inv[ 2] =  m1 * m6 * mF - m1 * m7 * mE - m5 * m2 * mF + 
                   m5 * m3 * mE + mD * m2 * m7 - mD * m3 * m6;
        inv[ 6] = -m0 * m6 * mF + m0 * m7 * mE + m4 * m2 * mF - 
                   m4 * m3 * mE - mC * m2 * m7 + mC * m3 * m6;
        inv[10] =  m0 * m5 * mF - m0 * m7 * mD - m4 * m1 * mF + 
                   m4 * m3 * mD + mC * m1 * m7 - mC * m3 * m5;
        inv[14] = -m0 * m5 * mE + m0 * m6 * mD + m4 * m1 * mE - 
                   m4 * m2 * mD - mC * m1 * m6 + mC * m2 * m5;
        inv[ 3] = -m1 * m6 * mB + m1 * m7 * mA + m5 * m2 * mB - 
                   m5 * m3 * mA - m9 * m2 * m7 + m9 * m3 * m6;
        inv[ 7] =  m0 * m6 * mB - m0 * m7 * mA - m4 * m2 * mB + 
                   m4 * m3 * mA + m8 * m2 * m7 - m8 * m3 * m6;
        inv[11] = -m0 * m5 * mB + m0 * m7 * m9 + m4 * m1 * mB - 
                   m4 * m3 * m9 - m8 * m1 * m7 + m8 * m3 * m5;
        inv[15] =  m0 * m5 * mA - m0 * m6 * m9 - m4 * m1 * mA + 
                   m4 * m2 * m9 + m8 * m1 * m6 - m8 * m2 * m5;
        // (Ain't that pretty?)
        
        float det = m0 * inv[0] + m1 * inv[4] + m2 * inv[8] + m3 * inv[12];
        if (Math.abs(det) <= FLOAT_EPSILON)
        {
            return identity4x4();
        }
        float invDet = 1.0f / det;
        for (int i = 0; i < 16; i++)
        {
            inv** *= invDet;
        }
        return inv;
    }    
    
    static float[] transpose4x4(float m[])
    {
        float m0 = m[ 0];
        float m1 = m[ 1];
        float m2 = m[ 2];
        float m3 = m[ 3];
        float m4 = m[ 4];
        float m5 = m[ 5];
        float m6 = m[ 6];
        float m7 = m[ 7];
        float m8 = m[ 8];
        float m9 = m[ 9];
        float mA = m[10];
        float mB = m[11];
        float mC = m[12];
        float mD = m[13];
        float mE = m[14];
        float mF = m[15];
        
        float t[] = new float[4*4];
        t[ 0] = m0;
        t[ 1] = m4;
        t[ 2] = m8;
        t[ 3] = mC;
        t[ 4] = m1;
        t[ 5] = m5;
        t[ 6] = m9;
        t[ 7] = mD;
        t[ 8] = m2;
        t[ 9] = m6;
        t[10] = mA;
        t[11] = mE;
        t[12] = m3;
        t[13] = m7;
        t[14] = mB;
        t[15] = mF;
        return t;
    }
    
    private static float[] transformPoint3D(
        float matrix4x4[], float point3D[])
    {
        float result3D[] = new float[3];
        for (int r=0; r<3; r++)
        {
            for (int c=0; c<3; c++)
            {
                int index = c * 4 + r;
                float m = matrix4x4[index];
                result3D[r] += m * point3D```;
            }
            int index = 3 * 4 + r;
            float m = matrix4x4[index];
            result3D[r] += m;
        }
        return result3D;
    }

    private static float[] transformPoint4D(
        float matrix4x4[], float point4D[])
    {
        float result4D[] = new float[4];
        for (int r=0; r<3; r++)
        {
            for (int c=0; c<4; c++)
            {
                int index = c * 4 + r;
                float m = matrix4x4[index];
                result4D[r] += m * point4D```;
            }
        }
        return result4D;
    }
    
    
    
    
    //==========================================================================
    // Utility methods
    
    private void printShaderLogInfo(int id) 
    {
        IntBuffer infoLogLength = ByteBuffer.allocateDirect(4)
            .order(ByteOrder.nativeOrder()).asIntBuffer();
        glGetShader(id, GL_INFO_LOG_LENGTH, infoLogLength);
        if (infoLogLength.get(0) > 0) 
        {
            infoLogLength.put(0, infoLogLength.get(0)-1);
        }

        ByteBuffer infoLog = ByteBuffer.allocateDirect(infoLogLength.get(0))
            .order(ByteOrder.nativeOrder());
        glGetShaderInfoLog(id, infoLogLength, infoLog);

        String infoLogString =
            Charset.forName("US-ASCII").decode(infoLog).toString();
        if (infoLogString.trim().length() > 0)
        {
            System.out.println("shader log:
"+infoLogString);
        }
    }    

    private void printProgramLogInfo(int id) 
    {
        IntBuffer infoLogLength = ByteBuffer.allocateDirect(4)
            .order(ByteOrder.nativeOrder()).asIntBuffer();
        glGetProgram(id, GL_INFO_LOG_LENGTH, infoLogLength);
        if (infoLogLength.get(0) > 0) 
        {
            infoLogLength.put(0, infoLogLength.get(0)-1);
        }

        ByteBuffer infoLog = ByteBuffer.allocateDirect(infoLogLength.get(0))
            .order(ByteOrder.nativeOrder());
        glGetProgramInfoLog(id, infoLogLength, infoLog);

        String infoLogString = 
            Charset.forName("US-ASCII").decode(infoLog).toString();
        if (infoLogString.trim().length() > 0)
        {
            System.out.println("program log:
"+infoLogString);
        }
    }    
    
    
    //==========================================================================
    // Data
    
    private void createData(int depth)
    {
        // Coordinates and indices taken from the redbook
        final float X = 0.525731112119133606f; 
        final float Z = 0.850650808352039932f;
        float vdata[][] = 
        {    
            {  -X, 0.0f,    Z}, 
            {   X, 0.0f,    Z}, 
            {  -X, 0.0f,   -Z}, 
            {   X, 0.0f,   -Z},    
            {0.0f,    Z,    X}, 
            {0.0f,    Z,   -X}, 
            {0.0f,   -Z,    X}, 
            {0.0f,   -Z,   -X},    
            {   Z,    X, 0.0f}, 
            {  -Z,    X, 0.0f}, 
            {   Z,   -X, 0.0f}, 
            {  -Z,   -X, 0.0f} 
        };
        int tindices[][] = 
        { 
            { 0, 4, 1}, 
            { 0, 9, 4}, 
            { 9, 5, 4}, 
            { 4, 5, 8}, 
            { 4, 8, 1},    
            { 8,10, 1}, 
            { 8, 3,10}, 
            { 5, 3, 8}, 
            { 5, 2, 3}, 
            { 2, 7, 3},    
            { 7,10, 3}, 
            { 7, 6,10}, 
            { 7,11, 6}, 
            {11, 0, 6}, 
            { 0, 1, 6}, 
            { 6, 1,10}, 
            { 9, 0,11}, 
            { 9,11, 2}, 
            { 9, 2, 5}, 
            { 7, 2,11} 
        };
        
        int power = (int) Math.round(Math.pow(4, depth));

        int numTriangles = 20 * power;
        indices = new int[numTriangles * 3];
        IntBuffer indicesBuffer = IntBuffer.wrap(indices);
        
        int numVertices = 60 * power;
        positions = new float[numVertices * 3];
        FloatBuffer positionsBuffer = FloatBuffer.wrap(positions);
        
        for (int i = 0; i < 20; i++)
        {
            FloatBuffer v1 = FloatBuffer.wrap(vdata[tindices**[0]]);
            FloatBuffer v2 = FloatBuffer.wrap(vdata[tindices**[1]]);
            FloatBuffer v3 = FloatBuffer.wrap(vdata[tindices**[2]]);
            subdivide(v1, v2, v3, positionsBuffer, indicesBuffer, depth);
        }
        normals = positions.clone();
        
        texCoords0 = new float[numVertices * 2];
        for (int i = 0; i < numVertices; i++)
        {
            float nx = positionsBuffer.get(i * 3 + 0);
            float ny = positionsBuffer.get(i * 3 + 1);
            float nz = positionsBuffer.get(i * 3 + 2);
            texCoords0[i * 2 + 0] =
                (float) (Math.atan2(nx, nz) / (2 * Math.PI) + 0.5);
            texCoords0[i * 2 + 1] = (float) (Math.asin(ny) / Math.PI + 0.5);
        }
    }
    
    // Subdivision as described in the redbook
    static void subdivide(
        FloatBuffer v1, FloatBuffer v2, FloatBuffer v3, 
        FloatBuffer vertices, IntBuffer indices, long depth)
    {
        if (depth == 0) 
        {
            drawTriangle(v1, v2, v3, vertices, indices);
            return;
        }

        FloatBuffer v12 = FloatBuffer.allocate(3);
        FloatBuffer v23 = FloatBuffer.allocate(3);
        FloatBuffer v31 = FloatBuffer.allocate(3);

        for (int i = 0; i < 3; i++) 
        {
            v12.put(i, v1.get(i) + v2.get(i));
            v23.put(i, v2.get(i) + v3.get(i));
            v31.put(i, v3.get(i) + v1.get(i));
        }
        normalize3D(v12);
        normalize3D(v23);
        normalize3D(v31);
        subdivide( v1, v12, v31, vertices, indices, depth-1);
        subdivide( v2, v23, v12, vertices, indices, depth-1);
        subdivide( v3, v31, v23, vertices, indices, depth-1);
        subdivide(v12, v23, v31, vertices, indices, depth-1);
    }

    private static void normalize3D(FloatBuffer v)
    {
        float x = v.get(0);
        float y = v.get(1);
        float z = v.get(2);
        float length = (float) Math.sqrt(x * x + y * y + z * z);
        float invLength = 1.0f / length;
        v.put(0, x * invLength);
        v.put(1, y * invLength);
        v.put(2, z * invLength);
    }

    private static void drawTriangle(
        FloatBuffer v1, FloatBuffer v2, FloatBuffer v3, 
        FloatBuffer vertices, IntBuffer indices)
    {
        indices.put(vertices.position() / 3);
        for (int i = 0; i < 3; i++)
        {
            vertices.put(v1.get(i));
        }
        indices.put(vertices.position() / 3);
        for (int i = 0; i < 3; i++)
        {
            vertices.put(v3.get(i));
        }
        indices.put(vertices.position() / 3);
        for (int i = 0; i < 3; i++)
        {
            vertices.put(v2.get(i));
        }
    }
}

Vertex shader:

#version 330 core

#define MAX_LIGHTS 8

#define LIGHT_TYPE_DIRECTIONAL 0
#define LIGHT_TYPE_POINT 1
#define LIGHT_TYPE_SPOT 2

// The attributes of the object, stored in VBOs. 
// Namely the position, normal, color and texcoords
in vec3 vertexPosition;
in vec3 vertexNormal;
in vec4 vertexColor;
in vec2 vertexTexcoord0;
in vec2 vertexTexcoord1;
in vec2 vertexTexcoord2;
in vec2 vertexTexcoord3;

uniform int numTextures;
uniform int hasColors;

//The matrices
uniform mat4 modelMatrix;
uniform mat4 normalMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

//The light structure. The position and spotDirection
//are given in view space
struct Light
{
    int type;
    
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 position;
    
    vec3 spotDirection;
    float spotExponent;
    float spotCutoff;
    
    float constantAttenuation;
    float linearAttenuation;
    float quadraticAttenuation;
};

// The lights
uniform Light lights[MAX_LIGHTS];
uniform int numLights;  

uniform vec4 globalAmbient;


//The material structure
struct Material 
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 emission;
    float shininess;
};

// The material
uniform Material material;

// Position, normal, color and texture coordinates for fragment shader
out vec3 fragmentPosition;
out vec3 fragmentNormal;
out vec4 fragmentColor;
out vec2 fragmentTexcoord0;
out vec2 fragmentTexcoord1;
out vec2 fragmentTexcoord2;
out vec2 fragmentTexcoord3;

void main (void)
{
    // Compute the eye-coordinate position of the vertex
    mat4 modelviewMatrix = viewMatrix*modelMatrix;
    vec4 position = vec4(vertexPosition, 1.0);
    vec4 fragmentPosition4 = modelviewMatrix * position; 
    fragmentPosition = fragmentPosition4.xyz / fragmentPosition4.w;

    // Compute the transformed normal
    fragmentNormal = (normalMatrix*vec4(normalize(vertexNormal), 0.0)).xyz;
    
    fragmentColor = vertexColor;

    // Pass the texture coordinates to the fragment shader
    if (numTextures > 0) fragmentTexcoord0 = vertexTexcoord0;
    if (numTextures > 1) fragmentTexcoord1 = vertexTexcoord1;
    if (numTextures > 2) fragmentTexcoord2 = vertexTexcoord2;
    if (numTextures > 3) fragmentTexcoord3 = vertexTexcoord3;
    
    // Do fixed functionality vertex transform
    gl_Position = projectionMatrix*modelviewMatrix*position;
}

Fragment shader:

#version 330 core

#define MAX_LIGHTS 8

#define LIGHT_TYPE_DIRECTIONAL 0
#define LIGHT_TYPE_POINT 1
#define LIGHT_TYPE_SPOT 2


// The position, normal, color and texture coordinates
in vec3 fragmentPosition;
in vec3 fragmentNormal;
in vec4 fragmentColor;
in vec2 fragmentTexcoord0;
in vec2 fragmentTexcoord1;
in vec2 fragmentTexcoord2;
in vec2 fragmentTexcoord3;

uniform int numTextures;
uniform int hasColors;

//The matrices
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 normalMatrix;


//The light structure. The position and spotDirection
//are given in view space
struct Light
{
    int type;
    
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 position;

    vec3 spotDirection;
    float spotExponent;
    float spotCutoff;
    
    float constantAttenuation;
    float linearAttenuation;
    float quadraticAttenuation;
};

// The lights
uniform Light lights[MAX_LIGHTS];
uniform int numLights;

uniform vec4 globalAmbient;


//The material structure
struct Material 
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 emission;
    float shininess;
};

// The material
uniform Material material;

// The texture samplers
uniform sampler2D texture0;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D texture3;

// The ambient, diffuse and specular
// components of the lights output
vec4 lightsAmbient;
vec4 lightsDiffuse;
vec4 lightsSpecular;
vec4 lightsColor;

// The resulting pixel color
out vec4 outColor;



//Compute the contribution of the point light with index i
//to lightsAmbient, lightsDiffuse and lightsSpecular
void pointLight(in int i)
{
    // The eye position in view space is constant
    vec3 eye = vec3 (0.0, 0.0, 1.0);
    
    // Normalize the input normal
    vec3 normal = normalize(fragmentNormal);
   
    // Compute vector from surface to light position
    vec3 VP = vec3 (lights**.position) - fragmentPosition;

    // Compute distance between surface and light position
    float d = length(VP);

    // Normalize the vector from surface to light position
    VP = normalize(VP);

    // Compute attenuation
    float attenuation = 1.0 / 
        (lights**.constantAttenuation +
         lights**.linearAttenuation * d +
         lights**.quadraticAttenuation * d * d);


    // Compute the ambient contribution
    lightsAmbient += lights**.ambient * attenuation;
    
    // Check if the surface faces the light 
    float nDotVP = dot(normal, VP);
    if (!gl_FrontFacing) 
    {
        nDotVP = -nDotVP;
    }
    if (nDotVP > 0)
    {
        // Add the diffuse contribution
        lightsDiffuse  += lights**.diffuse * nDotVP * attenuation;

        // Compute the specular contribution
        vec3 halfVector = normalize(VP + eye);
        float nDotHV = dot(normal, halfVector);
        if (!gl_FrontFacing) 
        {
            nDotHV = -nDotHV;
        }
        float pf = pow(nDotHV, material.shininess);
        lightsSpecular += lights**.specular * pf * attenuation;
    }
}



//Compute the contribution of the spot light with index i
//to lightsAmbient, lightsDiffuse and lightsSpecular
void spotLight(in int i)
{
    // The eye position in view space is constant
    vec3 eye = vec3 (0.0, 0.0, 1.0);
   
    // Normalize the input normal
    vec3 normal = normalize(fragmentNormal);
   
    // Compute vector from surface to light position
    vec3 VP = vec3 (lights**.position) - fragmentPosition;
   
    // Compute distance between surface and light position
    float d = length(VP);

    // Normalize the vector from surface to light position
    VP = normalize(VP);

    // Compute attenuation
    float attenuation = 1.0 / 
        (lights**.constantAttenuation +
         lights**.linearAttenuation * d +
         lights**.quadraticAttenuation * d * d);

    // Check if the surface point is inside the light cone
    float spotDot = dot(-VP, normalize(lights**.spotDirection));
    float spotAttenuation = 0;
    if (spotDot >= cos(radians(lights**.spotCutoff)))
    {
        spotAttenuation = pow(spotDot, lights**.spotExponent);
    }
    attenuation *= spotAttenuation;

    // Compute the ambient contribution
    lightsAmbient  += lights**.ambient * attenuation;
    
    // Check if the surface faces the light 
    float nDotVP = dot(normal, VP);
    if (!gl_FrontFacing) 
    {
        nDotVP = -nDotVP;
    }
    if (nDotVP > 0)
    {
        // Add the diffuse contribution
        lightsDiffuse  += lights**.diffuse * nDotVP * attenuation;

        // Compute the specular contribution
        vec3 halfVector = normalize(VP + eye);
        float nDotHV = dot(normal, halfVector);
        if (!gl_FrontFacing) 
        {
            nDotHV = -nDotHV;
        }
        float pf = pow(nDotHV, material.shininess);
        lightsSpecular += lights**.specular * pf * attenuation;
    }
}




//Compute the contribution of the directional light with index i
//to lightsAmbient, lightsDiffuse and lightsSpecular
void directionalLight(in int i)
{
    // The eye position in view space is constant
    vec3 eye = vec3 (0.0, 0.0, 1.0);

    // Normalize the input normal
    vec3 normal = normalize(fragmentNormal);

    // Compute the light direction (stored in the position 
    // for directional lights)
    vec3 VP = normalize(lights**.position.xyz);

    // Compute the ambient contribution
    lightsAmbient  += lights**.ambient;
    
    // Check if the surface faces the light 
    float nDotVP = dot(normal, VP);
    if (!gl_FrontFacing) 
    {
        nDotVP = -nDotVP;
    }
    if (nDotVP > 0)
    {
        // Add the diffuse contribution
        lightsDiffuse  += lights**.diffuse * nDotVP;
        
        // Compute the specular contribution
        vec3 halfVector = normalize(VP + eye);
        float nDotHV = dot(normal, halfVector);
        if (!gl_FrontFacing) 
        {
            nDotHV = -nDotHV;
        }
        float pf = pow(nDotHV, material.shininess);
        lightsSpecular += lights**.specular * pf;
    }

}


// Compute the contributions of all lights  
// to lightsAmbient, lightsDiffuse and lightsSpecular
void handleLights()
{
    // Initialize the light intensity accumulators
    lightsAmbient = globalAmbient;
    lightsDiffuse  = vec4 (0.0);
    lightsSpecular = vec4 (0.0);

    // Accumulate all light contributions 
    for (int i=0; i<numLights; i++)
    {
        Light light = lights**;
        if (light.type == LIGHT_TYPE_DIRECTIONAL)
        {
            directionalLight(i);
        }
        if (light.type == LIGHT_TYPE_POINT)
        {
            pointLight(i);
        }
        if (light.type == LIGHT_TYPE_SPOT)
        {
            spotLight(i);
        }
    }
    
    // Light component of the pixel color
    lightsColor = 
        lightsAmbient  * material.ambient +
        lightsDiffuse  * material.diffuse +
        lightsSpecular * material.specular +
        material.emission;
    lightsColor = clamp(lightsColor, 0.0, 1.0 );
}



void main (void) 
{
    vec4 color = vec4(1.0);
    if (hasColors != 0)
    {
        fragmentColor;
    }
    if (numTextures == 1) 
    {
    	color =            texture2D(texture0, fragmentTexcoord0);
    }
    if (numTextures == 2) 
    {
        color =            texture2D(texture0, fragmentTexcoord0);
        color = mix(color, texture2D(texture1, fragmentTexcoord1), 0.5);
    }  
    if (numTextures == 3) 
    {
        color =            texture2D(texture0, fragmentTexcoord0);
        color = mix(color, texture2D(texture1, fragmentTexcoord1), 0.5);
        color = mix(color, texture2D(texture2, fragmentTexcoord2), 0.5);
    }  
    if (numTextures == 4) 
    {
        color =            texture2D(texture0, fragmentTexcoord0);
        color = mix(color, texture2D(texture1, fragmentTexcoord1), 0.5);
        color = mix(color, texture2D(texture2, fragmentTexcoord2), 0.5);
        color = mix(color, texture2D(texture3, fragmentTexcoord3), 0.5);
    }
    if (numLights == 0)
    {
        outColor = color; 
    }
    else
    {
        handleLights();
        outColor = lightsColor * color;
    }
}

Ja, wer hätte das gedacht? Ich bastel’ da immernoch weiter dran rum :rolleyes:

Allerdings konnte ich (mal wieder) nicht widerstehen, und hab’ es jetzt DOCH in mehrere Dateien aufgeteilt. Das ganze landet dann voraussichtlich (früher oder später, also irgendwann vielleicht) auf GitHub, mit dem treffenden Namen “gleff”: GL-emulation der fixed-funtion pipeline.

wo nimmst du eigentlich die zeit her? :smiley:

https://www.arbeitsagentur.de/ :rolleyes:

Tja, und Samstage, natürlich. Geht doch nichts über 8 Stunden Debugging, nur um dann festzustellen, dass elementarste Matrix-Operationen schlicht falsch implementiert sind. Die unangenehmen Folgen des Versuches, das ganze (erst) in eine riesen-Klasse zu packen…

Ich dachte solche Sachen passieren Profis nicht :smiley:

Ja, das mit den Matrizen ist eben so eine Sache.

Es ist natürlich ein absoluter Krampf und Murx die “händisch” auf Basis von float-Arrays zu implementieren. Aber leider gibt es sowas wie GLM nicht für Java.

Es gibt zwar https://github.com/java-graphics/glm oder https://github.com/jroyalty/jglm, aber das scheint beides nicht sooo aktiv gepflegt zu werden, und setzt den IMHO recht wichtigen Punkt der Allokationsfreiheit nicht wirklich um.

Inzwischen gibt es https://github.com/JOML-CI/JOML , was in vieler Hinsicht SEHR gut aussieht (z.B. eben Allokationsfreiheit), aber so ein, zwei kleine “Schnitzer” hat, die mich davon abhalten, es “breiter” einzusetzen: Warum zur Hölle muss da mit “Unsafe” und JNI rumhantiert werden?

Früher gab es mal https://java.net/projects/vecmath/ , aber das ist etwas antiquiert und recht “weit weg” von GL: Die Matrizen haben 16 einzelne float-fields, aber die in einen (direct) ByteBuffer zu packen, um sie in GL zu verwenden, ist etwas krampfig.

Wie auch immer: Die eigentlichen Emulation von Lichtern und Materialien nähert sich dem, wie es sein soll:

Sowas wie “doubleSided” fehlt noch, und ein Umschalten zwischen Blinn und Phong will ich noch einbauen, und dann kommt ja nicht der große Block: Texturen. Aber … zumindest für glTF ist das ja gerade wieder out, und dort läuft alles auf PBR hinaus: https://forum.byte-welt.net/byte-welt-projekte-projects/allgemeines-zu-projekten-generals/20075-jgltf-java-libraries-fuer-gltf.html#post143623

Das ist eine nette Idee.
Als ich das erste mal auf OGL 2.0 umgestiegen bin (bzw. direkt 3.3) hat es mich schon gestört dass man mit den Shadern nicht auf der fixed-function Pipeline aufbauen konnte sondern stattdessen alles von scratch programmieren musste - bzw. Haare raufen und in Tischkanten beißen.

Ich denke für Anfänger auf dem Gebiet ist so etwas sicherlich hilfreich.

Jetzt suchst du aber schon ziemlich lange nach einem Job oder kommt mir das nur so vor? Die Branche klagt doch im Moment nach “Facharbeitern”?
(Bin im Moment auch auf der Suche, aber noch einige Zeit im Beruf :D)

Ja, an einigen Punkten bin ich noch nicht sicher, wie die eigentliche Umsetzung am Ende sein sollte.

Eigentlich ging es NUR um die Shader. D.h. der ganze Rest sollte “schnell runtergeschrieben” und weitgehend Kommentarfrei (und ungetestet - siehe oben :rolleyes: ) und in dieser Hinsicht eben “minimal” sein. Aber irgendwie kommt man um bestimmte Strukturen nicht drumrum. Das “zentralste” ist da schon die angedeutete Matrix/Vektor-Sache. Dann dachte ich aber noch drüber nach, ob man das in anderer Hinsicht “nützlich” machen könnte. Vielleicht eine Abstraktionsschicht um GL herum? Also ein GL-Interface mit JOGL- oder LWJGL-Implementierungen? Und dann vielleicht gleich (darauf aufbauend) ein “Gleff”-Interface, so dass da am Ende als Pseudocode sowas rauskommt wie

class DefaultGleff implements Gleff {
    private final GL gl;

    @Override
    void glLight3fv(int id, int param, float values[]) {
        ...
        gl.setUniform(....);
        ...
    }
]

(so direkt wird das nicht gehen, aber ich hatte kurz überlegt, ob oder wie weit man in diese Richtung gehen könnte)

[ot]

Jetzt suchst du aber schon ziemlich lange nach einem Job oder kommt mir das nur so vor? Die Branche klagt doch im Moment nach “Facharbeitern”?

Ja, ist schon eine Weile. Wird langsam heikel. Was auch immer da draußen so gesucht wird: Ich bin’s offenbar nicht.
[/ot]