继续搞Java的OpenGL
需要导入的jar包
将官方Demo里的LWJGLCanvas进行简单的修改
LWJGLCanvas.java
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.Platform;
import org.lwjgl.system.jawt.JAWT;
import org.lwjgl.system.jawt.*;
import org.lwjgl.system.linux.XVisualInfo;
import java.awt.*;
import java.awt.event.*;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFWNativeWin32.glfwAttachWin32Window;
import static org.lwjgl.opengl.GLX13.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.NULL;
import static org.lwjgl.system.jawt.JAWTFunctions.*;
public class LWJGLCanvas extends Canvas {
private final JAWT awt;
private JAWTDrawingSurface ds;
private final List<Renderer> renderers = new ArrayList<>();
private long context;
private GLCapabilities caps;
public LWJGLCanvas() {
awt = JAWT.calloc();
awt.version(JAWT_VERSION_1_4);
if (!JAWT_GetAWT(awt)) {
throw new IllegalStateException("GetAWT failed");
}
// AWT event listeners are invoked in the EDT
addComponentListener(new ComponentAdapter() {
@Override public void componentResized(ComponentEvent e) {
System.out.println(e);
if (context != NULL) {
paint();
}
}
@Override public void componentMoved(ComponentEvent e) {
System.out.println(e);
}
@Override public void componentShown(ComponentEvent e) {
System.out.println(e);
}
@Override public void componentHidden(ComponentEvent e) {
System.out.println(e);
}
});
// addFocusListener(new FocusListener() {
// @Override public void focusGained(FocusEvent e) {
// System.out.println(e);
// }
// @Override public void focusLost(FocusEvent e) {
// System.out.println(e);
// }
// });
// addKeyListener(new KeyAdapter() {
// @Override public void keyPressed(KeyEvent e) {
// System.out.println(e);
// }
// @Override public void keyTyped(KeyEvent e) {
// System.out.println(e);
// }
// @Override public void keyReleased(KeyEvent e) {
// System.out.println(e);
// }
// });
// addMouseListener(new MouseAdapter() {
// @Override public void mouseClicked(MouseEvent e) {
// System.out.println(e);
// }
// @Override public void mousePressed(MouseEvent e) {
// System.out.println(e);
// }
// @Override public void mouseReleased(MouseEvent e) {
// System.out.println(e);
// }
// @Override public void mouseEntered(MouseEvent e) {
// System.out.println(e);
// }
// @Override public void mouseExited(MouseEvent e) {
// System.out.println(e);
// }
// @Override public void mouseWheelMoved(MouseWheelEvent e) {
// System.out.println(e);
// }
// @Override public void mouseDragged(MouseEvent e) {
// System.out.println(e);
// }
// @Override public void mouseMoved(MouseEvent e) {
// System.out.println(e);
// }
// });
// addMouseMotionListener(new MouseMotionListener() {
// @Override public void mouseDragged(MouseEvent e) {
// System.out.println(e);
// }
// @Override public void mouseMoved(MouseEvent e) {
// System.out.println(e);
// }
// });
// addMouseWheelListener(System.out::println);
}
@Override
public void update(Graphics g) {
paint(g);
}
@Override
public void paint(Graphics g) {
paint();
repaint();
}
public void addRenderer(Renderer renderer){
if(renderer == null){
return;
}
renderers.add(renderer);
}
public void removeRenderer(Renderer renderer){
if(renderer == null){
return;
}
renderers.remove(renderer);
}
private void paint() {
if (ds == null) {
// Get the drawing surface
ds = JAWT_GetDrawingSurface(this, awt.GetDrawingSurface());
if (ds == null) {
throw new IllegalStateException("awt->GetDrawingSurface() failed");
}
}
// Lock the drawing surface
int lock = JAWT_DrawingSurface_Lock(ds, ds.Lock());
if ((lock & JAWT_LOCK_ERROR) != 0) {
throw new IllegalStateException("ds->Lock() failed");
}
try {
// Get the drawing surface info
JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo());
if (dsi == null) {
throw new IllegalStateException("ds->GetDrawingSurfaceInfo() failed");
}
try {
switch (Platform.get()) {
case LINUX:
// Get the platform-specific drawing info
JAWTX11DrawingSurfaceInfo dsi_x11 = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo());
long drawable = dsi_x11.drawable();
if (drawable == NULL) {
break;
}
if (context == NULL) {
createContextGLX(dsi_x11);
for (Renderer r: renderers) {
r.onCreated();
}
} else {
if (!glXMakeCurrent(dsi_x11.display(), drawable, context)) {
throw new IllegalStateException("glXMakeCurrent() failed");
}
GL.setCapabilities(caps);
}
render(getWidth(), getHeight());
glXSwapBuffers(dsi_x11.display(), drawable);
glXMakeCurrent(dsi_x11.display(), NULL, NULL);
GL.setCapabilities(null);
break;
case WINDOWS:
// Get the platform-specific drawing info
JAWTWin32DrawingSurfaceInfo dsi_win = JAWTWin32DrawingSurfaceInfo.create(dsi.platformInfo());
long hdc = dsi_win.hdc();
if (hdc == NULL) {
break;
}
// The render method is invoked in the EDT
if (context == NULL) {
createContextGLFW(dsi_win);
for (Renderer r: renderers) {
r.onCreated();
}
} else {
glfwMakeContextCurrent(context);
GL.setCapabilities(caps);
}
try (MemoryStack stack = stackPush()) {
IntBuffer pw = stack.mallocInt(1);
IntBuffer ph = stack.mallocInt(1);
glfwGetFramebufferSize(context, pw, ph);
render(pw.get(0), ph.get(0));
}
glfwSwapBuffers(context);
glfwMakeContextCurrent(NULL);
GL.setCapabilities(null);
break;
}
} finally {
// Free the drawing surface info
JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo());
}
} finally {
// Unlock the drawing surface
JAWT_DrawingSurface_Unlock(ds, ds.Unlock());
}
}
private int viewWidth = -1;
private int viewHeight = -1;
private void render(int width, int height) {
if(viewWidth != width || viewHeight != height){
viewWidth = width;
viewHeight = height;
for (Renderer r: renderers) {
r.onChanged(width,height);
}
}
for (Renderer r: renderers) {
r.onDrawFrame();
}
}
private void createContextGLFW(JAWTWin32DrawingSurfaceInfo dsi_win) {
// glfwWindowHint can be used here to configure the GL context
context = glfwAttachWin32Window(dsi_win.hwnd(), NULL);
if (context == NULL) {
throw new IllegalStateException("Failed to attach win32 window.");
}
// Any callbacks registered here will work. But care must be taken because
// the callbacks are NOT invoked in the EDT, but in an AWT thread that
// does the event polling. Many GLFW functions that require main thread
// invocation, should only be called in that thread.
// Because of how input focus is implemented in AWT, it is recommended that AWT
// KeyListeners are always used for keyboard input.
glfwMakeContextCurrent(context);
caps = GL.createCapabilities();
}
// Simplest possible context creation.
private void createContextGLX(JAWTX11DrawingSurfaceInfo dsi_x11) {
long display = dsi_x11.display();
long drawable = dsi_x11.drawable();
PointerBuffer configs = Objects.requireNonNull(glXChooseFBConfig(display, 0, (IntBuffer)null));
long config = NULL;
for (int i = 0; i < configs.remaining(); i++) {
XVisualInfo vi = Objects.requireNonNull(glXGetVisualFromFBConfig(display, configs.get(i)));
if (vi.visualid() == dsi_x11.visualID()) {
config = configs.get(i);
break;
}
}
context = glXCreateNewContext(display, config, GLX_RGBA_TYPE, NULL, true);
if (context == NULL) {
throw new IllegalStateException("glXCreateContext() failed");
}
if (!glXMakeCurrent(display, drawable, context)) {
throw new IllegalStateException("glXMakeCurrent() failed");
}
caps = GL.createCapabilities();
}
public void destroy() {
// Free the drawing surface
JAWT_FreeDrawingSurface(ds, awt.FreeDrawingSurface());
awt.free();
if (context != NULL) {
glfwDestroyWindow(context);
}
}
public interface Renderer {
void onCreated();
void onChanged(int width, int height);
void onDrawFrame();
}
}
主要加入了Renderer,用于渲染用,模仿Android的GLSurfaceView的Renderer
创建Shader加载类ShaderUtils.java
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.system.MemoryStack.stackPush;
public class ShaderUtils {
public static int createProgram(ByteBuffer vs, ByteBuffer fs) {
GLCapabilities caps = GL.getCapabilities();
if (!caps.OpenGL20) {
throw new IllegalStateException("This demo requires OpenGL 2.0 or higher.");
}
int version;
if (caps.OpenGL33) {
version = 330;
} else if (caps.OpenGL21) {
version = 120;
} else {
version = 110;
}
int v = glCreateShader(GL_VERTEX_SHADER);
int f = glCreateShader(GL_FRAGMENT_SHADER);
compileShader(version, v, vs);
compileShader(version, f, fs);
int p = glCreateProgram();
glAttachShader(p, v);
glAttachShader(p, f);
glLinkProgram(p);
printProgramInfoLog(p);
if (glGetProgrami(p, GL_LINK_STATUS) != GL_TRUE) {
throw new IllegalStateException("Failed to link program.");
}
glUseProgram(p);
return p;
}
private static void compileShader(int version, int shader, ByteBuffer code) {
try (MemoryStack stack = stackPush()) {
ByteBuffer header = stack.ASCII("#version " + version + "\n#line 0\n", false);
glShaderSource(
shader,
stack.pointers(header, code),
stack.ints(header.remaining(), code.remaining())
);
glCompileShader(shader);
printShaderInfoLog(shader);
if (glGetShaderi(shader, GL_COMPILE_STATUS) != GL_TRUE) {
throw new IllegalStateException("Failed to compile shader.");
}
}
}
private static void printShaderInfoLog(int obj) {
int infologLength = glGetShaderi(obj, GL_INFO_LOG_LENGTH);
if (infologLength > 0) {
glGetShaderInfoLog(obj);
System.out.format("%s\n", glGetShaderInfoLog(obj));
}
}
private static void printProgramInfoLog(int obj) {
int infologLength = glGetProgrami(obj, GL_INFO_LOG_LENGTH);
if (infologLength > 0) {
glGetProgramInfoLog(obj);
System.out.format("%s\n", glGetProgramInfoLog(obj));
}
}
}
创建渲染类GLRenderer.java
import org.lwjgl.BufferUtils;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import static com.hyq.hm.test.gl.IOUtil.ioResourceToByteBuffer;
import static org.lwjgl.opengl.GL30C.*;
public class GLRenderer implements LWJGLCanvas.Renderer {
private final int[] textures = new int[1];
private final int[] bos = new int[2];
private int programId = -1;
private int aPositionHandle;
private int aTextureCoordHandle;
private int uTextureSamplerHandle;
private int imageWidth,imageHeight;
private ByteBuffer imageBuffer;
public void setImage(BufferedImage image){
imageWidth = image.getWidth();
imageHeight = image.getHeight();
imageBuffer = imageToBuffer(image);
}
@Override
public void onCreated() {
try {
ByteBuffer vs = ioResourceToByteBuffer("X:\\test\\image.vert", 4096);
ByteBuffer fs = ioResourceToByteBuffer("X:\\test\\image.frag", 4096);
programId = ShaderUtils.createProgram(vs, fs);
} catch (IOException e) {
e.printStackTrace();
}
if(programId == -1){
return;
}
aPositionHandle = glGetAttribLocation(programId, "aPosition");
aTextureCoordHandle = glGetAttribLocation(programId, "aTexCoord");
uTextureSamplerHandle = glGetUniformLocation(programId, "sTexture");
final float[] vertexData = {
1f, -1f, 0f,
-1f, -1f, 0f,
1f, 1f, 0f,
-1f, 1f, 0f
};
final float[] textureVertexData = {
1f, 0f,
0f, 0f,
1f, 1f,
0f, 1f
};
FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
vertexBuffer.position(0);
FloatBuffer textureVertexBuffer = ByteBuffer.allocateDirect(textureVertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureVertexData);
textureVertexBuffer.position(0);
glGenBuffers(bos);
glBindBuffer(GL_ARRAY_BUFFER, bos[0]);
glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, bos[1]);
glBufferData(GL_ARRAY_BUFFER, textureVertexBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenTextures(textures);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageWidth, imageHeight,0, GL_RGBA, GL_UNSIGNED_BYTE, imageBuffer);
glBindTexture(GL_TEXTURE_2D, 0);
}
@Override
public void onChanged(int width, int height) {
if(programId == -1){
return;
}
viewportSize(width,height,imageWidth,imageHeight);
}
@Override
public void onDrawFrame() {
if(programId == -1){
return;
}
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glClearColor(1.0f,1.0f,1.0f,1.0f);
glUseProgram(programId);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glUniform1i(uTextureSamplerHandle, 0);
glBindBuffer(GL_ARRAY_BUFFER, bos[0]);
glEnableVertexAttribArray(aPositionHandle);
glVertexAttribPointer(aPositionHandle, 3, GL_FLOAT, false,
0, 0);
glBindBuffer(GL_ARRAY_BUFFER, bos[1]);
glEnableVertexAttribArray(aTextureCoordHandle);
glVertexAttribPointer(aTextureCoordHandle, 2, GL_FLOAT, false, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0);
}
private void viewportSize(int screenWidth,int screenHeight,int width,int height) {
int left, top, viewWidth, viewHeight;
float sh = screenWidth * 1.0f / screenHeight;
float vh = width * 1.0f / height;
if (sh < vh) {
left = 0;
viewWidth = screenWidth;
viewHeight = (int) (height * 1.0f / width * viewWidth);
top = (screenHeight - viewHeight) / 2;
} else {
top = 0;
viewHeight = screenHeight;
viewWidth = (int) (width * 1.0f / height * viewHeight);
left = (screenWidth - viewWidth) / 2;
}
glViewport(left, top, viewWidth, viewHeight);
}
public static ByteBuffer imageToBuffer(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int[] pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);
ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 4);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = pixels[y * width + x];
buffer.put((byte) ((pixel >> 16) & 0xFF));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
buffer.put((byte) ((pixel >> 24) & 0xFF));
}
}
buffer.flip();
return buffer;
}
}
代码和我上一篇的差不多,区别在Shader的加载是文件转成ByteBuffer,而不是直接使用String
加载Shader需要用到官方Demo里的IOUtil.java,直接复制过来就好了
image.frag
uniform sampler2D sTexture;
#if __VERSION__ < 130
varying vec2 vTexCoord;
#define out_Color gl_FragColor
#else
in vec2 vTexCoord;
#if __VERSION__ < 330
out vec4 out_Color;
#else
layout(location = 0) out vec4 out_Color;
#endif
#endif
void main() {
out_Color = texture(sTexture , vec2(vTexCoord.x , 1.0 - vTexCoord.y));
}
image.vert
#if __VERSION__ < 130
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
#else
#if __VERSION__ < 330
in vec4 aPosition;
in vec2 aTexCoord;
#else
layout(location = 0) in vec4 aPosition;
layout(location = 1) in vec2 aTexCoord;
#endif
out vec2 vTexCoord;
#endif
void main() {
vTexCoord = aTexCoord;
gl_Position = aPosition;
}
最后是Main函数
public static void main(String[] args) {
// write your code here
if (Platform.get() == Platform.MACOSX) {
throw new UnsupportedOperationException("This demo cannot run on macOS.");
}
GLFWErrorCallback.createPrint().set();
if (!glfwInit()) {
throw new IllegalStateException("Unable to initialize glfw");
}
LWJGLCanvas canvas = new LWJGLCanvas();
canvas.setSize(640, 480);
BufferedImage image = null;
try {
image = ImageIO.read(new File("X:\\test\\ic_car.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
if(image != null){
GLRenderer renderer = new GLRenderer();
renderer.setImage(image);
canvas.addRenderer(renderer);
}
JFrame frame = new JFrame("JAWT Demo");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
canvas.destroy();
frame.dispose();
glfwTerminate();
Objects.requireNonNull(glfwSetErrorCallback(null)).free();
}
});
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(e -> {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE && e.getID() == KeyEvent.KEY_PRESSED) {
canvas.destroy();
frame.dispose();
glfwTerminate();
Objects.requireNonNull(glfwSetErrorCallback(null)).free();
return true;
}
return false;
});
frame.setLayout(new BorderLayout());
frame.add(canvas, BorderLayout.CENTER);
frame.add(new JTextField(), BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
效果