OpenGL開發利用lwjgl類庫繪製一個三角形
阿新 • • 發佈:2018-11-16
文章目錄
本文一共分為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);
}
執行結果: