1. 程式人生 > 其它 >OpenGL新手教程 學習筆記

OpenGL新手教程 學習筆記

OpenGL學習——基於QOpenGL

1. 基本知識

# VAO 頂點陣列物件(記錄頂點狀態資訊,無法記錄紋理狀態)
# VBO 頂點緩衝物件
# EBO 索引緩衝物件

# VAO中記錄了 : 
# 1. vertex attribute 的格式,由 glVertexAttribPointer 設定
# 2. vertex attribute 對應的 VBO 的名字, 由一對 glBindBuffer 和 	    	   glVertexAttribPointer 設定
# 3. #當前#繫結的 GL_ELEMENT_ARRAY_BUFFER 的名字,由 glBindBuffer 設定

#-----圖形渲染管線-----
# 頂點資料——>頂點著色器——>(圖元裝配)——>幾何著色器(可選)——>(光柵化)——>片段著色器
# ——>(測試與混合)

# OpenGL僅當3D座標在(-1.0——1.0)範圍內時才會處理,即在標準化裝置座標範圍內的座標才會   最終渲染在螢幕上

# OpenGL原生函式著色器編譯部分略顯繁瑣,這裡直接採用Qt封裝好的著色器類來進行著色器的編   譯。

# -------著色器程式------
# 著色器程式物件是多個著色器連結完成後的最終版本,即(頂點著色器+幾何著色器(可選)+片段著   色器)
# 在渲染物件時需要啟用著色器程式

2. OpenGL常用函式

//開啟深度測試 z緩衝
glEnable(GL_DEPTH_TEST);
//如果啟用了深度緩衝,還應該在每個渲染迭代之前使用GL_DEPTH_BUFFER_BIT來清除深度緩衝,否則會仍在使用上一次渲染迭代中的寫入的深度值

//建立VAO並繫結 用於儲存之後設定的所有狀態 下次繪製就直接啟用VAO就行
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

//建立緩衝區
glGenBuffers(); // --- 只有在bind後才會真正建立Object
glCreateBuffers(); // --- 建立即生成Object

//將緩衝區繫結到相應目標型別,即設定緩衝區型別,以後對該目標操作即對繫結到該目標的物件生效
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 繫結緩衝時附帶binding目標點
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); 
// 繫結部分緩衝資料至bindding目標點
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);

//將頂點資料存入指定目標型別的緩衝區
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW) // 給指定目標的緩衝區新增資料
glNamedBufferData(GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); //直接給指定的緩衝區新增資料,緩衝區可以不繫結到目標
glNamedBufferStorage(GLuint buffer, GLsizeiptr size, const void *data, GLenum flag); // *Storage()不能對同一個buffer做兩次。 *Data()可以。 *Storage()最後一個引數flag有GL_DYNAMIC_STORAGE_BIT則允許後續 *subdata()更新資料。

//設定如何解析頂點資料
//  1.對應著色器中location 頂點資料將傳到該位置
//  2.屬性大小 vec3 or vec4
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0)
//設定啟用頂點屬性 
glEnableVertexAttribArray(0);

//清除顏色緩衝和深度緩衝
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//建立幀緩衝
unsigned int fbo;
glGenFramebuffers(1, &fbo);

//繫結為啟用的幀緩衝
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

//定義了一個緩衝區陣列,片段著色器資料的輸出將寫入其中
GLenum buffer[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, &buffer);
    
    

3. QOpenLGShaderProgram

//------該類可免去自己封裝著色器類的過程------
//新增著色器
addShaderFromSourceFile()

//連結著色器
link()
    
//使用著色器程式物件
bind()

//設定著色器中 uniform 的值
setUniformValue()
    
//不過還是建議自己封裝一個著色器類,就用QOpenGLShaderProgram進行二次封裝,以應對各種其他情況

4. GLSL

// uniform 變數在著色器中全域性使用
//-----設定uniform變數值-----
//原生OpenGL函式
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); 
//QOpenGLShaderProgram封裝
shderProgram->setUniformValue("uniformName",value)
    
//------------------紋理相關-----------------------
//內建函式 texture
texture(ourTexture1,TexCoord) //紋理取樣
//內建函式 mix
mix(texture(),texture(),0.2)  //按照0.8texture1+0.2texture2取樣
    
//------------------深度相關-----------------------
gl_FragCoord //x和y分量代表片段的螢幕空間座標 z分量代表片段真正深度值
    
//丟棄片段
discard;

//紋理變數
sampler2D  ,  samplerCube
    

5. Textures 紋理

// 在Qt中紋理圖片的讀取 可以用QImage讀取,資料傳入採用 img.bits()

// 2D紋理座標在x和y軸上,範圍為0到1之間 ( >1則預設重複紋理影象)

//--1.生成紋理引用
unsigned int texture;
glGenTextures(1, &texture);
//--2.繫結到指定紋理型別目標,對該目標操作即可以對繫結到該目標上的物件生效
glBindTexture(GL_TEXTURE_2D, texture);
//--3.傳入圖片資料生成紋理
// 第一個引數指定了紋理目標(Target)。設定為GL_TEXTURE_2D意味著會生成與當前繫結的紋理物件在同一個目標上的紋理(任何繫結到GL_TEXTURE_1D和GL_TEXTURE_3D的紋理不會受到影響)。
// 第二個引數為紋理指定多級漸遠紋理的級別,如果你希望單獨手動設定每個多級漸遠紋理的級別的話。這裡我們填0,也就是基本級別。
// 第三個引數告訴OpenGL我們希望把紋理儲存為何種格式。我們的影象只有RGB值,因此我們也把紋理儲存為RGB值。
// 第四個和第五個引數設定最終的紋理的寬度和高度。我們之前載入影象的時候儲存了它們,所以我們使用對應的變數。
// 下個引數應該總是被設為0(歷史遺留的問題)。
// 第七第八個引數定義了源圖的格式和資料型別。我們使用RGB值載入這個影象,並把它們儲存為char(byte)陣列,我們將會傳入對應值。
// 最後一個引數是真正的影象資料。
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

5.1 紋理環繞方式

# 當紋理座標設定在(0,0) (1,1)之外時,紋理重複方式
環繞方式 描述
GL_REPEAT 對紋理的預設行為。重複紋理影象。
GL_MIRRORED_REPEAT 和GL_REPEAT一樣,但每次重複圖片是映象放置的。
GL_CLAMP_TO_EDGE 紋理座標會被約束在0到1之間,超出的部分會重複紋理座標的邊緣,產生一種邊緣被拉伸的效果。
GL_CLAMP_TO_BORDER 超出的座標為使用者指定的邊緣顏色。
//使用glTexParameter*函式對單獨的一個座標軸(s t r)設定
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT)

5.2 紋理過濾

// 設定紋理的取樣方式

// GL_NEAREST 鄰近過濾
// GL_LINEAR  線性過濾

// 當進行放大(Magnify)和縮小(Minify)操作的時候可以設定紋理過濾的選項
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

5.3 多級漸進紋理

過濾方式 描述
GL_NEAREST_MIPMAP_NEAREST 使用最鄰近的多級漸遠紋理來匹配畫素大小,並使用鄰近插值進行紋理取樣
GL_LINEAR_MIPMAP_NEAREST 使用最鄰近的多級漸遠紋理級別,並使用線性插值進行取樣
GL_NEAREST_MIPMAP_LINEAR 在兩個最匹配畫素大小的多級漸遠紋理之間進行線性插值,使用鄰近插值進行取樣
GL_LINEAR_MIPMAP_LINEAR 在兩個鄰近的多級漸遠紋理之間使用線性插值,並使用線性插值進行取樣
//自動建立多級漸進紋理
glGenerateMipmaps(GL_TEXTURE_2D);
    
//設定紋理過濾方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

5.4 紋理單元

//---若只有一個紋理 則預設啟用GL_TEXTURE0,
//   glBindTexture預設對應GL_TEXTURE0
//---對於多紋理使用,必須:
//  1.然後定義哪個uniform取樣器對應哪個紋理單元.
shaderProgram->setuniformValue("texture1",0)
shaderProgram->setuniformValue("texture2",1)
//  2.先繫結紋理物件到對應的紋理單元.
glActivateTexture(GL_TEXTURE0)//啟用紋理單元
glBindTexture(GL_TEXTURE_2D,texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);

6. 座標

6.1 座標變換

// 專案中採用Eigen庫進行矩陣操作
Eigen::Translation3f translation;//平移 本質是一個3x1的矩陣
Eigen::AngleAxisf rotation;//旋轉
rotation = Eigen::AngleAxisf(angle, Eigen::Vector3f::UnitZ());//設定旋轉角和旋轉軸
Eigen::Isometry3f trans;//歐式變換矩陣

// 1.Eigen: 列主序 OpenGL mat: 列主序
// 2.Eigen Matrix 轉 QMatrix
Eigen::Matrix4f mat;
QMatrix4x4(mat.template cast<float>().data(), 4, 4)

//傳入uniform 矩陣變數
shaderProgram->setUniformValue("transform", QMatrix4x4(transform.template cast<float>().data()));

transform.data() //矩陣首地址   

6.2 座標系統

# 模型矩陣 觀察矩陣 投影矩陣 
# 具體見camera.h和camera.cpp檔案

#在本程式Camera原理中,左右上下平移本質為改變Camera位置,旋轉採用四元數通過改變觀察矩陣(LookAt)實現

7. Lighting 光照

# 馮氏光照模型(Phong)
# 主要由三個分量組成: 1.環境光照 2.漫反射光照 3.鏡面光照

# 漫反射光照計算 理論上是在世界座標中進行,利用法向量和光線方向,計算得到頂點所對應的顏色.
# 鏡面反射計算 由於本程式Camera原理,應該在觀察空間下進行,所有相關向量資訊均需要乘以觀察   矩陣轉換到觀察空間下.

7.1 材質

//物體材質資訊
struct Material{
    vec3 ambient;//環境光照下物體反射的顏色,通常和物體本身顏色一致
    vec3 diffuse;//漫反射光照下物體的顏色,通常也為物體本身顏色
    vec3 specular;//鏡面光照對物體的顏色影響,類似於specularstrength
    float shininess;//反光度
};
uniform Material material;
    
struct Light{
    vec3 position;

    vec3 ambient;//環境光
    vec3 diffuse;//漫反射光
    vec3 specular;//鏡面反射光
};
uniform Light light;

void main()
{
    //環境光照
    vec3 ambient = material.ambient * light.ambient;

    //漫反射光照計算 在世界座標下進行
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);//光線方向
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = (diff * material.diffuse) * light.diffuse;

    //鏡面反射 在觀察空間下進行
    vec3 viewDir = normalize(-vec3(view * vec4(FragPos, 1.0)));
    vec3 reflectDir = reflect(-vec3(normalize(view*vec4(lightPos,1.0) - 	view*vec4(FragPos,1.0))), mat3(transpose(inverse(view))) * Normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 					material.shininess);
    vec3 specular = (material.specular * spec) * light.specular;

    vec3 result = (ambient + diffuse + specular);
    FragColor = vec4(result, 1.0);
}

7.2 光照貼圖

//漫反射貼圖

//鏡面光貼圖

//光源資訊
struct Light
{
    vec3 position;

    vec3 ambient;//環境光
    vec3 diffuse;//漫反射光
    vec3 specular;//鏡面反射光
};
uniform Light light;

//材質貼圖---漫反射貼圖 鏡面光貼圖
struct Material
{
    sampler2D diffuse;//漫反射貼圖
    sampler2D specular;//鏡面光貼圖
    float shininess;
};
uniform Material material;

7.3 投光物

7.4 多光源

# 目前不是特別重要 以後有需要再學...

8. 模型載入

# 這個可以先放一放 不是特別重要

9. 高階OpenGL

9.1 深度測試

//開啟深度測試 z緩衝
glEnable(GL_DEPTH_TEST);
//如果啟用了深度緩衝,還應該在每個渲染迭代之前使用GL_DEPTH_BUFFER_BIT來清除深度緩衝,否則會仍在使用上一次渲染迭代中的寫入的深度值

//清除顏色緩衝和深度緩衝
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//禁用深度緩衝寫入 即 使用一個只讀的深度緩衝 
//***只在深度測試被啟用的時候才有效果
glDepthMask(GL_FALSE);

//深度測試函式 
//即 通過修改比較運算子, 讓我們來決定什麼時候該通過或者丟棄一個片段
glDepthFunc(GL_LESS);
函式 描述
GL_ALWAYS 永遠通過深度測試
GL_NEVER 永遠不通過深度測試
GL_LESS 在片段深度值小於緩衝的深度值時通過測試
GL_EQUAL 在片段深度值等於緩衝區的深度值時通過測試
GL_LEQUAL 在片段深度值小於等於緩衝區的深度值時通過測試
GL_GREATER 在片段深度值大於緩衝區的深度值時通過測試
GL_NOTEQUAL 在片段深度值不等於緩衝區的深度值時通過測試
GL_GEQUAL 在片段深度值大於等於緩衝區的深度值時通過測試

9.2 模板測試

//其本質原理類比於模板操作
//通過設定一個viewport的模板值,即模板緩衝,基於一定的規則 來決定緩衝對應的片段是否應該被繪製.以及模板測試時緩衝的內容應該如何更新.

//模板緩衝操作允許我們在渲染片段時將模板緩衝設定為一個特定的值。通過在渲染時修改模板緩衝的內容,我們寫入了模板緩衝。在同一個(或者接下來的)渲染迭代中,我們可以讀取這些值,來決定丟棄還是保留某個片段。

//啟用模板測試
glEnable(GL_STENCIL_TEST);

//每次渲染迭代前 清除模板緩衝
glClear(GL_STENCIL_BUFFER_BIT);

glStencilMask(0xFF); // 每一位寫入模板緩衝時都保持原樣(正常寫入)
glStencilMask(0x00); // 每一位在寫入模板緩衝時都會變成0(禁用寫入)

//--------------------模板函式------------------------
// ---該函式決定了以何種規則進行比較,以確定模板測試是否通過
glStencilFunc(GLenum func, GLint ref, GLuint mask);
// 1.func:設定模板測試函式(Stencil Test Function)。這個測試函式將會應用到已儲存的模板值上和glStencilFunc函式的ref值上。可用的選項有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它們的語義和深度緩衝的函式類似。
// 2.ref:設定了模板測試的參考值(Reference Value)。模板緩衝的內容將會與這個值進行比較。
// 3.mask:設定一個掩碼,它將會與參考值和儲存的模板值在測試比較它們之前進行與(AND)運算。初始情況下所有位都為1

// ---該函式決定了模板測試通過或失敗時該如何更新模板緩衝值 
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
// 1.sfail:模板測試失敗時採取的行為。
// 2.dpfail:模板測試通過,但深度測試失敗時採取的行為。
// 3.dppass:模板測試和深度測試都通過時採取的行為。
// *行為及其描述見下表:
// #預設情況下glStencilOp是設定為(GL_KEEP, GL_KEEP, GL_KEEP)的,所以不論任何測試的結果是如何,模板緩衝都會保留它的值
行為 描述
GL_KEEP 保持當前儲存的模板值
GL_ZERO 將模板值設定為0
GL_REPLACE 將模板值設定為glStencilFunc函式設定的ref
GL_INCR 如果模板值小於最大值則將模板值加1
GL_INCR_WRAP 與GL_INCR一樣,但如果模板值超過了最大值則歸零
GL_DECR 如果模板值大於最小值則將模板值減1
GL_DECR_WRAP 與GL_DECR一樣,但如果模板值小於0則將其設定為最大值
GL_INVERT 按位翻轉當前的模板緩衝值

9.3 混合

// ***用於渲染多個透明度級別的紋理影象
//啟用混合
glEnable(GL_BLEND);

//混合函式 ---設定源和目標因子值
glBlendFunc(GLenum sfactor, GLenum dfactor)
選項
GL_ZERO 因子等於0
GL_ONE 因子等於1
GL_SRC_COLOR 因子等於源顏色向量C¯source
GL_ONE_MINUS_SRC_COLOR 因子等於1−C¯source
GL_DST_COLOR 因子等於目標顏色向量C¯destination
GL_ONE_MINUS_DST_COLOR 因子等於1−C¯destination
GL_SRC_ALPHA 因子等於C¯sourceC¯source的alphaalpha分量
GL_ONE_MINUS_SRC_ALPHA 因子等於1−1− C¯sourceC¯source的alphaalpha分量
GL_DST_ALPHA 因子等於C¯destinationC¯destination的alphaalpha分量
GL_ONE_MINUS_DST_ALPHA 因子等於1−1− C¯destinationC¯destination的alphaalpha分量
GL_CONSTANT_COLOR 因子等於常數顏色向量C¯constant
GL_ONE_MINUS_CONSTANT_COLOR 因子等於1−C¯constant
GL_CONSTANT_ALPHA 因子等於C¯constantC¯constant的alphaalpha分量
GL_ONE_MINUS_CONSTANT_ALPHA 因子等於1−1− C¯constantC¯constant的alphaalpha分量

9.4 面剔除

面剔除含義: 渲染正向面 , 丟棄背向面

//啟用面剔除
glEnable(GL_CULL_FACE);

//設定需要剔除的面型別
glCullFace(GL_FRONT);
// GL_BACK:只剔除背向面。
// GL_FRONT:只剔除正向面。
// GL_FRONT_AND_BACK:剔除正向面和背向面。

//設定正向面的定義
glFrontFace(GL_CCW);
// 逆時針 --GL_CCW
// 順時針 --GL_CW

9.5 幀緩衝(重要!!!) -------離屏渲染

// 幀緩衝 = 顏色緩衝 + 深度緩衝 + 模板緩衝
//glfw 或者 QOpenGLWidget在建立視窗的時候會生成預設的幀緩衝.=螢幕

//**** QOpenGL預設螢幕幀緩衝與原生OpenGL不同 
//     原生OpenGL預設為0; 
//     QOpenGL為defaultFrameBufferObject()

// 其本質是經過離屏渲染後將場景作為紋理繪製到預設幀緩衝(螢幕)上.

//建立幀緩衝
unsigned int fbo;
glGenFramebuffers(1, &fbo);
//繫結為啟用的幀緩衝
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

//也可以使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER,將一個幀緩衝分別繫結到讀取目標或寫入目標。
// 繫結到GL_READ_FRAMEBUFFER的幀緩衝將會使用在所有像是glReadPixels的讀取操作中;
// 而繫結到GL_DRAW_FRAMEBUFFER的幀緩衝將會被用作渲染、清除等寫入操作的目標
    
//---------------------------------------***一個完整的幀緩衝需要滿足以下的條件:-------------------------------------------------
//  1.附加至少一個緩衝(顏色、深度或模板緩衝)。
//  2.至少有一個顏色附件(Attachment)。
//  3.所有的附件都必須是完整的(保留了記憶體)。
//  4.每個緩衝都應該有相同的樣本數。

// 檢查幀緩衝是否完整
glCheckFramebufferStatus(GL_FRAMEBUFFER)

//***通常的規則是,如果你不需要從一個緩衝中取樣資料,那麼對這個緩衝使用渲染緩衝物件會是明智的選擇。如果你需要從緩衝中取樣顏色或深度值等資料,那麼你應該選擇紋理附件.   
 
//---------------------------------------------1. 紋理附件-----------------------------------------------
// 即 建立一個紋理 並附加到幀緩衝
// 建立一個紋理
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
// 將上述建立的紋理 附加到幀緩衝
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
// glFrameBufferTexture2D有以下的引數:
// 1.target:幀緩衝的目標(繪製、讀取或者兩者皆有)
// 2.attachment:我們想要附加的附件型別。當前我們正在附加一個顏色附件。注意最後的0意味著我們可以附加多個顏色附件。
// 3.textarget:你希望附加的紋理型別
// 4.texture:要附加的紋理本身
// 5.level:多級漸遠紋理的級別。我們將它保留為0。

//***也可以將深度和模板緩衝附加為一個單獨的紋理到幀緩衝,可以單獨分別附加,也可以合併為一個紋理進行附加.
GL_DEPTH_COMPONENT
GL_STENCIL_ATTACHMENT
GL_DEPTH_STENCIL_ATTACHMENT
//------------------------------------------2. 渲染緩衝物件附件-------------------------------------------
//*** 渲染緩衝物件是隻寫的 ***
//建立渲染緩衝物件 
unsigned int rbo;
glGenRenderbuffers(1, &rbo);

//繫結 讓之後所有的渲染緩衝操作影響當前的rbo
glBindRenderbuffer(GL_RENDERBUFFER, rbo);

//建立一個深度和模板渲染緩衝物件
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);

//附加渲染緩衝物件到幀緩衝
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

// ========================================== 相關函式 ==================================================
// 直接複製幀緩衝 到螢幕緩衝 不需要重新繫結螢幕幀緩衝 即可顯示圖形
glBlitNamedFramebuffer(fboEDL->fbo,defaultFramebufferObject(),
		0, 0, fboEDL->width, fboEDL->height,
		0, 0, 1536, 864,
		GL_COLOR_BUFFER_BIT, GL_LINEAR);

9.6 立方體貼圖

//  應用 —— 天空盒

//建立立方體貼圖紋理
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
紋理目標 方位
GL_TEXTURE_CUBE_MAP_POSITIVE_X
GL_TEXTURE_CUBE_MAP_NEGATIVE_X
GL_TEXTURE_CUBE_MAP_POSITIVE_Y
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
GL_TEXTURE_CUBE_MAP_POSITIVE_Z
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
//紋理目標其背後對應int值依次遞增
//設定立方體貼圖每個面紋理資料
i = 0:1:5;
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

//立方體貼圖紋理取樣器
// --取樣時需要給定一個方向向量
uniform samplerCube cubemap;

9.7 高階資料

//插入或更新部分緩衝記憶體  ----必須先呼叫glBufferData
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // 範圍: [24, 24 + sizeof(data)]
//*** 分塊讀取頂點屬性 ***
// 填充緩衝
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
// 設定頂點屬性讀取
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);  
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));  
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));

//返回當前繫結緩衝目標對應的緩衝變數記憶體指標
glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// 複製資料到緩衝記憶體
memcpy(ptr, data, sizeof(data));

//複製緩衝
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,GLintptr writeoffset, GLsizeiptr size);
//***但如果我們想讀寫資料的兩個不同緩衝都為頂點陣列緩衝該怎麼辦呢?我們不能同時將兩個緩衝繫結到同一個緩衝目標上。正是出於這個原因,OpenGL提供給我們另外兩個緩衝目標,叫做GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER。我們接下來就可以將需要的緩衝繫結到這兩個緩衝目標上,並將這兩個目標作為readtarget和writetarget引數。

9.8 高階GLSL

9.8.1 GLSL內建變數

//============================GLSL內建變數=============================
//----------------------------頂點著色器變數----------------------------
//---頂點著色器的裁剪空間輸出位置向量
gl_Position   
    
//***必須先啟用***
glEnable(GL_PROGRAM_POINT_SIZE);
//---設定渲染的點大小 (預設禁用)
gl_PointSize

//正在繪製頂點的當前ID
//***當(使用glDrawElements)進行索引渲染的時候,這個變數會儲存正在繪製頂點的當前索引。當(使用glDrawArrays)不使用索引進行繪製的時候,這個變數會儲存從渲染呼叫開始的已處理頂點數量(每呼叫一次Draw重新計數)。***
gl_VertexID

//當前渲染的例項ID
gl_InstanceID
//----------------------------片段著色器變數----------------------------
//***gl_FragCoord的z分量等於對應片段的深度值, x和y分量是片段的視窗空間(Window-space)座標,其原點為視窗的左下角***
gl_FragCoord
    
//bool變數,如果當前片段是正向面的一部分那麼就是true,否則就是false
gl_FrontFacing

//設定片段的深度值
gl_FragDepth
//***然而,由我們自己設定深度值有一個很大的缺點,只要我們在片段著色器中對gl_FragDepth進行寫入,OpenGL就會(像深度測試小節中討論的那樣)禁用所有的提前深度測試(Early Depth Testing)。它被禁用的原因是,OpenGL無法在片段著色器執行之前得知片段將擁有的深度值,因為片段著色器可能會完全修改這個深度值。***

//但可以進行一定程度的調和
layout (depth_<condition>) out float gl_FragDepth;

condition可以為下表中的值:
條件 描述
any 預設值。提前深度測試是禁用的,你會損失很多效能
greater 你只能讓深度值比gl_FragCoord.z更大
less 你只能讓深度值比gl_FragCoord.z更小
unchanged 如果你要寫入gl_FragDepth,你將只能寫入gl_FragCoord.z的值

9.8.2 介面塊

//================================介面塊================================
// 類似C/C++結構體
// 例如
struct Light{
    vec3 position;

    vec3 ambient;//環境光
    vec3 diffuse;//漫反射光
    vec3 specular;//鏡面反射光
};
uniform Light light;
out Light light_out;

9.8.3 Uniform緩衝物件(Uniform塊)

//=============================Uniform緩衝物件============================
//------------------------------uniform Block塊佈局----------------------------
//***Uniform塊的內容是儲存在一個緩衝物件中的,它實際上只是一塊預留記憶體。因為這塊記憶體並不會儲存它具體儲存的是什麼型別的資料,我們還需要告訴OpenGL記憶體的哪一部分對應著著色器中的哪一個uniform變數***
//---Uniform塊---
layout (std140) uniform ExampleBlock
{
    float value;
    vec3  vector;
    mat4  matrix;
    float values[3];
    bool  boolean;
    int   integer;
};

//每個變數都有一個基準對齊量(Base Alignment),它等於一個變數在Uniform塊中所佔據的空間(包括填充量(Padding)),這個基準對齊量是使用std140佈局的規則計算出來的。接下來,對每個變數,我們再計算它的對齊偏移量(Aligned Offset),它是一個變數從塊起始位置的位元組偏移量。一個變數的對齊位元組偏移量必須等於基準對齊量的倍數。
layout (std140) uniform ExampleBlock
{
                     // 基準對齊量       // 對齊偏移量
    float value;     // 4               // 0 
    vec3 vector;     // 16              // 16  (必須是16的倍數,所以 4->16)
    mat4 matrix;     // 16              // 32  (列 0)
                     // 16              // 48  (列 1)
                     // 16              // 64  (列 2)
                     // 16              // 80  (列 3)
    float values[3]; // 16              // 96  (values[0])
                     // 16              // 112 (values[1])
                     // 16              // 128 (values[2])
    bool boolean;    // 4               // 144
    int integer;     // 4               // 148
}; 

//-----------------------------使用Uniform緩衝----------------------------
// * 首先跟其他緩衝一樣必須在程式中建立
unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152位元組的記憶體

//***如何才能讓OpenGL知道哪個Uniform緩衝對應的是哪個Uniform塊呢?***
//在OpenGL上下文中,定義了一些繫結點(Binding Point),我們可以將一個Uniform緩衝連結至它。在建立Uniform緩衝之後,我們將它繫結到其中一個繫結點上,並將著色器中的Uniform塊繫結到相同的繫結點,把它們連線到一起。

//將Uniform塊繫結到一個特定的繫結點
//先查詢著色器中Uniform塊索引,繫結到繫結點2
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID,"Lights"); 
glUniformBlockBinding(shaderA.ID, lights_index, 2);
//再將Uniform緩衝繫結到相同的繫結點2
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); 
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);

//***從OpenGL 4.2版本起,也可以新增一個佈局識別符號,顯式地將Uniform塊的繫結點儲存在著色器中,這樣就不用再呼叫glGetUniformBlockIndex和glUniformBlockBinding了。***
// 例如:
layout(std140, binding = 2) uniform Lights { ... };
    
//填充或者更新Uniform緩衝
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
int b = true; // GLSL中的bool是4位元組的,所以我們將它存為一個integer
glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); 
glBindBuffer(GL_UNIFORM_BUFFER, 0);

9.8.4 著色器快取物件(buffer)

//處理大型結構化資料塊,這時可以用一個buffer型別快取變數來儲存資料,著色器中定義快取變數方法為將變數放置到一個塊介面中,使用buffer關鍵字宣告。
// 例如
layout(std 430, binging = 0) buffer BufferObject{
    int mode;
    vec4 points[];
}

// *** 著色器快取物件與Uniform塊最大區別在於: 著色器儲存快取物件可以在著色器中讀寫,通過快取塊寫入的儲存快取的內容也可以被其他著色器請求使用,並且可以通過程式進行回讀。 ***

// 與 GL_SHADER_STORAGE_BUFFER 相對應

9.9 幾何著色器

// 頂點著色器和片段著色器之間 可選的一個著色器

9.10 例項化(重要!!!) ---------批量渲染

// 使用一次渲染呼叫來繪製多個物體, 節省每次繪製物體時CPU->GPU的通訊

//使用例項化渲染 將glDrawArrays和glDrawElements改為如下API:
glDrawArraysInstanced()
glDrawElementsInstanced()
//這個函式本身並沒有什麼用。渲染同一個物體一千次對我們並沒有什麼用處,每個物體都是完全相同的,而且還在同一個位置。我們只能看見一個物體!出於這個原因,GLSL在頂點著色器中嵌入了另一個內建變數,gl_InstanceID。
//在使用例項化渲染呼叫時,gl_InstanceID會從0開始,在每個例項被渲染時遞增1。比如說,我們正在渲染第43個例項,那麼頂點著色器中它的gl_InstanceID將會是42。因為每個例項都有唯一的ID,我們可以建立一個數組,將ID與位置值對應起來,將每個例項放置在世界的不同位置。
// *** 例如:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out vec3 fColor;

uniform vec2 offsets[100];

void main()
{
    vec2 offset = offsets[gl_InstanceID];
    gl_Position = vec4(aPos + offset, 0.0, 1.0);
    fColor = aColor;
}

gl_InstanceID //當前渲染例項ID

//-----------------------------例項化陣列---------------------------------
//雖然上述實現在目前的情況下能夠正常工作,但是如果我們要渲染遠超過100個例項的時候,我們最終會超過最大能夠傳送至著色器的uniform資料大小上限。它的一個代替方案是例項化陣列(Instanced Array).

//告訴OpenGL什麼時候更新頂點屬性,以下例子表明位於頂點位置2的頂點屬性是一個例項化陣列,每渲染一個例項更新一次頂點屬性
glVertexAttribDivisor(2, 1)

9.11 抗鋸齒

//多重取樣抗鋸齒(MSAA)
glEnable(GL_MULTISAMPLE);

// *** 離屏MSAA *** ---幀緩衝---
//----------------------------多重取樣紋理附件-------------------------------
//用glTexImage2DMultisample來替代glTexImage2D,它的紋理目標是GL_TEXTURE_2D_MULTISAPLE
//建立多重取樣紋理
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
//附加紋理到幀緩衝
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);

//---------------------------多重取樣渲染緩衝物件---------------------------
//建立多重取樣渲染緩衝物件 並指定儲存
//指定樣本數為4
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);


//渲染到多重取樣幀緩衝
// *** 渲染到多重取樣幀緩衝物件的過程都是自動的。只要在幀緩衝繫結時繪製任何東西,光柵器就會負責所有的多重取樣運算。最終會得到一個多重取樣顏色緩衝以及/或深度和模板緩衝。因為多重取樣緩衝有一點特別,不能直接將它們的緩衝影象用於其他運算,比如在著色器中對它們進行取樣。因此不能直接在螢幕中以紋理方式進行顯示,需要先縮小或者還願影象。

//多重取樣幀緩衝還原
//glBlitFramebuffer將一個幀緩衝中的某個區域複製到另一個幀緩衝中,並且將多重取樣緩衝還原
glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO); //幀緩衝
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); //螢幕緩衝
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);

// *** 但如果想要使用多重取樣幀緩衝的紋理輸出來做像是後期處理這樣的事情呢?我們不能直接在片段著色器中使用多重取樣的紋理。但我們能做的是將多重取樣緩衝位塊傳送到一個沒有使用多重取樣紋理附件的FBO中。然後用這個普通的顏色附件來做後期處理,從而達到我們的目的。然而,這也意味著我們需要生成一個新的FBO,作為中介幀緩衝物件,將多重取樣緩衝還原為一個能在著色器中使用的普通2D紋理。***

// *** 將一個多重取樣的紋理影象不進行還原直接傳入著色器也是可行的。GLSL提供了這樣的選項,讓我們能夠對紋理影象的每個子樣本進行取樣,所以我們可以建立我們自己的抗鋸齒演算法。
// 例如:
uniform sampler2DMS screenTextureMS;
//獲取每個子樣本顏色值
vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3);  // 第4個子樣本

10. 高階光照

//暫時先放一放

11. PBR

//暫時先放一放

12. 除錯

// 返回錯誤標記
glGetError();
標記 程式碼 描述
GL_NO_ERROR 0 自上次呼叫glGetError以來沒有錯誤
GL_INVALID_ENUM 1280 列舉引數不合法
GL_INVALID_VALUE 1281 值引數不合法
GL_INVALID_OPERATION 1282 一個指令的狀態對指令的引數不合法
GL_STACK_OVERFLOW 1283 壓棧操作造成棧上溢(Overflow)
GL_STACK_UNDERFLOW 1284 彈棧操作時棧在最低點(譯註:即棧下溢(Underflow))
GL_OUT_OF_MEMORY 1285 記憶體呼叫操作無法呼叫(足夠的)記憶體
GL_INVALID_FRAMEBUFFER_OPERATION 1286 讀取或寫入一個不完整的幀緩衝