本文一共分为2部分
- 创建展示窗口。
- 在窗口上绘制三角形。
在绘制三角形之前,需要创建一个OpenGL上下文(Context)和一个用于显示的窗口。然而,这些操作在每个系统上都是不一样的,OpenGL将这部分抽离了出去。
GLFW
GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文,定义窗口参数以及处理用户输入。
一、只绘制渲染窗口的代码
public class Demo01_open_window {
public static void main(String[] args){
glfwInit();//初始化
glfwWindowHint(GLFW_VISIBLE, GL_FALSE);//设置窗口的可见性为false
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);//设置窗口是否可以重新调整大小
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//OpenGL的主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);//OpenGL的副版本号
int width = 500;//窗口宽度
int height = 300;//窗口高度
long window = glfwCreateWindow(width, height, "Hello World!", NULL, NULL);//创建一个窗口,返回值是个long值。
glfwMakeContextCurrent(window);//通知GLFW将window的上下文设置为当前线程的主上下文
glfwShowWindow(window);//展示当前的窗口
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());//获取当前设备的一些属性
glfwSetWindowPos(window, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2);//设置窗口的位置在最中间
GL.createCapabilities();//创建opengl上下文
//不间断的一直渲染窗口,如果没有这步循环,窗口会一闪而过。
while (!glfwWindowShouldClose(window)) {
// 清除之前渲染的缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glfwSwapBuffers(window); // 交换前后缓冲区
glfwPollEvents();//检查是否有键盘或是鼠标事件
}
glfwTerminate();//释放之前分配的所有资源。
}
}
运行结果:
上面代码中的相关说明
- 如果不在一开始就调用
glfwInit();
函数,则后面的函数将都会失效。 - OpenGL的主版本号在绘制图形时,一定要设置。
双缓冲(Double Buffer)
:应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们glfwSwapBuffers()
函数就是用来交换(Swap)前缓冲和后缓冲的,这样图像就立即呈显出来,之前提到的不真实感就消除了。
二、绘制三角形的代码
将展示窗口进行封装整理,分别封装成以下几个类:
- DrawTriangleMain:主函数入口。
- Window:展示窗口。
- ShaderProgram:着色器处理类。
- Model:三角形顶点相关。
- Renderer:负责渲染,即循环绘制图像。
DrawTriangleMain类
public class DrawTriangleMain {
public static void main(String[] args) throws Exception {
//创建一个窗口
Window window = new Window();
//创建一个着色器程序处理类
ShaderProgram shaderProgram = new ShaderProgram();
try {
//加载着色器程序
shaderProgram.createShader(GL_VERTEX_SHADER,"/shader/demo02_vertex.vs");
shaderProgram.createShader(GL_FRAGMENT_SHADER,"/shader/demo02_fragment.fs");
shaderProgram.linkShader();
} catch (Exception e) {
e.printStackTrace();
}
//创建一个vao集合,将所有需要被渲染的vao全部装入该集合中。
List<Integer> vaoList = new ArrayList<>();
Model model = new Model();
//渲染时,只用vao就可以了
int vaoId = model.getVaoId();
vaoList.add(vaoId);
//创建一个渲染器
Renderer renderer = new Renderer();
//调用渲染代码
renderer.render(window,shaderProgram.getProgramId(),vaoList);
}
}
Window类
public class Window {
private long windowId;
private int width = 600;
private int height = 500;
private String title = "暂无";
public Window() {
init();
}
public Window(int width, int height, String title) {
this.width = width;
this.height = height;
this.title = title;
init();
}
public void init(){
//初始化
glfwInit();
//设置窗口的可见性为false
glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
//设置窗口是否可以重新调整大小
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//OpenGL的主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);//OpenGL的副版本号
//创建一个窗口,返回值是个long值。
windowId = glfwCreateWindow(width, height, title, NULL, NULL);
//通知GLFW将window的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(windowId);
//展示当前的窗口
glfwShowWindow(windowId);
//获取当前设备的一些属性
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
//设置窗口的位置在最中间
glfwSetWindowPos(windowId, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2);
//创建opengl上下文
GL.createCapabilities();
}
//获取窗口Id
public long getWindowId() {
return windowId;
}
//循环时的判断条件
public boolean windowShouldClose() {
return glfwWindowShouldClose(windowId);
}
public void swapBuffers(){
glfwSwapBuffers(windowId);
glfwPollEvents();
}
}
ShaderProgram类
public class ShaderProgram {
private int programId;
public ShaderProgram() {
createProgram();
}
//创建一个着色器程序
public void createProgram(){
programId = glCreateProgram();
}
/**
* 创建并绑定相应的着色器程序
* @param shaderType:着色器类型(顶点着色器GL_VERTEX_SHADER | 片段着色器 GL_FRAGMENT_SHADER)
* @param shaderPath
* @throws Exception
*/
public void createShader(int shaderType,String shaderPath) throws Exception {
int vertexShaderId = glCreateShader(shaderType);
glShaderSource(vertexShaderId, loadResource(shaderPath));
glCompileShader(vertexShaderId);
glAttachShader(programId, vertexShaderId);
}
/**
* 链接着色器程序,也可以理解成将着色器激活
*/
public void linkShader(){
glLinkProgram(programId);
}
/**
* 加载着色器文件
* @param fileName:文件路径
* @return : 返回着色器代码的字符串形式
* @throws Exception
*/
private static String loadResource(String fileName) throws Exception {
String result;
try (InputStream in = Class.forName(Utils.class.getName()).getResourceAsStream(fileName);
Scanner scanner = new Scanner(in, "UTF-8")) {
result = scanner.useDelimiter("\\A").next();
}
return result;
}
public int getProgramId() {
return programId;
}
}
Model类
这个类中提到了顶点加载,以及VAO和VBO的概念。里面的相关API参考另外一篇文章OpenGL开发关于VAO和VBO的理解
public class Model {
private int vaoId;
private int vboId;
public Model() {
init();
}
//获取vao
public int getVaoId() {
return vaoId;
}
public void init(){
float[] vertices = new float[]{
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
FloatBuffer verticesBuffer = null;
try {
//将顶点数组封装至FloatBuffer中。
verticesBuffer = MemoryUtil.memAllocFloat(vertices.length);
verticesBuffer.put(vertices).flip();
//生成一个vao
vaoId = glGenVertexArrays();
//绑定该vao
glBindVertexArray(vaoId);
//创建一个vbo
vboId = glGenBuffers();
//声明类型的同时并绑定。
glBindBuffer(GL_ARRAY_BUFFER, vboId);
//将顶点数据放入vbo中。
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
//将顶点的数据格式告诉opengl,否则opengl不知道该如何去解析这些顶点数组。
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
//将vbo进行解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
//将vao解绑
glBindVertexArray(0);
} finally {
if (verticesBuffer != null) {
MemoryUtil.memFree(verticesBuffer);
}
}
}
}
Renderer类
public class Renderer {
public void render(Window window,int programId,List<Integer> vaoList){
while (!window.windowShouldClose()) {
//清除window
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//调用着色器程序
glUseProgram(programId);
for(int item : vaoList){
//绑定相应id的vao内存
glBindVertexArray(item);
//启用顶点属性
glEnableVertexAttribArray(0);
//开始绘制该顶点
glDrawArrays(GL_TRIANGLES, 0, 3);
//禁用顶点属性
glDisableVertexAttribArray(0);
//解绑vao
glBindVertexArray(0);
}
//调用着色器程序
glUseProgram(0);
//交换窗口的前后缓存帧
window.swapBuffers();
}
}
}
顶点着色器程序
#version 330
layout (location =0) in vec3 position;
void main()
{
gl_Position = vec4(position, 1.0);
}
片段着色器程序
#version 330
out vec4 fragColor;
void main()
{
fragColor = vec4(0.0, 0.5, 0.5, 1.0);
}
运行结果: