OpenGL开发利用lwjgl类库绘制一个三角形

本文一共分为2部分
  1. 创建展示窗口。
  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);
}

运行结果:

在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
绘制平面空心圆,可以使用OpenGL的GL_LINES和GL_LINE_LOOP模式来绘制圆的边界线。其中,GL_LINES模式可以绘制两个点之间的线段,而GL_LINE_LOOP模式可以绘制连接所有点的闭合线段。 以下是一个简单的OpenGL3程序,它使用GL_LINE_LOOP模式绘制平面空心圆: ```java import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL33; import org.lwjgl.opengl.GLUtil; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import java.nio.FloatBuffer; public class CircleRenderer { private int vaoId; private int vboId; private int shaderProgramId; public CircleRenderer() { // 初始化OpenGL GL.createCapabilities(); GLUtil.setupDebugMessageCallback(); // 编译着色器程序 shaderProgramId = compileShaderProgram(); // 创建VAO和VBO vaoId = GL33.glGenVertexArrays(); vboId = GL33.glGenBuffers(); // 绑定VAO和VBO GL33.glBindVertexArray(vaoId); GL33.glBindBuffer(GL33.GL_ARRAY_BUFFER, vboId); // 设置顶点数据 float[] vertices = getCircleVertices(0.5f, 32); FloatBuffer vertexBuffer = MemoryUtil.memAllocFloat(vertices.length); vertexBuffer.put(vertices).flip(); GL33.glBufferData(GL33.GL_ARRAY_BUFFER, vertexBuffer, GL33.GL_STATIC_DRAW); GL33.glVertexAttribPointer(0, 2, GL33.GL_FLOAT, false, 0, 0); GL33.glEnableVertexAttribArray(0); // 解绑VAO和VBO GL33.glBindVertexArray(0); GL33.glBindBuffer(GL33.GL_ARRAY_BUFFER, 0); } public void render() { // 使用着色器程序 GL33.glUseProgram(shaderProgramId); // 绑定VAO GL33.glBindVertexArray(vaoId); // 绘制圆 GL33.glDrawArrays(GL33.GL_LINE_LOOP, 0, 33); // 解绑VAO GL33.glBindVertexArray(0); // 停用着色器程序 GL33.glUseProgram(0); } private int compileShaderProgram() { int vertexShaderId = GL33.glCreateShader(GL33.GL_VERTEX_SHADER); GL33.glShaderSource(vertexShaderId, "#version 330 core\n" + "layout (location = 0) in vec2 aPos;\n" + "void main() {\n" + " gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);\n" + "}\n"); GL33.glCompileShader(vertexShaderId); int fragmentShaderId = GL33.glCreateShader(GL33.GL_FRAGMENT_SHADER); GL33.glShaderSource(fragmentShaderId, "#version 330 core\n" + "out vec4 FragColor;\n" + "void main() {\n" + " FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" + "}\n"); GL33.glCompileShader(fragmentShaderId); int shaderProgramId = GL33.glCreateProgram(); GL33.glAttachShader(shaderProgramId, vertexShaderId); GL33.glAttachShader(shaderProgramId, fragmentShaderId); GL33.glLinkProgram(shaderProgramId); GL33.glDeleteShader(vertexShaderId); GL33.glDeleteShader(fragmentShaderId); return shaderProgramId; } private float[] getCircleVertices(float radius, int segments) { float[] vertices = new float[(segments + 1) * 2]; for (int i = 0; i <= segments; i++) { float angle = (float) (i * 2 * Math.PI / segments); vertices[i * 2] = radius * (float) Math.cos(angle); vertices[i * 2 + 1] = radius * (float) Math.sin(angle); } return vertices; } public void cleanup() { GL33.glDeleteProgram(shaderProgramId); GL33.glDeleteBuffers(vboId); GL33.glDeleteVertexArrays(vaoId); } } ``` 该程序使用GL_LINE_LOOP模式绘制32个点组成的圆。首先,它使用getCircleVertices方法生成圆的顶点坐标,然后将其存储在VBO中。接下来,它使用着色器程序和VAO绘制圆。 需要注意的是,该程序使用了LWJGL库来调用OpenGL函数。如果你不熟悉LWJGL,可以参考官方文档或其他教程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值