package net.hvidtfeldts.meshia.engine3d; import java.awt.FlowLayout; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import javax.media.opengl.DebugGL2; import javax.media.opengl.GL; import javax.media.opengl.GL2; import javax.media.opengl.GL2ES2; import javax.media.opengl.GL2GL3; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; import javax.media.opengl.GLUniformData; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import net.hvidtfeldts.meshia.gui.Project; import net.hvidtfeldts.meshia.gui.Project.ProjectEvent; import net.hvidtfeldts.utils.EventSource.EventListener; import net.hvidtfeldts.utils.Logger; import com.jogamp.opengl.JoglVersion; import com.jogamp.opengl.util.GLPixelStorageModes; import com.jogamp.opengl.util.PMVMatrix; import com.jogamp.opengl.util.awt.ImageUtil; import com.jogamp.opengl.util.glsl.ShaderCode; import com.jogamp.opengl.util.glsl.ShaderProgram; import com.jogamp.opengl.util.glsl.ShaderState; public class Engine implements GLEventListener, EventListener<ProjectEvent> { private final Camera camera = new Camera(); private final Project project; private ShaderState shaderState; private PMVMatrix matrixStack; private GLUniformData pmvMatrixUniform; private PMVMatrix raytracerMatrixStack; private ShaderState raytracerShaderState; private GLUniformData raytracerPmvMatrixUniform; private Hemesh3D hemesh; private Object3D crossSection; private FullscreenQuadObject raytracerObject; private JPanel panel; private final FloatBuffer farNear = FloatBuffer.allocate(2); private GLUniformData fovYScaleUniform; private float fovY = 45.0f; private GLUniformData aspectUniform; private GLUniformData farNearUniform; private long lastTime; private int frames; private boolean inError; private GL2ES2 gl; private float aspect; private float zNear; private float zFar; public Engine(Project project) { this.project = project; project.addEventListener(this); } @Override public void init(GLAutoDrawable glad) { glad.setGL(new DebugGL2((GL2) glad.getGL())); try { final GL2ES2 gl = glad.getGL().getGL2ES2(); this.gl = gl; Logger.log("Chosen GLCapabilities: " + glad.getChosenGLCapabilities()); Logger.log("INIT GL IS: " + gl.getClass().getName()); Logger.log(JoglVersion.getGLStrings(gl, null, false).toString()); if (!gl.hasGLSL()) { Logger.warn("No GLSL available, no rendering."); return; } shaderState = new ShaderState(); shaderState.setVerbose(true); raytracerShaderState = new ShaderState(); raytracerShaderState.setVerbose(true); initializeShaders(gl); hemesh = new Hemesh3D(shaderState, "Hemesh"); raytracerObject = new FullscreenQuadObject(raytracerShaderState, "Quad"); project.addObject(hemesh); project.addObject(raytracerObject); for (Object3D o : project.getObjects()) { o.init(gl); } // OpenGL Render Settings gl.glEnable(GL2ES2.GL_DEPTH_TEST); shaderState.useProgram(gl, false); raytracerShaderState.useProgram(gl, false); Logger.log("Init finished"); } catch (GLException e) { Logger.warn(e); check(false); } } private void setupMatrixStack(final GL2ES2 gl) { matrixStack = new PMVMatrix(); matrixStack.glMatrixMode(PMVMatrix.GL_PROJECTION); matrixStack.glLoadIdentity(); matrixStack.glMatrixMode(PMVMatrix.GL_MODELVIEW); matrixStack.glLoadIdentity(); pmvMatrixUniform = new GLUniformData("pmvMatrix", 4, 4, matrixStack.glGetPMvMvitMatrixf()); // P, Mv shaderState.ownUniform(pmvMatrixUniform); shaderState.uniform(gl, pmvMatrixUniform); } private void setupRaytracerMatrixStack(final GL2ES2 gl) { raytracerMatrixStack = new PMVMatrix(); raytracerMatrixStack.glMatrixMode(PMVMatrix.GL_PROJECTION); raytracerMatrixStack.glLoadIdentity(); raytracerMatrixStack.glMatrixMode(PMVMatrix.GL_MODELVIEW); raytracerMatrixStack.glLoadIdentity(); raytracerPmvMatrixUniform = new GLUniformData("pmvMatrix", 4, 4, raytracerMatrixStack.glGetPMvMvitMatrixf()); // P, Mv fovYScaleUniform = new GLUniformData("fov_y_scale", 1); aspectUniform = new GLUniformData("aspect", 1); float fovYScale = (float) Math.tan((Math.PI * fovY / 360.0)); fovYScaleUniform.setData(fovYScale); aspectUniform.setData(1.0f); farNearUniform = new GLUniformData("farNear", 2, farNear); raytracerShaderState.ownUniform(raytracerPmvMatrixUniform); raytracerShaderState.uniform(gl, raytracerPmvMatrixUniform); } private void initializeFlatShaders(final GL2ES2 gl, ShaderState shaderState) { final ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(), "shaders", "shader/bin", "CrossSectionShader", true); final ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(), "shaders", "shader/bin", "CrossSectionShader", true); vp0.defaultShaderCustomization(gl, true, true); fp0.defaultShaderCustomization(gl, true, true); final ShaderProgram sp0 = new ShaderProgram(); check(sp0.add(gl, vp0, Logger.getLoggerWarnStream())); check(sp0.add(gl, fp0, Logger.getLoggerWarnStream())); check(shaderState.attachShaderProgram(gl, sp0, true)); } private void initializeShaders(final GL2ES2 gl) { final ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(), "shaders", "shader/bin", "PhongShader", true); final ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(), "shaders", "shader/bin", "PhongShader", true); vp0.defaultShaderCustomization(gl, true, true); fp0.defaultShaderCustomization(gl, true, true); final ShaderProgram sp0 = new ShaderProgram(); check(sp0.add(gl, vp0, Logger.getLoggerWarnStream())); check(sp0.add(gl, fp0, Logger.getLoggerWarnStream())); check(shaderState.attachShaderProgram(gl, sp0, true)); setupMatrixStack(gl); final ShaderCode vp1 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(), "shaders", "shader/bin", "RaytracerShader", true); final ShaderCode fp1 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(), "shaders", "shader/bin", "RaytracerShader", true); vp1.defaultShaderCustomization(gl, true, true); fp1.defaultShaderCustomization(gl, true, true); final ShaderProgram sp1 = new ShaderProgram(); check(sp1.add(gl, vp1, Logger.getLoggerWarnStream())); check(sp1.add(gl, fp1, Logger.getLoggerWarnStream())); check(raytracerShaderState.attachShaderProgram(gl, sp1, true)); setupRaytracerMatrixStack(gl); } private void check(boolean returnValue) { if (!returnValue) { inError = true; Logger.warn("Failed"); } } @Override public void display(GLAutoDrawable glad) { if (inError) { return; } if (OpenGlWindow.MEASURE_FPS) { showFPS(); } final GL2ES2 gl = glad.getGL().getGL2ES2(); this.gl = gl; gl.glClearColor(0, 0, 0, 0); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); shaderState.useProgram(gl, true); setCamera(matrixStack, false); matrixStack.update(); shaderState.uniform(gl, pmvMatrixUniform); for (Object3D o : project.getObjects()) { if (o.isVisible()) { o.draw(gl); } } shaderState.useProgram(gl, false); raytracerShaderState.useProgram(gl, true); setCamera(raytracerMatrixStack, true); raytracerMatrixStack.update(); raytracerShaderState.uniform(gl, raytracerPmvMatrixUniform); raytracerShaderState.uniform(gl, fovYScaleUniform); raytracerShaderState.uniform(gl, aspectUniform); raytracerShaderState.uniform(gl, farNearUniform); raytracerObject.draw(gl); raytracerShaderState.useProgram(gl, false); } private void showFPS() { long time = System.currentTimeMillis(); if (lastTime == 0) { lastTime = time; } if (time - lastTime > 5000) { float fps = (frames * 1000.0f) / (time - lastTime); lastTime = time; frames = 0; Logger.log("FPS: " + fps); } frames++; } private void setCamera(PMVMatrix m, boolean full) { m.glMatrixMode(PMVMatrix.GL_MODELVIEW); m.glLoadIdentity(); camera.setMatrix(m); } @Override public void reshape(GLAutoDrawable glad, int x, int y, int width, int height) { if (inError) { return; } try { final GL2ES2 gl = glad.getGL().getGL2ES2(); gl.setSwapInterval(1); shaderState.useProgram(gl, true); aspect = (float) width / (float) height; zNear = 0.5f; zFar = 100f; updateFOV(); shaderState.uniform(gl, pmvMatrixUniform); shaderState.useProgram(gl, false); raytracerShaderState.useProgram(gl, true); raytracerMatrixStack.glMatrixMode(PMVMatrix.GL_PROJECTION); raytracerMatrixStack.glLoadIdentity(); raytracerMatrixStack.gluPerspective(fovY, aspect, zNear, zFar); raytracerShaderState.uniform(gl, raytracerPmvMatrixUniform); raytracerShaderState.useProgram(gl, false); } catch (GLException e) { Logger.warn(e); check(false); } } @Override public void dispose(GLAutoDrawable glad) { Logger.log("JOGL Dispose"); final GL2ES2 gl = glad.getGL().getGL2ES2(); if (!gl.hasGLSL()) { return; } shaderState.destroy(gl); matrixStack.destroy(); } public void rotate(int i, int j) { camera.rotateAboutRight(j / 100.0f); camera.rotateAboutUp(i / 100.0f); } public void moveCamera(float x, float y, float z) { camera.move(x / 3.0f, y / 3.0f, z / 3.0f); } public void takeSnapshot() { // GL gl = GLContext.getCurrentGL(); int[] color = new int[] { -1 }; int[] depth = new int[] { -1 }; int[] fbo = new int[] { -1 }; gl.getContext().makeCurrent(); gl.glGenTextures(1, color, 0); Logger.log(color[0]); int width = 500; int height = 500; gl.glBindTexture(GL.GL_TEXTURE_2D, color[0]); gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, width, height, 0, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, null); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); gl.glGenRenderbuffers(1, depth, 0); gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, depth[0]); gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL2ES2.GL_DEPTH_COMPONENT, width, height); gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0); gl.glGenFramebuffers(1, fbo, 0); gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo[0]); gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, color[0], 0); gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, GL.GL_RENDERBUFFER, depth[0]); gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0); gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo[0]); Logger.log("Created FBO: Color=" + color[0] + " depth=" + depth[0] + " fbo=" + fbo[0]); // Do some drawing here. gl.glViewport(0, 0, width, height); gl.glClearColor(1.0f, 0.0f, 0.0f, 1.0f); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); ShaderState shaderState = new ShaderState(); shaderState.setVerbose(true); initializeFlatShaders(gl, shaderState); crossSection = new CrossSection2D(shaderState, "Cross-section"); crossSection.init(gl); shaderState.useProgram(gl, true); crossSection.draw(gl); shaderState.useProgram(gl, false); // Based on JOGL Screenshot.java boolean alpha = false; int bufImgType = (alpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); int readbackType = (alpha ? GL2.GL_ABGR_EXT : GL2GL3.GL_BGR); BufferedImage image = new BufferedImage(width, height, bufImgType); // Set up pixel storage modes GLPixelStorageModes psm = new GLPixelStorageModes(); psm.setPackAlignment(gl, 1); // read the BGR values into the image gl.glReadPixels(0, 0, width, height, readbackType, GL.GL_UNSIGNED_BYTE, ByteBuffer.wrap(((DataBufferByte) image.getRaster().getDataBuffer()).getData())); // Restore pixel storage modes psm.restore(gl); if (gl.getContext().getGLDrawable().isGLOriented()) { // Must flip BufferedImage vertically for correct results ImageUtil.flipImageVertically(image); } gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0); JFrame frame = new JFrame(); frame.getContentPane().setLayout(new FlowLayout()); frame.getContentPane().add(new JLabel(new ImageIcon(image))); frame.pack(); frame.setVisible(true); } public Camera getCamera() { return camera; } public SunflowRenderable getMesh() { for (Object3D o : project.getObjects()) { if (o instanceof SunflowRenderable && o.isVisible()) { return (SunflowRenderable) o; } } return null; } public ShaderState getShaderState() { return shaderState; } @Override public void eventReceived(ProjectEvent event) { panel.repaint(); } public void setPanel(JPanel panel) { this.panel = panel; } public void zoom(float f) { this.fovY *= (1.0 + f); if (fovY > 90) { fovY = 90; } if (fovY < 4) { fovY = 4; } updateFOV(); Logger.log(fovY); } private void updateFOV() { // compute projection parameters 'normal' perspective aspectUniform.setData(aspect); farNear.put(0, zFar); farNear.put(1, zNear); matrixStack.glMatrixMode(PMVMatrix.GL_PROJECTION); matrixStack.glLoadIdentity(); matrixStack.gluPerspective(fovY, aspect, zNear, zFar); float fovYScale = (float) Math.tan((Math.PI * fovY / 360.0)); fovYScaleUniform.setData(fovYScale); } }