OpenGL 04--GLSL、數據類型、Uniform、著色器類

一、著色器

? ? ? ?在 OpenGL 中,著色器(Shader)是運行在 GPU 上的程序,用于處理圖形渲染管線中的不同階段

? ? ? ?這些小程序為圖形渲染管線的某個特定部分而運行。從基本意義上來說,著色器只是一種把輸入轉化為輸出的程序。著色器也是一種非常獨立的程序,因為它們之間不能相互通信;它們之間唯一的溝通只有通過輸入和輸出

? ?? OpenGL 著色器通常分為幾種類型,每種類型負責處理特定的渲染任務。以下是一些常見的著色器類型及其作用:

  1. 頂點著色器(Vertex Shader)

    • 處理每個頂點的數據。

    • 進行頂點位置的變換,包括模型變換、視圖變換和投影變換。

    • 計算光照、陰影和其它與頂點相關的屬性。

    • 輸出用于片元著色器的插值(如紋理坐標、法線方向等)。

  2. 片元著色器(Fragment Shader)

    • 處理每個片元的每個像素的數據。

    • 計算最終的像素顏色值,包括紋理采樣、顏色混合和透明度。

    • 應用后處理效果,如模糊、色調映射等。

  3. 幾何著色器(Geometry Shader)(可選):

    • 處理圖元的頂點數據。

    • 允許創建新的圖元(如將三角形轉換為線框)或修改現有圖元的頂點。

    • 可以增加或減少渲染的頂點數量。

  4. 曲面細分著色器(Tessellation Shader)(可選):

    • 控制曲面細分的過程,用于生成平滑的曲面。

    • 定義細分后的頂點位置和其它相關數據。

  5. 計算著色器(Compute Shader)(可選):

    • 執行通用計算任務,不直接參與渲染管線。

    • 可以用于處理大量數據并行計算,如物理模擬、圖像處理等。

二、GLSL?

? ? ? 在 OpenGL 程序中,著色器通常以 GLSL(OpenGL Shading Language)編寫,這是一種類似于 C/C 的高級著色語言。著色器程序需要編譯鏈接到著色器程序(Shader Program)中,然后才能被 OpenGL 使用。

? ? ??GLSL是為圖形計算量身定制的,它包含一些針對向量和矩陣操作的有用特性。

? ? ? 著色器的開頭總是要聲明版本,接著是輸入和輸出變量uniformmain函數每個著色器的入口點都是main函數,在這個函數中我們處理所有的輸入變量,并將結果輸出到輸出變量中。?

一個典型的著色器結構:?
// 指定使用的 GLSL 版本號
#version version_number// 定義一個輸入變量,其數據類型為 type,名稱為 in_variable_name
// 該變量在頂點著色器中用于接收頂點數據
in type in_variable_name;// 定義另一個輸入變量,其數據類型為 type,名稱為 in_variable_name
// 該變量在頂點著色器中用于接收頂點數據
in type in_variable_name;// 定義一個輸出變量,其數據類型為 type,名稱為 out_variable_name
// 該變量在頂點著色器中用于輸出處理后的頂點數據
out type out_variable_name;// 定義一個統一變量,其數據類型為 type,名稱為 uniform_name
// 該變量在頂點著色器中用于存儲著色器程序中全局的數據
uniform type uniform_name;// 頂點著色器的主函數
void main()
{// 在這里處理輸入變量,并進行一些圖形操作// 例如,應用變換、計算光照、紋理坐標等...// 將處理過的結果輸出到輸出變量out_variable_name = weird_stuff_we_processed;
}

應用示例:

#version 400 corelayout (location = 0) in vec3 aPos;  // 頂點位置
layout (location = 1) in vec3 aColor;  // 頂點顏色out vec4 outColor;  // 輸出顏色uniform mat4 matModel;  // 模型變換矩陣
uniform mat4 matView;  // 視圖變換矩陣
uniform mat4 matProjection;  // 投影變換矩陣void main()
{gl_Position = matProjection * matView * matModel * vec4(aPos, 1.0);  // 應用變換矩陣outColor = aColor;  // 輸出顏色
}

在這個示例中:

  • aPosaColor 是輸入變量,分別表示頂點的位置和顏色。

  • outColor 是輸出變量,表示處理后的顏色。

  • matModelmatViewProjection 是統一變量,分別表示模型變換矩陣、視圖變換矩陣和投影變換矩陣。

  • main 函數中,我們應用變換矩陣計算變換后的頂點位置,并將頂點顏色直接輸出。

? ? ?當我們特別談論到頂點著色器的時候,每個輸入變量也叫頂點屬性(Vertex Attribute)。我們能聲明的頂點屬性是有上限的,它一般由硬件來決定。OpenGL確保至少有16個包含4分量的頂點屬性可用,但是有些硬件或許允許更多的頂點屬性,可以查詢GL_MAX_VERTEX_ATTRIBS來獲取具體的上限:?

int nrAttributes;  // 定義一個整數變量,用于存儲最大頂點屬性數量
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);  // 查詢 OpenGL 實現支持的最大頂點屬性數量
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;  // 輸出最大頂點屬性數量

通常情況下它至少會返回16個,大部分情況下是夠用了。?

三、數據類型?

? ? ? ?和其他編程語言一樣,GLSL有數據類型可以來指定變量的種類。GLSL中包含C等其它語言大部分的默認基礎數據類型:intfloatdoubleuintbool。GLSL也有兩種容器類型分別是向量(Vector)矩陣(Matrix)

向量

? ? ? GLSL中的向量是一個可以包含有2、3或者4個分量的容器,分量的類型可以是前面默認基礎類型的任意一個。它們可以是下面的形式(n代表分量的數量):

類型含義
vecn包含n個float分量的默認向量
bvecn包含n個bool分量的向量
ivecn包含n個int分量的向量
uvecn包含n個unsigned int分量的向量
dvecn包含n個double分量的向量

大多數時候使用vecn,因為float足夠滿足大多數要求了。

? ? ?一個向量的分量可以通過vec.x這種方式獲取,這里x是指這個向量的第一個分量。你可以分別使用.x.y.z.w來獲取它們的第1、2、3、4個分量。GLSL也允許你對顏色使用rgba,或是對紋理坐標使用stpq訪問相同的分量。?

? ? ? 向量這一數據類型也允許一些有趣而靈活的分量選擇方式,叫做重組(Swizzling)。重組允許這樣的語法:

vec2 someVec; // 聲明一個包含兩個分量的二維向量
vec4 differentVec = someVec.xyxx; // 嘗試從 someVec 中提取 x 和 y 分量,并添加額外的 x 和 x 分量
vec3 anotherVec = differentVec.zyw; // 嘗試從 differentVec 中提取 z、y 和 w 分量
vec4 otherVec = someVec.xxxx + anotherVec.yxzy; // 嘗試從 someVec 中提取四個 x 分量,并與 anotherVec 的 y、z 和 y 分量相加
  1. vec4 differentVec = someVec.xyxx; 這一行試圖從 vec2 類型的 someVec 中提取兩個分量,然后錯誤地添加了兩個額外的 x 分量。在 GLSL 中,你只能通過 .xy.xz 等來訪問向量的分量。如果你想要復制 someVecxy 分量到 differentVec,應該是 vec4 differentVec = vec4(someVec, 0.0, 0.0, 1.0);

  2. vec3 anotherVec = differentVec.zyw; 這一行試圖從 vec4 類型的 differentVec 中提取三個分量,這是不允許的。vec3 類型只能有三個索引(.x.y.z)。如果你想要從 vec4 類型中提取 zy 分量到 vec3 類型,應該是 vec3 anotherVec = vec3(differentVec.z, differentVec.y, 0.0);

  3. vec4 otherVec = someVec.xxxx + anotherVec.yxzy; 這一行試圖訪問 someVec 的四個 x 分量,這是不正確的,因為 vec2 類型只有兩個分量。另外,anotherVec.yxzy 試圖訪問 vec3 類型的 anotherVecxyz 分量,但是沒有 vec4 類型,所以不能進行加法操作。如果你想要將 someVecxy 分量與 anotherVecyz 分量進行組合,應該是 vec4 otherVec = vec4(someVec.x, someVec.y, anotherVec.y, anotherVec.z);。?

修:?

vec2 someVec; // 聲明一個包含兩個分量的二維向量
vec4 differentVec = vec4(someVec, 0.0, 0.0, 1.0); // 復制 someVec 的 x 和 y 分量到 differentVec 的前兩個分量vec3 anotherVec = vec3(differentVec.z, differentVec.y, 0.0); // 提取 differentVec 的 z 和 y 分量到 anotherVec
vec4 otherVec = vec4(someVec.x, someVec.y, anotherVec.y, anotherVec.z); // 組合 someVec 和 anotherVec 的分量

? ? 上述代碼中的變量 someVecdifferentVecanotherVec 需要在著色器中被賦予具體的值,否則它們將包含未定義的值。?

? ? 你可以使用上面4個字母任意組合來創建一個和原來向量一樣長的(同類型)新向量,只要原來向量有那些分量即可;然而,不允許在一個vec2向量中去獲取.z元素。我們也可以把一個向量作為一個參數傳給不同的向量構造函數,以減少需求參數的數量:

vec2 vect = vec2(0.5, 0.7); // 創建一個二維向量 vect 并初始化其 x 分量為 0.5,y 分量為 0.7
vec4 result = vec4(vect, 0.0, 0.0); // 創建一個四維向量 result,并將 vect 的 x 和 y 分量復制到 result 的前兩個分量,z 和 w 分量初始化為 0.0
vec4 otherResult = vec4(result.xyz, 1.0); // 創建一個新的四維向量 otherResult,其 x、y 和 z 分量來自 result,w 分量設置為 1.0

使用場景

? ? ? ?這種類型的操作在圖形程序中非常有用,特別是在處理頂點數據、變換和光照計算時。例如,vec4 向量常用于表示齊次坐標(包括位置和齊次坐標 w),這在進行投影變換時非常有用。

注意事項

  • 在 GLSL 中,向量的分量訪問(如 result.xyz)返回一個新的向量,包含原向量的指定分量。

  • 當你從一個四維向量中提取三個分量并創建一個新的四維向量時,你需要顯式地指定第四個分量(在這個例子中是 1.0)。

  • 這種操作在頂點著色器中特別有用,因為頂點著色器可以接收位置、顏色、紋理坐標等作為輸入,然后對這些數據進行變換和計算。

向量是一種靈活的數據類型,我們可以把它用在各種輸入和輸出上。

四、輸入和輸出?

? ? ? ?雖然著色器是各自獨立的小程序,但是它們都是一個整體的一部分,出于這樣的原因,我們希望每個著色器都有輸入和輸出,這樣才能進行數據交流和傳遞。GLSL定義了inout關鍵字專門來實現這個目的。每個著色器使用這兩個關鍵字設定輸入和輸出,只要一個輸出變量與下一個著色器階段的輸入匹配,它就會傳遞下去。但在頂點和片段著色器中會有點不同

? ? ? ?頂點著色器應該接收的是一種特殊形式的輸入,否則就會效率低下。頂點著色器的輸入特殊在,它從頂點數據中直接接收輸入。為了定義頂點數據該如何管理,使用location這一元數據指定輸入變量,這樣我們才可以在CPU上配置頂點屬性。我們已經在前面看過這個了,layout (location = 0)頂點著色器需要為它的輸入提供一個額外的layout標識,這樣我們才能把它鏈接到頂點數據。

? ? 你也可以忽略layout (location = 0)標識符,通過在OpenGL代碼中使用glGetAttribLocation查詢屬性位置值(Location)。

// 指定使用的 GLSL 版本號和核心配置文件
#version 400 core// 定義一個輸入變量,其數據類型為 vec3(三個浮點數),名稱為 aPos
// 這個變量在頂點著色器中用于接收頂點位置數據
layout (location = 0) in vec3 aPos; // 位置變量的屬性位置值為0// 定義一個輸出變量,其數據類型為 vec4(四個浮點數),名稱為 vertexColor
// 這個變量在頂點著色器中用于輸出顏色數據,該數據將傳遞給片元著色器
out vec4 vertexColor; // 為片段著色器指定一個顏色輸出// 頂點著色器的主函數
void main()
{// 設置 gl_Position,這是頂點的最終位置// 我們如何把一個 vec3 作為 vec4 的構造器的參數,第四個分量默認為 1.0gl_Position = vec4(aPos, 1.0); // 將頂點位置轉換為齊次坐標// 把輸出變量設置為暗紅色// 注意:顏色值的順序是 RGBA(紅、綠、藍、透明度)vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把輸出變量設置為暗紅色
}

? ? ?在 OpenGL 程序中用于頂點著色器階段,負責處理頂點位置和顏色。通過設置 gl_Position,它將頂點位置轉換為齊次坐標,以便在視圖變換和投影變換后正確渲染頂點。同時,通過設置 vertexColor,它為每個頂點指定一個顏色值,這些顏色值將傳遞給片元著色器,用于確定每個片元的最終顏色。?

?? ? ? ?片段著色器,它需要一個vec4顏色輸出變量,因為片段著色器需要生成一個最終輸出的顏色。如果你在片段著色器沒有定義輸出顏色,OpenGL會把你的物體渲染為黑色(或白色)。

? ? ? ?所以,如果我們打算從一個著色器向另一個著色器發送數據,必須在發送方著色器中聲明一個輸出,在接收方著色器中聲明一個類似的輸入。當類型和名字都一樣的時候,OpenGL就會把兩個變量鏈接到一起,它們之間就能發送數據了(這是在鏈接程序對象時完成的)。

// 指定使用的 GLSL 版本號和核心配置文件
#version 400 core// 定義一個輸出變量,其數據類型為 vec4(四個浮點數),名稱為 FragColor
// 這個變量在片段著色器中用于輸出顏色數據,該數據將用于最終的像素顏色
out vec4 FragColor;// 定義一個輸入變量,其數據類型為 vec4(四個浮點數),名稱為 vertexColor
// 這個變量在片段著色器中用于接收從頂點著色器傳遞過來的顏色數據
in vec4 vertexColor; // 從頂點著色器傳來的輸入變量(名稱相同、類型相同)// 片段著色器的主函數
void main()
{// 將從頂點著色器接收到的顏色值直接賦值給輸出變量 FragColorFragColor = vertexColor;
}

? ? ?可以看到我們在頂點著色器中聲明了一個vertexColor變量作為vec4輸出,并在片段著色器中聲明了一個類似的vertexColor。由于它們名字相同且類型相同,片段著色器中的vertexColor就和頂點著色器中的vertexColor鏈接了。

五、Uniform

? ? ? ?Uniform是另一種從我們的應用程序在 CPU 上傳遞數據到 GPU 上的著色器的方式,但uniform和頂點屬性有些不同。

? ? ? ?首先,uniform是全局的(Global)。全局意味著uniform變量必須在每個著色器程序對象中都是獨一無二的,而且它可以被著色器程序的任意著色器任意階段訪問//第二,無論你把uniform值設置成什么,uniform會一直保存它們的數據,直到它們被重置或更新

? ? ? ?要在 GLSL 中聲明 uniform,只需在著色器中使用?uniform?關鍵字,并帶上類型和名稱。從那時起,我們就可以在著色器中使用新聲明的 uniform。

// 指定使用的 GLSL 版本號和核心配置文件
#version 400 core// 定義一個輸出變量,其數據類型為 vec4(四個浮點數),名稱為 FragColor
// 這個變量在片段著色器中用于輸出顏色數據,該數據將用于最終的片段(像素)顏色
out vec4 FragColor;// 定義一個統一變量,其數據類型為 vec4(四個浮點數),名稱為 ourColor
// 這個變量在片段著色器中用于存儲從OpenGL程序代碼中傳遞的顏色值
uniform vec4 ourColor; // 在OpenGL程序代碼中設定這個變量// 片段著色器的主函數
void main()
{// 將統一變量 ourColor 的值賦給輸出變量 FragColor// 這意味著每個片段(像素)都將使用 ourColor 指定的顏色FragColor = ourColor;
}

? ? ?在片段著色器中聲明了一個uniform?vec4的ourColor,并把片段著色器的輸出顏色設置為uniform值的內容。因為uniform是全局變量,可以在任何著色器中定義它們,而無需通過頂點著色器作為中介。頂點著色器中不需要這個uniform,所以不用在那里定義它。

? ? 如果你聲明了一個uniform卻在GLSL代碼中沒用過,編譯器會靜默移除這個變量,導致最后編譯出的版本中并不會包含它,這可能導致幾個非常麻煩的錯誤,記住這點!?

? ? ? 這個uniform現在還是空的;還沒有給它添加任何數據。首先需要找到著色器中uniform屬性的索引/位置值。當得到uniform的索引/位置值后,就可以更新它的值了。這次我們不去給像素傳遞單獨一個顏色,而是讓它隨著時間改變顏色:?

// 獲取當前時間值,通常用于動畫效果
float timeValue = glfwGetTime();// 計算綠色分量的值,使其在0.0到1.0之間變化,創建一個周期性的效果
// 這里使用了正弦函數(sin)來生成周期性變化,并將其范圍限制在0.0到1.0之間
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;// 獲取著色器程序中名為 "ourColor" 的uniform變量的位置
// 這個位置用于后續通過 glUniform 函數設置uniform變量的值
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");// 激活(使用)名為 shaderProgram 的著色器程序
// 這確保后續的 glUniform 調用更新的是正確的著色器程序中的uniform變量
glUseProgram(shaderProgram);// 更新著色器程序中 "ourColor" uniform變量的值
// 這里設置紅色和藍色分量為0.0,綠色分量為之前計算的 greenValue,透明度(Alpha)為1.0
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

? ? 首先通過glfwGetTime()獲取運行的秒數。然后使用sin函數讓顏色在0.0到1.0之間改變,最后將結果儲存到greenValue里。

? ? 接著,用glGetUniformLocation查詢uniform?ourColor的位置值。為查詢函數提供著色器程序和uniform的名字(這是我們希望獲得的位置值的來源)。如果glGetUniformLocation返回-1就代表沒有找到這個位置值。最后,可以通過glUniform4f函數設置uniform值。注意,查詢uniform地址不要求你之前使用過著色器程序,但是更新一個uniform之前你必須先使用程序(調用glUseProgram),因為它是在當前激活的著色器程序中設置uniform的。

? ? ?因為OpenGL在其核心是一個C庫,所以它不支持類型重載,在函數參數不同的時候就要為其定義新的函數;glUniform是一個典型例子。這個函數有一個特定的后綴,標識設定的uniform的類型。可能的后綴有:

后綴含義
f函數需要一個float作為它的值
i函數需要一個int作為它的值
ui函數需要一個unsigned int作為它的值
3f函數需要3個float作為它的值
fv函數需要一個float向量/數組作為它的值

? ? ?每當你打算配置一個OpenGL的選項時就可以簡單地根據這些規則選擇適合你的數據類型的重載函數。在上面的例子里,我們希望分別設定uniform的4個float值,所以通過glUniform4f傳遞我們的數據。

? ? ?如果打算讓顏色慢慢變化,就要在游戲循環的每一次迭代中(所以他會逐幀改變)更新這個uniform,否則三角形就不會改變顏色。下面計算greenValue然后每個渲染迭代都更新這個uniform:?

while(!glfwWindowShouldClose(window))
{// 輸入processInput(window);// 渲染// 清除顏色緩沖glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 記得激活著色器glUseProgram(shaderProgram);// 更新uniform顏色float timeValue = glfwGetTime();float greenValue = sin(timeValue) / 2.0f + 0.5f;int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);// 繪制三角形glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);// 交換緩沖并查詢IO事件glfwSwapBuffers(window);glfwPollEvents();
}
while(!glfwWindowShouldClose(window)) // 當窗口沒有接收到關閉信號時持續循環
{// 處理用戶輸入,例如按鍵或鼠標移動processInput(window);// 渲染指令開始// 清除顏色緩沖區,設置清屏顏色為 RGB(0.2, 0.3, 0.3),Alpha 為 1.0(不透明)glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// 清除當前激活的緩沖區(顏色緩沖區、深度緩沖區等)glClear(GL_COLOR_BUFFER_BIT);// 激活(使用)之前編譯好的著色器程序glUseProgram(shaderProgram);// 更新著色器程序中的 uniform 變量 ourColor,以動態改變顏色float timeValue = glfwGetTime(); // 獲取當前時間float greenValue = sin(timeValue) / 2.0f + 0.5f; // 計算綠色分量值int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor"); // 獲取 uniform 變量位置glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); // 設置 uniform 變量的值// 繪制三角形// 綁定頂點數組對象,它包含了頂點數據和頂點屬性的配置glBindVertexArray(VAO);// 繪制數組中的頂點,GL_TRIANGLES 表示繪制三角形glDrawArrays(GL_TRIANGLES, 0, 3);// 交換前后緩沖區,將渲染結果展示給用戶glfwSwapBuffers(window);// 處理所有待處理的事件,例如鍵盤輸入、鼠標移動等glfwPollEvents();
}

? ? ?這段代碼中,glfwWindowShouldClose(window) 函數檢查用戶是否嘗試關閉窗口(例如,點擊窗口的關閉按鈕或按下 ESC 鍵)。只要窗口沒有關閉,就繼續執行循環體內的代碼。

? ? ?在循環體內,首先處理用戶輸入,然后開始渲染過程。使用 glClearColorglClear 函數清除顏色緩沖區。接著,激活之前編譯好的著色器程序,并更新其中的 ourColor 變量,使其動態變化,從而創建一個顏色隨時間變化的效果。

? ? ?然后,綁定頂點數組對象(VAO)并繪制三角形。最后,交換前后緩沖區,使渲染結果可見,并處理所有待處理的事件。

完整代碼:

#include <glad/glad.h>  // 包含GLAD庫的頭文件,用于加載OpenGL函數指針
#include <GLFW/glfw3.h>  // 包含GLFW庫的頭文件,用于創建窗口和管理輸入
#include <iostream>  // 包含標準輸入輸出流庫
#include <cmath>  // 包含數學庫,用于sin函數void framebuffer_size_callback(GLFWwindow* window, int width, int height);  // 定義處理窗口大小改變事件的回調函數
void processInput(GLFWwindow* window);  // 定義處理用戶輸入的函數// 設置窗口的寬度和高度
const unsigned int SCR_WIDTH = 800;  // 窗口寬度
const unsigned int SCR_HEIGHT = 600;  // 窗口高度// 定義頂點著色器的GLSL源碼
const char* vertexShaderSource = "#version 400 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos, 1.0);\n"
"}\0";// 定義片段著色器的GLSL源碼
const char* fragmentShaderSource = "#version 400 core\n"
"out vec4 FragColor;\n"
"uniform vec4 ourColor;\n"
"void main()\n"
"{\n"
"   FragColor = ourColor;\n"
"}\n\0";int main()
{// 初始化GLFW并配置// ----------------------glfwInit();  // 初始化GLFW庫glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);  // 設置GLFW創建OpenGL 3的上下文glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);  // 設置次要版本號為3glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 設置OpenGL核心配置文件#ifdef __APPLE__  // 蘋果系統需要設置為向前兼容模式glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif// 創建GLFW窗口// --------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL)  // 如果窗口創建失敗{std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();  // 終止GLFWreturn -1;}glfwMakeContextCurrent(window);  // 設置當前上下文為新創建的窗口glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  // 設置窗口大小改變的回調函數// 加載OpenGL函數指針// ---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))  // 使用GLAD加載OpenGL函數指針{std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// 構建并編譯我們的著色器程序// ------------------------------------// 頂點著色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// 檢查著色器編譯錯誤int success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}// 片段著色器unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// 檢查著色器編譯錯誤glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// 鏈接著色器unsigned int shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// 檢查鏈接錯誤glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success) {glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// 設置頂點數據(和緩沖區)并配置頂點屬性// ------------------------------------------------------------------float vertices[] = {0.5f, -0.5f, 0.0f,  // 底部右側-0.5f, -0.5f, 0.0f,  // 底部左側0.0f,  0.5f, 0.0f   // 頂部 };unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// 首先綁定頂點數組對象,然后綁定并設置頂點緩沖區,然后配置頂點屬性。glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// 你可以在之后解綁頂點數組對象,這樣其他頂點數組對象的調用就不會意外修改這個對象,但這很少發生。修改其他// 頂點數組對象需要調用 glBindVertexArray,所以我們通常不需要解綁頂點數組對象(VAO)(VAOs)(或 VBOs)當它不是直接必要時。// glBindVertexArray(0);// 綁定頂點數組對象(它已經被綁定,但只是為了演示):看到我們只有一個頂點數組對象我們// 可以在渲染相應的三角形之前綁定它;這是另一種方法。glBindVertexArray(VAO);// 渲染循環// -----------while (!glfwWindowShouldClose(window))  // 當窗口沒有接收到關閉信號時持續循環{// 輸入processInput(window);  // 處理用戶輸入// 渲染// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);  // 清除顏色緩沖區,設置清屏顏色為 RGB(0.2, 0.3, 0.3),Alpha 為 1.0(不透明)glClear(GL_COLOR_BUFFER_BIT);  // 清除當前激活的緩沖區(顏色緩沖區、深度緩沖區等)// 確保在任何 glUniform 調用之前激活著色器glUseProgram(shaderProgram);  // 激活(使用)名為 shaderProgram 的著色器程序// 更新著色器uniform顏色double timeValue = glfwGetTime();  // 獲取當前時間float greenValue = static_cast<float>(sin(timeValue) / 2.0 + 0.5);  // 計算綠色分量值int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");  // 獲取 uniform 變量位置glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);  // 設置 uniform 變量的值// 繪制三角形glBindVertexArray(VAO);  // 綁定頂點數組對象glDrawArrays(GL_TRIANGLES, 0, 3);  // 繪制數組中的頂點,GL_TRIANGLES 表示繪制三角形// GLFW:交換緩沖區并輪詢IO事件(按鍵按下/釋放、鼠標移動等)glfwSwapBuffers(window);  // 交換前后緩沖區,將渲染結果展示給用戶glfwPollEvents();  // 處理所有待處理的事件}// 可選:一旦它們的目的已經達到,就釋放所有資源:// --------------------------------------------------------------------glDeleteVertexArrays(1, &VAO);  // 刪除頂點數組對象glDeleteBuffers(1, &VBO);  // 刪除緩沖區對象glDeleteProgram(shaderProgram);  // 刪除著色器程序// 終止 GLFW,清除所有先前分配的 GLFW 資源。// ------------------------------------------------------------------glfwTerminate();  // 終止 GLFWreturn 0;
}// 處理所有輸入:查詢 GLFW 本幀是否有相關按鍵被按下/釋放,并相應地做出反應
// ----------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)  // 如果按下了 ESC 鍵glfwSetWindowShouldClose(window, true);  // 設置窗口關閉標志為真
}// GLFW:每當窗口大小改變(由操作系統或用戶調整大小時)此回調函數執行
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {// 確保視口與新窗口尺寸匹配;注意,在視網膜顯示器上寬度和高度將明顯大于指定值。glViewport(0, 0, width, height);  // 設置視口大小
}

六、頂點添加顏色數據

? ? ?把顏色數據加進頂點數據中(把顏色數據添加為3個float值至vertices數組)。把三角形的三個角分別指定為紅色、綠色和藍色:

float vertices[] = {// 位置              // 顏色0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // 右下-0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // 左下0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 頂部
};

? ? ?調整一下頂點著色器,使它能夠接收顏色值作為一個頂點屬性輸入。需要注意的是用layout標識符來把aColor屬性的位置值設置為1:

#version 400 core
layout (location = 0) in vec3 aPos;   // 位置變量的屬性位置值為 0 
layout (location = 1) in vec3 aColor; // 顏色變量的屬性位置值為 1out vec3 ourColor; // 向片段著色器輸出一個顏色void main()
{gl_Position = vec4(aPos, 1.0);ourColor = aColor; // 將ourColor設置為我們從頂點數據那里得到的輸入顏色
}

? ? ? 由于不再使用uniform來傳遞片段的顏色了,現在使用ourColor輸出變量,必須再修改一下片段著色器:?

// 指定使用的 GLSL 版本號和核心配置文件
#version 400 core// 定義一個輸出變量,其數據類型為 vec4(四個浮點數),名稱為 FragColor
// 這個變量在片段著色器中用于輸出顏色數據,該數據將用于最終的片段(像素)顏色
out vec4 FragColor;  // 定義一個輸入變量,其數據類型為 vec3(三個浮點數),名稱為 ourColor
// 這個變量在片段著色器中用于接收從頂點著色器傳遞過來的顏色數據
in vec3 ourColor;// 片段著色器的主函數
void main()
{// 將從頂點著色器接收到的顏色值轉換為四維向量,并設置 alpha 分量為 1.0(不透明)FragColor = vec4(ourColor, 1.0);
}

? ? 因為添加了另一個頂點屬性,并且更新了VBO的內存,必須重新配置頂點屬性指針。更新后的VBO內存中的數據現在看起來像這樣:

知道了現在使用的布局,就可以使用glVertexAttribPointer函數更新頂點格式。

// 位置屬性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 顏色屬性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
glEnableVertexAttribArray(1);

? ? ? glVertexAttribPointer函數的前幾個參數比較明了。配置屬性位置值為1的頂點屬性。顏色值有3個float那么大,所以不去標準化這些值。

? ? ? 由于現在有了兩個頂點屬性,我們不得不重新計算步長值。為獲得數據隊列中下一個屬性值(比如位置向量的下個x分量)我們必須向右移動6個float,其中3個是位置值,另外3個是顏色值。這使我們的步長值為6乘以float的字節數(=24字節)。
? ? ? 同樣,這次必須指定一個偏移量。對于每個頂點來說,位置頂點屬性在前,所以它的偏移量是0。顏色屬性緊隨位置數據之后,所以偏移量就是3 * sizeof(float),用字節來計算就是12字節。

運行結果:

? ? ? 這是在片段著色器中進行的所謂片段插值(Fragment Interpolation)的結果。當渲染一個三角形時,光柵化(Rasterization)階段通常會造成比原指定頂點更多的片段。光柵會根據每個片段在三角形形狀上所處相對位置決定這些片段的位置。
? ? ? 基于這些位置,它會插值(Interpolate)所有片段著色器的輸入變量。比如,有一個線段,上面的端點是綠色的,下面的端點是藍色的。如果一個片段著色器在線段的70%的位置運行,它的顏色輸入屬性就會是一個綠色和藍色的線性結合;更精確地說就是30%藍 + 70%綠。

七、著色器類?

? ? ? 編寫、編譯、管理著色器是件麻煩事,寫一個類會輕松一點,它可以從硬盤讀取著色器,然后編譯并鏈接它們,并對它們進行錯誤檢測。

? ? ?把著色器類全部放在在頭文件里,主要是為了學習用途,當然也方便移植。先添加必要的include,并定義類結構:

#ifndef SHADER_H
#define SHADER_H// 防止頭文件重復包含#include <glad/glad.h>  // 錯誤:此處多余的分號(需刪除)
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>class Shader {
public:unsigned int ID;  // OpenGL著色器程序對象的ID// 構造函數:通過頂點和片段著色器文件路徑創建著色器程序Shader(const char* vertexPath, const char* fragmentPath);// 激活當前著色器程序void use();// 設置Uniform變量的工具函數(const成員函數,不修改對象狀態)void setBool(const std::string &name, bool value) const;void setInt(const std::string &name, int value) const;void setFloat(const std::string &name, float value) const;
};#endif

? ? ? 在頭文件頂部使用了幾個預處理指令(Preprocessor Directives)。這些預處理指令會告知你的編譯器只在它沒被包含過的情況下才包含和編譯這個頭文件,即使多個文件都包含了這個著色器頭文件。它是用來防止鏈接沖突的。

? ? ?著色器類儲存了著色器程序的ID。它的構造器需要頂點和片段著色器源代碼的文件路徑,這樣就可以把源碼的文本文件儲存在硬盤上了。除此之外,還加入了一些工具函數:use用來激活著色器程序,所有的set…函數能夠查詢一個unform的位置值并設置它的值。?

從文件讀取?

①?使用C++文件流讀取著色器內容,儲存到幾個string對象里。
Shader(const char* vertexPath, const char* fragmentPath)
{// 1. 從文件路徑中獲取頂點/片段著色器std::string vertexCode;std::string fragmentCode;std::ifstream vShaderFile;std::ifstream fShaderFile;// 保證ifstream對象可以拋出異常:vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);try {// 打開文件vShaderFile.open(vertexPath);fShaderFile.open(fragmentPath);std::stringstream vShaderStream, fShaderStream;// 讀取文件的緩沖內容到數據流中vShaderStream << vShaderFile.rdbuf();fShaderStream << fShaderFile.rdbuf();       // 關閉文件處理器vShaderFile.close();fShaderFile.close();// 轉換數據流到stringvertexCode   = vShaderStream.str();fragmentCode = fShaderStream.str();     }catch(std::ifstream::failure e){std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;}const char* vShaderCode = vertexCode.c_str();const char* fShaderCode = fragmentCode.c_str();[...]
②編譯和鏈接著色器。

(注意,我們也將檢查編譯/鏈接是否失敗,如果失敗則打印編譯時錯誤,調試的時候這些錯誤輸出會及其重要)

// 2. 編譯著色器
unsigned int vertex, fragment;
int success;
char infoLog[512];// 頂點著色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// 打印編譯錯誤(如果有的話)
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if(!success)
{glGetShaderInfoLog(vertex, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
};// 片段著色器也類似
[...]// 著色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
// 打印連接錯誤(如果有的話)
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if(!success)
{glGetProgramInfoLog(ID, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}// 刪除著色器,它們已經鏈接到我們的程序中了,已經不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
③use函數
void use() 
{ glUseProgram(ID);
}
④uniform的setter函數
// 設置布爾值到著色器的統一變量中
// 參數:
//   name: 統一變量的名稱(在著色器代碼中定義的變量名)
//   value: 要設置的布爾值
// 注意:
//   OpenGL 中布爾值通常以整數形式傳遞(true 為 1,false 為 0)
void setBool(const std::string &name, bool value) const
{// 調用 OpenGL 的 glUniform1i 函數將布爾值設置到著色器的統一變量中// value 被隱式轉換為整數(true 轉換為 1,false 轉換為 0)glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); 
}// 設置整數值到著色器的統一變量中
// 參數:
//   name: 統一變量的名稱
//   value: 要設置的整數值
void setInt(const std::string &name, int value) const
{ // 調用 OpenGL 的 glUniform1i 函數將整數值設置到著色器的統一變量中glUniform1i(glGetUniformLocation(ID, name.c_str()), value); 
}// 設置浮點值到著色器的統一變量中
// 參數:
//   name: 統一變量的名稱
//   value: 要設置的浮點值
void setFloat(const std::string &name, float value) const
{ // 調用 OpenGL 的 glUniform1f 函數將浮點值設置到著色器的統一變量中glUniform1f(glGetUniformLocation(ID, name.c_str()), value); 
}
⑤創建一個著色器對象(Shader 類的實例),并在渲染循環中使用它來設置統一變量并繪制圖形
// 創建一個 Shader 對象,指定頂點著色器和片段著色器的文件路徑
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
...
while(...) // 假設這是一個渲染循環,例如 OpenGL 的主循環
{// 激活著色器程序ourShader.use();// 設置著色器中的統一變量 "someUniform" 的值為 1.0fourShader.setFloat("someUniform", 1.0f);// 調用繪制函數,繪制圖形DrawStuff();
}

頂點和片段著色器儲存為兩個叫做shader.vsshader.fs的文件

練習?

4.0.shader.fs

#version 400 core
out vec4 FragColor; // 輸出片段顏色in vec3 ourColor; // 從頂點著色器接收的顏色void main()
{FragColor = vec4(ourColor, 1.0); // 設置片段顏色(添加透明度)
}

?4.0.shader.vs

#version 400 core
layout (location = 0) in vec3 aPos;   // 頂點位置屬性
layout (location = 1) in vec3 aColor; // 頂點顏色屬性out vec3 ourColor; // 將頂點顏色傳遞給片段著色器void main()
{gl_Position = vec4(aPos, 1.0); // 設置頂點位置(齊次坐標)ourColor = aColor;            // 將頂點顏色傳遞到片段著色器
}

1.修改頂點著色器讓三角形上下顛倒

#version 400 core
layout (location = 0) in vec3 aPos;    // 頂點位置屬性
layout (location = 1) in vec3 aColor;  // 頂點顏色屬性out vec3 ourColor;                     // 輸出變量,傳遞給片段著色器void main()
{gl_Position = vec4(aPos.x, -aPos.y, aPos.z, 1.0); // 設置頂點位置,并對 y 分量取反ourColor = aColor;                                // 將頂點顏色傳遞給片段著色器
}

2.使用uniform定義一個水平偏移量,在頂點著色器中使用這個偏移量把三角形移動到屏幕右側?

// In your CPP file:
// ======================
float offset = 0.5f;
ourShader.setFloat("xOffset", offset);// In your vertex shader:
// ======================
#version 400 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;out vec3 ourColor;uniform float xOffset;void main()
{gl_Position = vec4(aPos.x + xOffset, aPos.y, aPos.z, 1.0); // add the xOffset to the x position of the vertex positionourColor = aColor;
}
// 設置統一變量的值float offset = 0.5f;  // 定義一個偏移量ourShader.use();      // 激活著色器程序ourShader.setFloat("xOffset", offset);  // 設置統一變量的值
// 指定 GLSL 的版本為 4.00,并使用核心(Core)模式。這是 OpenGL 4.0 及以上版本的標準。
#version 400 core// 定義頂點位置輸入,位于頂點屬性位置 0。
layout (location = 0) in vec3 aPos;// 定義頂點顏色輸入,位于頂點屬性位置 1。
layout (location = 1) in vec3 aColor;// 定義一個輸出變量,用于將頂點顏色傳遞給片段著色器。
out vec3 ourColor;// 定義一個統一變量,用于在運行時動態調整頂點的 x 坐標。
uniform float xOffset;void main()
{// 設置頂點的最終位置。// 將 xOffset 的值添加到頂點的 x 坐標上,從而動態調整頂點的水平位置。gl_Position = vec4(aPos.x + xOffset, aPos.y, aPos.z, 1.0);// 將頂點顏色傳遞給片段著色器。ourColor = aColor;
}

?

3.使用out關鍵字把頂點位置輸出到片段著色器,并將片段的顏色設置為與頂點位置相等(來看看連頂點位置值都在三角形中被插值的結果)。

vs

#version 400 core
layout (location = 0) in vec3 aPos;       // 頂點位置輸入
layout (location = 1) in vec3 aColor;     // 頂點顏色輸入out vec3 ourPosition;                     // 輸出到片段著色器的頂點位置void main()
{gl_Position = vec4(aPos, 1.0);        // 設置頂點的最終位置ourPosition = aPos;                   // 將頂點位置傳遞給片段著色器
}

fs

#version 400 core
out vec4 FragColor;                       // 輸出片段顏色
in vec3 ourPosition;                      // 從頂著點色器接收的頂點位置void main()
{FragColor = vec4(ourPosition, 1.0);   // 將插值后的頂點位置作為顏色輸出
}

Q:為什么三角形的左下角是黑的?

A:片段顏色的輸出等于三角形頂點的坐標(插值后)。三角形左下角的坐標是 (-0.5f, -0.5f, 0.0f)。由于 x 和 y 值是負數,它們會被截斷為 0.0f 的值。這種情況會一直持續到三角形的中心部分,因為從中心部分開始,插值后的值會再次變為正值。0.0f 的值當然對應黑色。

參考:

著色器 - LearnOpenGL CN

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/70852.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70852.shtml
英文地址,請注明出處:http://en.pswp.cn/web/70852.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

服務器離線部署DeepSeek

目標 本次部署的目標是在本地服務器上部署DeepSeek。但是該服務不能連接外網&#xff0c;因此只能使用離線部署的方式。為了一次完成部署。現在云服務器上進行嘗試。 云服務器部署嘗試 云服務器配置 CentOS72080Ti 11GB 安裝準備 1、上傳iso并配置為本地yum源 安裝前先將…

刪除idea recent projects 記錄

1、退出idea&#xff08;一定要全部退出idea&#xff0c;要不然刪除后&#xff0c;idea一退出&#xff0c;又保存上了&#xff09; 2、進入 C:\Users\Administrator\AppData\Roaming\JetBrains\IntelliJIdea2024.1\options 目錄 根據不同的版本號 IntelliJIdea2024.1 這個地方…

【MySql】EXPLAIN執行計劃全解析:15個字段深度解讀與調優指南

文章目錄 一、執行計劃核心字段總覽二、關鍵字段深度拆解1. type&#xff08;訪問類型&#xff09;——查詢性能的晴雨表典型場景分析&#xff1a; 2. key_len&#xff08;索引使用長度&#xff09;——索引利用率的檢測儀計算示例&#xff1a; 3. Extra&#xff08;附加信息&a…

如何實現一個 Spring Boot Starter

在 Spring Boot 中&#xff0c;Starter 是一種自動配置的模塊&#xff0c;它封裝了一些常用的功能&#xff0c;并通過 Spring Boot 的約定大于配置的原則&#xff0c;使開發者能夠快速使用和集成相關功能。通常&#xff0c;Spring Boot Starter 包含了所需的依賴、配置、自動化…

使用python做http代理請求

有這樣一個需求現在有兩臺A&#xff0c;B兩臺電腦組成了一個局域網&#xff0c;在A電腦上開發webjava應用&#xff0c;需要調用第三方接口做http請求&#xff0c;但是這個請求只能在B電腦上請求。 一種解決方案&#xff1a;自定義一個中間服務&#xff0c;在電腦B上運行一個簡…

系統架構設計師考點——嵌入式技術

一、備考指南 嵌入式技術主要考查的是嵌入式基礎知識、嵌入式設計等相關知識&#xff0c;在系統架構設計師的考試中選擇題占2~4分&#xff0c;案例分析有時會考關鍵路徑的技術問答&#xff0c;這個題目一般比較難&#xff0c;但是由于案例分析題是五題選三題&#xff0c;所以…

當AI重構認知:技術狂潮下的教育沉思錄

備注&#xff1a;文章未Deepseek R1模型輔助生成&#xff0c;如有不妥請諒解。 以下使原文&#xff1a; 我有三個娃&#xff0c;各間隔4到5歲&#xff0c;經歷過搜索引擎&#xff0c;短視頻&#xff0c;短劇&#xff0c;本身曾經也是教育專業出生&#xff0c;任何事務都有兩面性…

EasyExcel 實踐案例:打印工資條

文章目錄 &#x1f4a1; 1. 每個員工一個 Excel 文件? 占位符格式&#x1f4cc; Excel 模板&#x1f4cc; Java 代碼&#x1f525; 關鍵點 &#x1f4a1; 2. 每個員工一個 Sheet? 占位符格式&#x1f4cc; Java 代碼&#x1f525; 關鍵點 &#x1f4a1; 3. 一個 Sheet&#x…

編程題-從前序與中序遍歷序列構造二叉樹(中等-重點)

題目&#xff1a; 給定兩個整數數組 preorder 和 inorder &#xff0c;其中 preorder 是二叉樹的先序遍歷&#xff0c; inorder 是同一棵樹的中序遍歷&#xff0c;請構造二叉樹并返回其根節點。 提示: preorder 和 inorder 均 無重復 元素 解法一&#xff08;遞歸&#xff0…

Vue 3 + Vite 項目配置訪問地址到服務器某個文件夾的解決方案

前言 在開發 Vue 3 Vite 項目時&#xff0c;我們經常需要將項目部署到服務器的某個特定文件夾下。例如&#xff0c;將項目部署到 /my-folder/ 目錄下&#xff0c;而不是服務器的根目錄。這時&#xff0c;我們需要對 Vite 和 Vue Router 進行一些配置&#xff0c;以確保項目能…

【Rust中級教程】2.10. API設計原則之受約束性(constrained) Pt.1:對類型進行修改、`#[non_exhaustive]`注解

喜歡的話別忘了點贊、收藏加關注哦&#xff08;加關注即可閱讀全文&#xff09;&#xff0c;對接下來的教程有興趣的可以關注專欄。謝謝喵&#xff01;(&#xff65;ω&#xff65;) 2.10.1. 接口的更改要三思 如果你的接口要做出對用戶可見的更改&#xff0c;那么一定要三思…

Imagination GPU 3D Graphics Wrokload

本次分享Imagination GPU 的3D 圖像處理負載流程。 總的分為兩個階段 第一階段&#xff1a;Geometry Processing Phase&#xff08;幾何處理階段&#xff09;是渲染管線中的一個關鍵環節&#xff0c;主要負責對三維幾何數據進行處理和變換&#xff0c;以便后續在屏幕上進行顯…

自動化設備對接MES系統找DeepSeek問方案

項目需要現場的PLC設備HTTP協議JSON格式的方式對接MES系統平臺&#xff0c;于是試了一下&#xff1a; 找到的相關資源鏈接在這里。

VoIP之音頻3A技術

音頻3A技術是改善語音通話質量的三種關鍵技術的簡稱&#xff0c;包括聲學回聲消除&#xff08;Acoustic Echo Cancellation, AEC&#xff09;、自動增益控制&#xff08;Automatic Gain Control, AGC&#xff09;、自噪聲抑制&#xff08;Automatic Noise Suppression, ANS&…

量子計算的數學基礎:復數、矩陣和線性代數

量子計算是基于量子力學原理的一種新型計算模式,它與經典計算機在信息處理的方式上有著根本性的區別。在量子計算中,信息的最小單位是量子比特(qubit),而不是傳統計算中的比特。量子比特的狀態是通過量子力學中的數學工具來描述的,因此,理解量子計算的數學基礎對于深入學…

京準電鐘:NTP精密時鐘服務器在自動化系統中的作用

京準電鐘&#xff1a;NTP精密時鐘服務器在自動化系統中的作用 京準電鐘&#xff1a;NTP精密時鐘服務器在自動化系統中的作用 NTP精密時鐘服務器在自動化系統中的作用非常重要&#xff0c;特別是在需要高精度時間同步的場景中。NTP能夠提供毫秒級的時間同步精度&#xff0c;這…

Python實現GO鵝優化算法優化Catboost回歸模型項目實戰

說明&#xff1a;這是一個機器學習實戰項目&#xff08;附帶數據代碼文檔視頻講解&#xff09;&#xff0c;如需數據代碼文檔視頻講解可以直接到文章最后關注獲取。 1.項目背景 在當今的數據驅動時代&#xff0c;機器學習模型在各種應用中扮演著至關重要的角色。特別是在預測分…

如何在docker上部署前端nginx服務(VUE)

目錄結構 clean.sh docker stop rszWeb; docker rm rszWeb; start.sh docker run -d \ --name rszWeb \ -p 7084:80 \ -m 500m \ --privileged=true \ --restart=always \ -v /home/rsz/ui/conf/nginx.conf:/etc/nginx/nginx.conf \ -v /home/rsz/ui/logs:/meta/logs \ -v /…

可獄可囚的爬蟲系列課程 15:防盜鏈反爬蟲的處理

一、防盜鏈了解 防盜鏈是一種技術手段&#xff0c;主要用于防止其他網站通過直接鏈接的方式使用本網站的資源&#xff08;如圖片、文件等&#xff09;&#xff0c;從而節省帶寬和服務器資源。當其他網站嘗試直接鏈接到受保護的資源時&#xff0c;服務器會根據設置的規則判斷請求…

2020年藍橋杯Java B組第二場題目+部分個人解析

#A&#xff1a;門牌制作 624 解一&#xff1a; public static void main(String[] args) {int count0;for(int i1;i<2020;i) {int ni;while(n>0) {if(n%102) {count;}n/10;}}System.out.println(count);} 解二&#xff1a; public static void main(String[] args) {…