openGL學習之路
shader學習貌似進入了一個瓶頸,可以看懂,但是真正上手的時候大腦卻一片混亂,所以就想著從更加基本的地方進入(好吧,還是數學問題),看看能不能增加一些奇怪的經驗值。。。
以下使用windows + vs2017 + glfw + glew + glm
https://www.bilibili.com/video/av24353839/?p=23
https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/#_2
=========================================Draw===========================================
openGL裡面的draw一般指的是glDrawArray之類的渲染指令,可是,他是怎麼繫結上去的,我每幀都需要傳送資料嗎?
!!!!!!以下我不確定是否為事實,只是自己的一些看法
先看一段程式塊
---------vertex.shader---------- #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; layout (location = 2) in vec2 aTexCoord; out vec4 ourColor; out vec2 TexCoord; uniform mat4 modleSpace; uniform mat4 viewSpace; uniform mat4 perjSpace; void main() { // gl_Position是openGL頂點著色器自帶的必須返回的頂點資料 // 而這段是計算“我”這個物體應該出現在視窗的位置和角度 gl_Position = perjSpace * viewSpace * modleSpace * vec4(aPos, 1.0); ourColor = vec4(1.0,0,0,1.0); TexCoord = aTexCoord; } ---------main.cpp-------- ...... unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // 首先繫結 // vertices 頂點,https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/#3d_1 太長了,就不復制了 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); // 將資料複製到快取 unsigned int EBO; // element buffer object glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 建立特徵碼,並填充資料到相應特徵碼中 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); unsigned int COL; glGenBuffers(1, &COL); glBindBuffer(GL_ARRAY_BUFFER, COL); glBufferData(GL_ARRAY_BUFFER, sizeof(color), &color, GL_STATIC_DRAW); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); .......... while (!glfwWindowShouldClose(window)) { //matrix = glm::rotate(matrix, glm::radians(1.0f), glm::vec3(0.0f, 0.0f, 1.0)); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glm::mat4 modleSpace; glm::mat4 viewSpace; glm::mat4 perjSpace; modleSpace = glm::translate(modleSpace, glm::vec3(0, 1, 0)); modleSpace = glm::rotate(modleSpace, glm::radians(10.0f) + (float)glfwGetTime(), glm::vec3(1.0, 0, 0)); //viewSpace = glm::translate(viewSpace, glm::vec3(-_right, 0, _forward)); viewSpace = glm::translate(viewSpace, glm::vec3(0, 0, -3)); // 透視投影矩陣,也就是camera裡面的fov,near之類的 perjSpace = glm::perspective(glm::radians(45.0f), (float)800 / (float)600, 0.1f, 10.0f); perjSpace = glm::translate(perjSpace, glm::vec3(-_right, 0, _forward)); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2); // 傳遞給shader引數,矩陣 glUniformMatrix4fv(glGetUniformLocation(shader.Shader_Program_ID, "modleSpace"), 1, GL_FALSE, glm::value_ptr(modleSpace)); glUniformMatrix4fv(glGetUniformLocation(shader.Shader_Program_ID, "viewSpace"), 1, GL_FALSE, glm::value_ptr(viewSpace)); glUniformMatrix4fv(glGetUniformLocation(shader.Shader_Program_ID, "perjSpace"), 1, GL_FALSE, glm::value_ptr(perjSpace)); shader.Use(); glDrawArrays(GL_TRIANGLES, 0, 36); glfwSwapBuffers(window); glfwPollEvents(); } ......
// 模型矩陣,用以計算物體在世界空間中的位置 // 物體出現在 worldUP * 1 的座標電商 modleSpace = glm::translate(modleSpace, glm::vec3(0, 1, 0)); // 觀察矩陣,也就是Camera會出現在那個位置,這裡Camera會出現在 -3 * worldForward 的座標上 viewSpace = glm::translate(viewSpace, glm::vec3(0, 0, -3)); // 透視投影矩陣,可以理解為是一個相機的大部分引數 // 第一個 -> 這個相機的角度有多大 對應unity fov // 第二個 -> 這個相機最大視角的高寬比 // 第三個 -> 這個相機的開口距離相機本體的距離 對應unity near // 第四個 -> 這個相機可以看到多遠的位置 對應unity far perjSpace = glm::perspective(glm::radians(45.0f), (float)800 / (float)600, 0.1f, 10.0f);
可以看到,頂點的複製和讀入只有一次,也就是glBufferData(),之後的操作我都沒有再次操作頂點資料,而是通過變換矩陣進行的視角移動,這樣的話,我的draw有幾個?
眾所周知,GPU的計算圖元是遠遠大於他的記憶體單元的,這也就導致了GPU的計算速度很快,但是儲存之類的卻是異想天開,但是我們一個場景裡面可是會有很多物體的啊,這些物體的的資料應該怎麼去處理,
(PS:如果你能看到這個PS,就說明這段話我不確定)猜想一下,glBufferData其實並不是將資料存入了GPU,而是儲存了一個相應資料的地址,&並把他壓入對應的資料管道緩衝中(VBO,vertex buffer object),當我呼叫Draw等繪圖指令的時候,他會依照對應管道的相應索引來將其一一放入一個列表中,GPU此時會按照相應管道中的順序依次繪製,完成一幀,那麼也就是說,當我們呼叫glBufferData的時候,CPU就會把一些資料暫存到與GPU通訊的管道中,而當CPU傳送一次Draw指令的時候,GPU會從這些管道中將資料的副本儲存下來,然後處理,丟棄,在處理下一個,直到索引到達終點。
用unity裡面的說法來說就是,當一幀開始的時候,GPU會先從CPU那裡拿走相應資料的索引,然後按照索引逐步的進行繪製,直到索引超出,完成一幀的繪製,也就是說其實Draw只是傳送一個指令,CPU告訴GPU,這一幀開始了,你處理一下我放在XX地方的資料,此時,GPU開始處理自己的工作,把CPU存入的資料依次拿出來,根據資料的相應指示繪製到螢幕上(也就是上面程式碼裡面的layout,告訴GPU,那些資料應該放在哪裡),那麼shader呢,我們好像沒有看到shader什麼事啊,其實shader就是GPU的接收函式,比如上面的shader,他會接收頂點,顏色,uv座標等,傳入之後GPU再次處理下一個,shader進行自己的工作,以上,一幀完成。
==========================================變換=============================================
變換的相應知識,可以在 https://learnopengl-cn.github.io/01%20Getting%20started/07%20Transformations/
我們在寫shader(unity)的時候,經常會看到mul,他會接受一個matrix4*4,vec4 的引數(當然,也可以matrix3*3,vec3,因為要滿足行列乘法嘛~),那麼這個到底有什麼作用,
在這裡,就要說一下座標系的問題了,世界座標,以標準的0.0作為世界中心,所有的物體都會在這個座標系上有相應的位置,而區域性座標系,則是以自身為基準(0.0),自身的正前方為z軸,假想一下,如果我們所有的物體都不進行轉換,直接使用自身的local座標來進行操作,會發生什麼?一堆物體在(x,y)座標上,你遮住我,我遮住你,所有物體都說我的座標是對的,根據shader的處理,我就應該顯示我的背面,臉我不要了,那麼會發生什麼~~~
camera躲在世界座標看著前面一部分顯示,一部分不顯示,一部分顯示了一部分,另一些我乍一看看不見,然後稍微走兩步他就突然出現了,此時camera的心情應該是這樣的????????????????????????????????????,我是誰,我在那,我要幹嘛??????
所以,物體最終顯示在螢幕中其實都是需要經過幾次變換的
https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/
第一步,也就是從本地座標先轉化為世界座標(也就是自身的座標位置*自身的模型矩陣,上面程式碼中的modleSpace = glm::translate...),此時,我們得到了物體在世界空間中的相對座標
第二步,也就是從世界的座標轉化為相對於相機的座標位置(自身的座標+camera座標),此時camera就可以知道我們的物體到底在自己的那個位置
第三步(偽):(正射投影,看名字也能瞭解一二,就是正交相機)
第三步,也是最複雜的一步,將所有的資料按照透視投影矩陣進行歸一化,所有超出的資料都將變為不可見,關於這個演算法實現(上面程式碼中的 perjSpace = glm::perspective.....),https://www.bilibili.com/video/av24353839/?p=23
其實。放到unity裡面大概就是這樣的
// unity裡面會自己幫助你進行幾次空間變換,所以一般我們寫的shader很少會用到多次變換,但是這裡只是過程
// 注意,unity和openGL左右手座標系不同,所以矩陣相乘的順序也不一樣
----------------------------------------------------------------------------------------
// 以下引數,aPos 為頂點,aColor為頂點顏色,aTexCoord為uv座標,gl_Position為我們需要返回的頂點位置,在unity裡面我們不需要寫
//gl_Position其實不應該寫到a2v裡面,因為在openGL裡面這個並不是傳遞給frag的引數
//而是直接回傳給GPU進行直接渲染的引數,而如果想要在frag裡面修改頂點資料,則應該在宣告一個引數,
//傳遞給frag
struct a2v
{
fixed3 aColor : COLOR;
fixed3 aPos : POSITION;
fixed3 aTexcoord : TEXCOORD0;
fixed4 gl_Position : POSITION; // 注意這個是openGL需要返回出去的位置
};
struct v2f
{
fixed4 ourColor : COLOR;
fixed3 TexCoord : TEXCOORD0;
};
// openGL裡面的頂點著色器,unity裡面是#pragma vertex vert
v2f vert(a2v v)
{
// 變換座標到世界空間,modleSpace是模型矩陣
// gl_Position = modleSpace * vec4(aPos, 1.0)
gl_Postion = mul((float4*4)unity_ObjectToWorld,fixed4(aPos,1.0));
// 把物體轉換到相機座標系
// gl_Position = glPosition + _WorldSpaceCameraPos.xyz;
// 把物體變換到檢視,座標系
// gl_Position = viewSpace * gl_Position;
gl_Position = mul((float4*4)UNITY_MATRIX_V,gl_Position);
// 把座標變換到裁剪空間,也就是透視投影矩陣
// gl_Position = perjSpace * gl_Position;
gl_Position = mul((float4*4)UNITY_MATRIX_VP,gl_Position);
}
上面步驟完成之後,我們其實就已經獲得了一個物體應該出現的相對於相機的位置,旋轉,他應該出現多少在相機的視角之中,
其實,我們在unity中也能看到一些矩陣資訊,用過Profiler的都知道,在繪製幀的時候,下方最常出現的就是UNITY_MATRIX_VP
也就是上面的第三步,將物體從世界空間轉化到裁剪空間中,
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------