版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.01.20 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫(kù)丧肴,感覺還是很有意思,也就自然想著好好的總結(jié)一下胧后,希望對(duì)大家能有所幫助芋浮。下面內(nèi)容來自歡迎來到OpenGL的世界。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式壳快、對(duì)象纸巷、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器镇草、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫(kù)的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫(kù)的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫(kù)的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫(kù)的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫(kù)的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫(kù)的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫(kù)的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫(kù)的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫(kù)的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫(kù)的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫(kù)的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫(kù)的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫(kù)的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫(kù)的使用(二十一)—— 高級(jí)OpenGL之深度測(cè)試
22. OpenGL 圖形庫(kù)的使用(二十二)—— 高級(jí)OpenGL之模板測(cè)試Stencil testing
23. OpenGL 圖形庫(kù)的使用(二十三)—— 高級(jí)OpenGL之混合Blending
24. OpenGL 圖形庫(kù)的使用(二十四)—— 高級(jí)OpenGL之面剔除Face culling
25. OpenGL 圖形庫(kù)的使用(二十五)—— 高級(jí)OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫(kù)的使用(二十六)—— 高級(jí)OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫(kù)的使用(二十七)—— 高級(jí)OpenGL之高級(jí)數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫(kù)的使用(二十八)—— 高級(jí)OpenGL之高級(jí)GLSL Advanced GLSL
29. OpenGL 圖形庫(kù)的使用(二十九)—— 高級(jí)OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫(kù)的使用(三十)—— 高級(jí)OpenGL之實(shí)例化Instancing
31. OpenGL 圖形庫(kù)的使用(三十一)—— 高級(jí)OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫(kù)的使用(三十二)—— 高級(jí)光照之高級(jí)光照Advanced Lighting
33. OpenGL 圖形庫(kù)的使用(三十三)—— 高級(jí)光照之Gamma校正Gamma Correction
34. OpenGL 圖形庫(kù)的使用(三十四)—— 高級(jí)光照之陰影 - 陰影映射Shadow Mapping
35. OpenGL 圖形庫(kù)的使用(三十五)—— 高級(jí)光照之陰影 - 點(diǎn)陰影Point Shadows
36. OpenGL 圖形庫(kù)的使用(三十六)—— 高級(jí)光照之法線貼圖Normal Mapping
37. OpenGL 圖形庫(kù)的使用(三十七)—— 高級(jí)光照之視差貼圖Parallax Mapping
38. OpenGL 圖形庫(kù)的使用(三十八)—— 高級(jí)光照之HDR
39. OpenGL 圖形庫(kù)的使用(三十九)—— 高級(jí)光照之泛光
40. OpenGL 圖形庫(kù)的使用(四十)—— 高級(jí)光照之延遲著色法Deferred Shading
41. OpenGL 圖形庫(kù)的使用(四十一)—— 高級(jí)光照之SSAO
42. OpenGL 圖形庫(kù)的使用(四十二)—— PBR之理論Theory
43. OpenGL 圖形庫(kù)的使用(四十三)—— PBR之光照Lighting
44. OpenGL 圖形庫(kù)的使用(四十四)—— PBR之幾篇沒有翻譯的英文原稿
45. OpenGL 圖形庫(kù)的使用(四十五)—— 實(shí)戰(zhàn)之調(diào)試Debugging
46. OpenGL 圖形庫(kù)的使用(四十六)—— 實(shí)戰(zhàn)之文本渲染Text Rendering
47. OpenGL 圖形庫(kù)的使用(四十七)—— 實(shí)戰(zhàn)之2D游戲 - Breakout
48. OpenGL 圖形庫(kù)的使用(四十八)—— 實(shí)戰(zhàn)之2D游戲 - 準(zhǔn)備工作
渲染精靈
為了給我們當(dāng)前這個(gè)黑漆漆的游戲世界帶來一點(diǎn)生機(jī),我們將會(huì)渲染一些精靈(Sprite)來填補(bǔ)這些空虛瘤旨。精靈有很多種定義梯啤,但這里主要是指一個(gè)2D圖片,它通常是和一些擺放相關(guān)的屬性數(shù)據(jù)一起使用存哲,比如位置因宇、旋轉(zhuǎn)角度以及二維的大小。簡(jiǎn)單來說祟偷,精靈就是那些可以在2D游戲中渲染的圖像/紋理對(duì)象察滑。
我們可以像前面大多數(shù)教程里做的那樣,用頂點(diǎn)數(shù)據(jù)創(chuàng)建2D形狀肩袍,將所有數(shù)據(jù)傳進(jìn)GPU并手動(dòng)變換圖形杭棵。然而,在我們這樣的大型應(yīng)用中氛赐,我們最好是對(duì)2D形狀渲染做一些抽象化魂爪。如果我們要對(duì)每一個(gè)對(duì)象手動(dòng)定義形狀和變換的話,很快就會(huì)變得非常凌亂了艰管。
在這個(gè)教程中滓侍,我們將會(huì)定義一個(gè)渲染類,讓我們用最少的代碼渲染大量的精靈牲芋。這樣撩笆,我們就可以從散沙一樣的OpenGL渲染代碼中抽象出游戲代碼,這也是在一個(gè)大型工程中常用的做法缸浦。雖然我們首先還要去配置一個(gè)合適的投影矩陣夕冲。
2D投影矩陣
從這個(gè)坐標(biāo)系統(tǒng)教程中,我們明白了投影矩陣的作用是把觀察空間坐標(biāo)轉(zhuǎn)化為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)裂逐。通過生成合適的投影矩陣歹鱼,我們就可以在不同的坐標(biāo)系下計(jì)算,這可能比把所有的坐標(biāo)都指定為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)(再計(jì)算)要更容易處理卜高。
我們不需要對(duì)坐標(biāo)系應(yīng)用透視弥姻,因?yàn)檫@個(gè)游戲完全是2D的,所以一個(gè)正射投影矩陣(Orthographic Projection Matrix)
就可以了掺涛。由于正射投影矩陣幾乎直接變換所有的坐標(biāo)至裁剪空間庭敦,我們可以定義如下的投影矩陣指定世界坐標(biāo)為屏幕坐標(biāo):
glm::mat4 projection = glm::ortho(0.0f, 800.0f, 600.0f, 0.0f, -1.0f, 1.0f);
前面的四個(gè)參數(shù)依次指定了投影平截頭體的左、右薪缆、下秧廉、上邊界。這個(gè)投影矩陣把所有在0到800之間的x坐標(biāo)變換到-1到1之間,并把所有在0到600之間的y坐標(biāo)變換到-1到1之間定血。這里我們指定了平截頭體頂部的y坐標(biāo)值為0赔癌,底部的y坐標(biāo)值為600诞外。所以澜沟,這個(gè)場(chǎng)景的左上角坐標(biāo)為(0,0),右下角坐標(biāo)為(800,600)峡谊,就像屏幕坐標(biāo)那樣茫虽。觀察空間坐標(biāo)直接對(duì)應(yīng)最終像素的坐標(biāo)。
這樣我們就可以指定所有的頂點(diǎn)坐標(biāo)為屏幕上的像素坐標(biāo)了既们,這對(duì)2D游戲來說相當(dāng)直觀濒析。
渲染精靈
渲染一個(gè)實(shí)際的精靈應(yīng)該不會(huì)太復(fù)雜。我們創(chuàng)建一個(gè)有紋理的四邊形啥纸,它在之后可以使用一個(gè)模型矩陣來變換号杏,然后我們會(huì)用之前定義的正射投影矩陣來投影它。
由于Breakout是一個(gè)靜態(tài)的游戲斯棒,這里不需要觀察/攝像機(jī)矩陣盾致,我們可以直接使用投影矩陣把世界空間坐標(biāo)變換到裁剪空間坐標(biāo)。
為了變換精靈我們會(huì)使用下面這個(gè)頂點(diǎn)著色器:
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords>
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 projection;
void main()
{
TexCoords = vertex.zw;
gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0);
}
注意荣暮,我們僅用了一個(gè)vec4變量來存儲(chǔ)位置和紋理坐標(biāo)數(shù)據(jù)庭惜。因?yàn)槲恢煤图y理坐標(biāo)數(shù)據(jù)都只包含了兩個(gè)float,所以我們可以把他們組合在一起作為一個(gè)單一的頂點(diǎn)屬性穗酥。
片段著色器也比較直觀护赊。我們會(huì)在這里獲取一個(gè)紋理和一個(gè)顏色向量,它們都會(huì)影響片段的最終顏色砾跃。我們?cè)O(shè)置了一個(gè)紋理和顏色向量骏啰,她們兩個(gè)都會(huì)對(duì)像素最后的顏色產(chǎn)生影響。有了這個(gè)uniform顏色向量抽高,我們就可以很方便地在游戲代碼中改變精靈的顏色了判耕。
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D image;
uniform vec3 spriteColor;
void main()
{
color = vec4(spriteColor, 1.0) * texture(image, TexCoords);
}
為了讓精靈的渲染更加有條理,我們定義了一個(gè)SpriteRenderer
類厨内,有了它只需要一個(gè)函數(shù)就可以渲染精靈了祈秕。它的定義如下:
class SpriteRenderer
{
public:
SpriteRenderer(Shader &shader);
~SpriteRenderer();
void DrawSprite(Texture2D &texture, glm::vec2 position,
glm::vec2 size = glm::vec2(10, 10), GLfloat rotate = 0.0f,
glm::vec3 color = glm::vec3(1.0f));
private:
Shader shader;
GLuint quadVAO;
void initRenderData();
};
SpriteRenderer
類封裝了一個(gè)著色器對(duì)象,一個(gè)頂點(diǎn)數(shù)組對(duì)象以及一個(gè)渲染和初始化函數(shù)雏胃。它的構(gòu)造函數(shù)接受一個(gè)著色器對(duì)象用于之后的渲染请毛。
1. 初始化
首先,讓我們深入研究一下負(fù)責(zé)配置quadVAO
的initRenderData
函數(shù):
void SpriteRenderer::initRenderData()
{
// 配置 VAO/VBO
GLuint VBO;
GLfloat vertices[] = {
// 位置 // 紋理
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f
};
glGenVertexArrays(1, &this->quadVAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindVertexArray(this->quadVAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
這里瞭亮,我們首先定義了一組以四邊形的左上角為(0,0)坐標(biāo)的頂點(diǎn)方仿。這意味著當(dāng)我們?cè)谒倪呅紊蠎?yīng)用一個(gè)位移或縮放變換的時(shí)候,它們會(huì)從四邊形的左上角開始進(jìn)行變換。這在2D圖形以及/或GUI系統(tǒng)中廣為接受仙蚜,元素的位置定義為元素左上角的位置此洲。
接下來我們簡(jiǎn)單地向GPU傳遞頂點(diǎn)數(shù)據(jù),并且配置頂點(diǎn)屬性委粉,當(dāng)然在這里僅有一個(gè)頂點(diǎn)屬性呜师。因?yàn)樗械木`共享著同樣的頂點(diǎn)數(shù)據(jù),我們只需要為這個(gè)精靈渲染器定義一個(gè)VAO就行了贾节。
2. 渲染
渲染精靈并不是太難汁汗;我們使用精靈渲染器的著色器,配置一個(gè)模型矩陣并且設(shè)置相關(guān)的uniform栗涂。這里最重要的就是變換的順序:
void SpriteRenderer::DrawSprite(Texture2D &texture, glm::vec2 position,
glm::vec2 size, GLfloat rotate, glm::vec3 color)
{
// 準(zhǔn)備變換
this->shader.Use();
glm::mat4 model;
model = glm::translate(model, glm::vec3(position, 0.0f));
model = glm::translate(model, glm::vec3(0.5f * size.x, 0.5f * size.y, 0.0f));
model = glm::rotate(model, rotate, glm::vec3(0.0f, 0.0f, 1.0f));
model = glm::translate(model, glm::vec3(-0.5f * size.x, -0.5f * size.y, 0.0f));
model = glm::scale(model, glm::vec3(size, 1.0f));
this->shader.SetMatrix4("model", model);
this->shader.SetVector3f("spriteColor", color);
glActiveTexture(GL_TEXTURE0);
texture.Bind();
glBindVertexArray(this->quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
當(dāng)試圖在一個(gè)場(chǎng)景中用旋轉(zhuǎn)矩陣和縮放矩陣放置一個(gè)對(duì)象的時(shí)候知牌,建議是首先做縮放變換,再旋轉(zhuǎn)斤程,最后才是位移變換角寸。因?yàn)榫仃嚦朔ㄊ菑挠蚁蜃髨?zhí)行的,所以我們變換的矩陣順序是相反的:移動(dòng)忿墅,旋轉(zhuǎn)扁藕,縮放。
旋轉(zhuǎn)變換可能看起來稍微有點(diǎn)讓人望而卻步球匕。我們從變換教程里面知道旋轉(zhuǎn)總是圍繞原點(diǎn)(0,0)轉(zhuǎn)動(dòng)的纹磺。因?yàn)槲覀冎付怂倪呅蔚淖笊辖菫?0,0),所有的旋轉(zhuǎn)都會(huì)圍繞這個(gè)(0,0)亮曹。簡(jiǎn)單來說橄杨,在四邊形左上角的旋轉(zhuǎn)原點(diǎn)(Origin of Rotation)
會(huì)產(chǎn)生不想要的結(jié)果。我們想要做的是把旋轉(zhuǎn)原點(diǎn)移到四邊形的中心照卦,這樣旋轉(zhuǎn)就會(huì)圍繞四邊形中心而不是左上角了式矫。我們會(huì)在旋轉(zhuǎn)之前把旋轉(zhuǎn)原點(diǎn)移動(dòng)到四邊形中心來解決這個(gè)問題。
因?yàn)槲覀兪紫葧?huì)縮放這個(gè)四邊形役耕,我們?cè)谖灰凭`的中心時(shí)還需要把精靈的大小考慮進(jìn)來(這也是為什么我們乘以了精靈的size向量)采转。在旋轉(zhuǎn)變換應(yīng)用之后,我們會(huì)反轉(zhuǎn)之前的平移操作瞬痘。
把所有變換組合起來我們就能以任何想要的方式放置故慈、縮放并平移每個(gè)精靈了。下面你可以找到精靈渲染器完整的源代碼:
/*******************************************************************
** This code is part of Breakout.
**
** Breakout is free software: you can redistribute it and/or modify
** it under the terms of the CC BY 4.0 license as published by
** Creative Commons, either version 4 of the License, or (at your
** option) any later version.
******************************************************************/
#include "sprite_renderer.h"
SpriteRenderer::SpriteRenderer(Shader &shader)
{
this->shader = shader;
this->initRenderData();
}
SpriteRenderer::~SpriteRenderer()
{
glDeleteVertexArrays(1, &this->quadVAO);
}
void SpriteRenderer::DrawSprite(Texture2D &texture, glm::vec2 position, glm::vec2 size, GLfloat rotate, glm::vec3 color)
{
// Prepare transformations
this->shader.Use();
glm::mat4 model;
model = glm::translate(model, glm::vec3(position, 0.0f)); // First translate (transformations are: scale happens first, then rotation and then finall translation happens; reversed order)
model = glm::translate(model, glm::vec3(0.5f * size.x, 0.5f * size.y, 0.0f)); // Move origin of rotation to center of quad
model = glm::rotate(model, rotate, glm::vec3(0.0f, 0.0f, 1.0f)); // Then rotate
model = glm::translate(model, glm::vec3(-0.5f * size.x, -0.5f * size.y, 0.0f)); // Move origin back
model = glm::scale(model, glm::vec3(size, 1.0f)); // Last scale
this->shader.SetMatrix4("model", model);
// Render textured quad
this->shader.SetVector3f("spriteColor", color);
glActiveTexture(GL_TEXTURE0);
texture.Bind();
glBindVertexArray(this->quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
void SpriteRenderer::initRenderData()
{
// Configure VAO/VBO
GLuint VBO;
GLfloat vertices[] = {
// Pos // Tex
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f
};
glGenVertexArrays(1, &this->quadVAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindVertexArray(this->quadVAO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
你好察绷,精靈
有了SpriteRenderer
類,我們終于能夠渲染實(shí)際的圖像到屏幕上了津辩!讓我們來在游戲代碼里面初始化一個(gè)精靈并且加載我們最喜愛的紋理:
SpriteRenderer *Renderer;
void Game::Init()
{
// 加載著色器
ResourceManager::LoadShader("shaders/sprite.vs", "shaders/sprite.frag", nullptr, "sprite");
// 配置著色器
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(this->Width),
static_cast<GLfloat>(this->Height), 0.0f, -1.0f, 1.0f);
ResourceManager::GetShader("sprite").Use().SetInteger("image", 0);
ResourceManager::GetShader("sprite").SetMatrix4("projection", projection);
// 設(shè)置專用于渲染的控制
Renderer = new SpriteRenderer(ResourceManager::GetShader("sprite"));
// 加載紋理
ResourceManager::LoadTexture("textures/awesomeface.png", GL_TRUE, "face");
}
然后拆撼,在渲染函數(shù)里面我們就可以渲染一下我們心愛的吉祥物來檢測(cè)是否一切都正常工作了:
void Game::Render()
{
Renderer->DrawSprite(ResourceManager::GetTexture("face"),
glm::vec2(200, 200), glm::vec2(300, 400), 45.0f, glm::vec3(0.0f, 1.0f, 0.0f));
}
這里我們把精靈放置在靠近屏幕中心的位置容劳,它的高度會(huì)比寬度大一點(diǎn)。我們同樣也把它旋轉(zhuǎn)了45度并把它設(shè)置為綠色闸度。注意竭贩,我們?cè)O(shè)定的精靈的位置是精靈四邊形左上角的位置。
如果你一切都做對(duì)了你應(yīng)該可以看到下面的結(jié)果:
你可以在這里找到更新后的游戲類源代碼莺禁。
/*******************************************************************
** This code is part of Breakout.
**
** Breakout is free software: you can redistribute it and/or modify
** it under the terms of the CC BY 4.0 license as published by
** Creative Commons, either version 4 of the License, or (at your
** option) any later version.
******************************************************************/
#include "game.h"
#include "resource_manager.h"
#include "sprite_renderer.h"
// Game-related State data
SpriteRenderer *Renderer;
Game::Game(GLuint width, GLuint height)
: State(GAME_ACTIVE), Keys(), Width(width), Height(height)
{
}
Game::~Game()
{
delete Renderer;
}
void Game::Init()
{
// Load shaders
ResourceManager::LoadShader("shaders/sprite.vs", "shaders/sprite.frag", nullptr, "sprite");
// Configure shaders
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(this->Width), static_cast<GLfloat>(this->Height), 0.0f, -1.0f, 1.0f);
ResourceManager::GetShader("sprite").Use().SetInteger("image", 0);
ResourceManager::GetShader("sprite").SetMatrix4("projection", projection);
// Load textures
ResourceManager::LoadTexture("textures/awesomeface.png", GL_TRUE, "face");
// Set render-specific controls
Renderer = new SpriteRenderer(ResourceManager::GetShader("sprite"));
}
void Game::Update(GLfloat dt)
{
}
void Game::ProcessInput(GLfloat dt)
{
}
void Game::Render()
{
Renderer->DrawSprite(ResourceManager::GetTexture("face"), glm::vec2(200, 200), glm::vec2(300, 400), 45.0f, glm::vec3(0.0f, 1.0f, 0.0f));
}
現(xiàn)在我們已經(jīng)讓渲染系統(tǒng)正常工作了留量,我們可以在下一節(jié)教程中用它來構(gòu)建游戲的關(guān)卡。
后記
本篇已結(jié)束睁宰,后面更精彩~~~