1. 程式人生 > >openGL學習之路

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

也就是上面的第三步,將物體從世界空間轉化到裁剪空間中,

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------