<2021SC@SDUSC>开源游戏引擎Overload代码分析五:OvEditor——RawShaders.cpp
2021SC@SDUSC
Overload代碼分析五:OvEditor——RawShaders.cpp
- 前言
- RawShaders.cpp
- 1.GetGrid()
- vertex shader
- fragment shader
- 2.GetGizmo()
- vertex shader
- fragment shader
- 3.GetBillboard()
- vertex shader
- fragment shader
- 總結
前言
這是Overload引擎相關的第七篇文章,同時也是OvEditor分析的第二篇。Overload引擎的Github主頁在這里。
本篇文章主要會介紹RawShaders.cpp中的三個shader,會介紹各種shader相關的知識,比較復雜,需要花一篇文章來好好講述。
RawShaders.cpp
1.GetGrid()
這個shader是用來顯示網格平面的,如圖:
首先我們需要聲明的是,OpenGL的光柵化shader,或者說近乎所有shader語言的光柵化shader,都要分為兩個部分。第一個部分對頂點,或者說幾何信息做處理,而第二個部分需要對片元,或者說像素信息做處理。也就是說,第一個部分用于控制形狀位置等,而第二個部分用于確定顏色。在OpenGL中,第一個部分被稱作vertex shader,第二個部分則被稱作fragment shader。
我們僅看由文本定義的shader,下面會逐句分析,先看source.first,也就是vertex shader所對應的:
vertex shader
#version 430 core此行用于設定所用的glsl版本,430對應4.3版本。
layout (location = 0) in vec3 geo_Pos; layout (location = 1) in vec2 geo_TexCoords; layout (location = 2) in vec3 geo_Normal;layout (std140) uniform EngineUBO {mat4 ubo_Model;mat4 ubo_View;mat4 ubo_Projection;vec3 ubo_ViewPos;float ubo_Time; };out VS_OUT {vec3 FragPos;vec2 TexCoords; } vs_out;此處在定義所需的數據結構,location是對應的偏移,或者更易懂一點,是各變量所占的空間,也即布局(layout)方式。此處在變量類型前面的in,指的是這些屬性是需要傳入shader的。而下方所定義的結構體,布局方式定義為遵循std140,實際上就是一種單調遞增的布局方式,結構體內部定義了多個變量屬性。對std140規則感興趣的,可以看這篇文章。在下面又是一個結構體的定義,但此結構體是vertex shader所必須的,用于傳遞結果到fragment shader,out就是用于表明這是需要傳出的。我們可以看到,它在定義結構體的同時就創建了一個對象。
void main() {vs_out.FragPos = vec3(ubo_Model * vec4(geo_Pos, 1.0));vs_out.TexCoords = vs_out.FragPos.xz;gl_Position = ubo_Projection * ubo_View * vec4(vs_out.FragPos, 1.0); }此處是這個vertex shader的主函數,也就是真正實現功能的地方。在第一行代碼中,geo_Pos是當前模型在自己的模型空間內的位置,也就是模型相對于自己的位置,我們給他用1.0增加一維,以方便我們在需要時進行投影,不過在第一行代碼中并沒有投影。因為我們用的ubo_Model這樣一個四維矩陣與位置相乘,所以必須讓三維的位置信息增加一維。ubo_Model這個矩陣,是用來將一個模型空間,或者說local坐標系的位置,轉到世界空間,或者說全局坐標系的。于是,我們把當前vertex shader實施的頂點對象由本地轉到了全局,并把全局坐標存入了vs_out.FragPos。如果想要搞清楚我們現在和之后要提到的各種空間,可以看看這篇文章。
接下來,在第二行代碼中,我們給vs_out.TexCoords賦值。TexCoords實際上就是模型表面上的坐標,或者說是貼圖所需要的坐標,用于告知貼圖應當怎樣去貼,通常我們更喜歡叫他的小名——uv。然而此處,它直接把我們得到的世界坐標的x坐標和z坐標賦給了uv,這看起來很令人費解,但其實并不是這樣。要知道,這個shader想要實現的效果是GetGrid,什么意思?是要獲取網格,網格是什么呢?一般也就是一個劃分過的平面,也就是說,我們是在給一個平面賦uv。一個平面一般是和xz平面平行的,甚至重合,所以對于這個平面,我們就可以直接把它的xz坐標當作是uv。
最后是第三行代碼,給我們的世界坐標乘上了一個ubo_View和ubo_Projection。view矩陣是用于把世界空間的坐標轉到觀察空間,或者說攝像機所在的空間比較容易懂。而projection矩陣用于做投影,也就是把在觀察空間的物體給投影到平面上,哪個平面呢?攝像機的鏡頭,通過這一步操作,我們才可以最終看到場景的物體,而且是支持透視的。支持透視是如何實現的?我們可以發現,三維的世界坐標在乘矩陣之前就被拓展到了四維,之后才開始進行各種變換,實際上,這里藏了一個隱藏步驟。在投影時,坐標的第四維,也就是w值,會被整個坐標向量同除,之后w值回到1,這是投影的步驟。通過這一步,距離鏡頭不同遠近的物體才出現了區別。這在我上面提到的文章中,也是有提及的。
綜上,我們就得到了模型真實的,可以被看到的位置,其中,gl_Position是由OpenGL直接管理的,所以它會被傳到fragment shader中。
fragment shader
接下來就是source.second所定義的,也就是fragment shader:
out vec4 FRAGMENT_COLOR;layout (std140) uniform EngineUBO {mat4 ubo_Model;mat4 ubo_View;mat4 ubo_Projection;vec3 ubo_ViewPos;float ubo_Time; };in VS_OUT {vec3 FragPos;vec2 TexCoords; } fs_in;uniform vec3 u_Color;這些代碼就是定義變量罷了,和上面講vertex shader時完全是一個原理,不再復述了。但有一點值得一提,此處的VS_OUT是用于接收vertex shader中的vs_out,它在被定義時就已經接收了值,因為它是被OpenGL管理的。同時,uniform是指一個全局的變量,也就是說可以在shader中任何地方,甚至shader外也可以賦值。
float MAG(float p_lp) {const float lineWidth = 1.0f;const vec2 coord = fs_in.TexCoords / p_lp;const vec2 grid = abs(fract(coord - 0.5) - 0.5) / fwidth(coord);const float line = min(grid.x, grid.y);const float lineResult = lineWidth - min(line, lineWidth);return lineResult; }float Grid(float height, float a, float b, float c) {const float cl = MAG(a);const float ml = MAG(b);const float fl = MAG(c);const float cmit = 10.0f;const float cmet = 40.0f;const float mfit = 80.0f;const float mfet = 160.0f;const float df = clamp((height - cmit) / (cmet - cmit), 0.0f, 1.0f);const float dff = clamp((height - mfit) / (mfet - mfit), 0.0f, 1.0f);const float inl = mix(cl, ml, df);const float fnl = mix(inl, fl, dff);return fnl; }void main() {const float height = distance(ubo_ViewPos.y, fs_in.FragPos.y);const float gridA = Grid(height, 1.0f, 4.0f, 8.0f);const float gridB = Grid(height, 4.0f, 16.0f, 32.0f);const float grid = gridA * 0.5f + gridB;const vec2 viewdirW = ubo_ViewPos.xz - fs_in.FragPos.xz;const float viewdist = length(viewdirW);FRAGMENT_COLOR = vec4(u_Color, grid); }此處定義了多個函數,但我們可以看到,最后一行輸出的顏色中,rgb值是由u_color,一個全局值來決定的。也就是說,最終顏色和前面的函數計算毫無關系,只有alpha值,也就是透明度,是由計算決定的。實際上,就是讓網格線處的的alpha值更小,使得平面網格更加立體。
m_gridMaterial.Set("u_Color", p_color);從EditorRenderer.cpp中的這一行,可以確定顏色是直接確定下來的。
2.GetGizmo()
這個shader是用來顯示坐標軸的,如圖:
vertex shader
還是從vertex shader開始。
#version 430 corelayout (location = 0) in vec3 geo_Pos; layout (location = 2) in vec3 geo_Normal; layout (location = 1) in vec2 geo_TexCoords;layout (std140) uniform EngineUBO {mat4 ubo_Model;mat4 ubo_View;mat4 ubo_Projection;vec3 ubo_ViewPos;float ubo_Time; };out VS_OUT {vec3 Color; } vs_out;uniform bool u_IsBall; uniform bool u_IsPickable; uniform int u_HighlightedAxis;這些代碼都在定義變量,沒有新的東西,就不再講一遍了。
mat4 rotationMatrix(vec3 axis, float angle) {axis = normalize(axis);float s = sin(angle);float c = cos(angle);float oc = 1.0 - c;return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,0.0, 0.0, 0.0, 1.0); }定義了旋轉矩陣,給定旋轉軸和角度,可以獲得旋轉矩陣。跟數學的關系比較大,在代碼實現方面并不困難,不細講了。
void main() {mat4 instanceModel = ubo_Model;if (gl_InstanceID == 1) instanceModel *= rotationMatrix(vec3(0, 1, 0), radians(-90)); /* X axis */else if (gl_InstanceID == 2) instanceModel *= rotationMatrix(vec3(1, 0, 0), radians(90)); /* Y axis */float distanceToCamera = distance(ubo_ViewPos, instanceModel[3].xyz);vec3 pos = geo_Pos;vec3 fragPos = vec3(instanceModel * vec4(pos * distanceToCamera * 0.1f, 1.0));if (u_IsPickable){int blueComponent = 0;if (gl_InstanceID == 1)blueComponent = 252;if (gl_InstanceID == 2)blueComponent = 253;if (gl_InstanceID == 0)blueComponent = 254;vs_out.Color = vec3(1.0f, 1.0f, blueComponent / 255.0f);}else{if (u_IsBall){vs_out.Color = vec3(1.0f);}else{float red = float(gl_InstanceID == 1); // Xfloat green = float(gl_InstanceID == 2); // Yfloat blue = float(gl_InstanceID == 0); // Zif (!u_IsPickable && ((gl_InstanceID == 1 && u_HighlightedAxis == 0) || (gl_InstanceID == 2 && u_HighlightedAxis == 1) || (gl_InstanceID == 0 && u_HighlightedAxis == 2))){vs_out.Color = vec3(1.0f, 1.0f, 0.0f);} else{vs_out.Color = vec3(red, green, blue);}}}gl_Position = ubo_Projection * ubo_View * vec4(fragPos, 1.0); }instanceModel是ubo_Model對各個軸向可視化的實例。這里以z軸為基礎,當可視化x軸時,以y軸為旋轉軸旋轉-90度,這里可能會有一些困惑,-90度不是會與x正方向背道而馳嗎?事實上,雖然主觀感受上是如此,但是對于坐標軸來說,實際旋轉的度數和矩陣的對應度數是相反的,也就是說這樣x方向并沒有問題。主要的原因基于坐標系的轉換原理,在這里就不多談了,感興趣可以自行搜索,只要記住對坐標軸的旋轉變換和對物體的旋轉變換是相反的就可以了。
接著用distanceToCamera得到了觀察攝像機和物體之間的距離,目的是在計算fragPos時改變觀察距離對大小的影響。這樣就可以讓顯示的坐標軸在攝像機遠離時變大,而在靠近時減小,以便于觀察。
如果u_IsPickable為真,會把三個軸都顯示成白色。否則進行判斷,如果是軸的坐標原點(u_IsBall),則顯示為白色;如果不是原點而是軸,x、y、z軸分別為紅、綠、藍,以當前渲染對象為準。如果此時對應的高亮對象(鼠標指向的軸)是當前渲染軸向,則此軸顯示為黃色,否則顯示為軸向對應色。
fragment shader
#version 430 coreout vec4 FRAGMENT_COLOR;in VS_OUT {vec3 Color; } fs_in;uniform bool u_IsPickable;void main() {FRAGMENT_COLOR = vec4(fs_in.Color, 1.0f); }這里沒什么新的東西,簡短易懂,只是把上面設定的顏色給了像素。
3.GetBillboard()
這個shader用于實現公告牌技術,就是讓貼圖能夠隨著觀察的視線改變方向,減少穿幫的可能。在Overload中只用于光源的圖標顯示。
注意前后視角的變化,且光源圖標并沒有隨視角轉動而發生變化。
vertex shader
#version 430 corelayout (location = 0) in vec3 geo_Pos; layout (location = 1) in vec2 geo_TexCoords; layout (location = 2) in vec3 geo_Normal;layout (std140) uniform EngineUBO {mat4 ubo_Model;mat4 ubo_View;mat4 ubo_Projection;vec3 ubo_ViewPos;float ubo_Time; };out VS_OUT {vec2 TexCoords; } vs_out;uniform float u_Scale = 1.0f;void main() {vs_out.TexCoords = geo_TexCoords;mat4 model = ubo_Model;float distanceToCamera = distance(ubo_ViewPos, model[3].xyz);mat4 modelView = ubo_View * model;// Column 0:modelView[0][0] = 1;modelView[0][1] = 0;modelView[0][2] = 0;// Column 1:modelView[1][0] = 0;modelView[1][1] = 1;modelView[1][2] = 0;// Column 2:modelView[2][0] = 0;modelView[2][1] = 0;modelView[2][2] = 1;gl_Position = ubo_Projection * modelView * vec4(geo_Pos * distanceToCamera * u_Scale, 1.0); }distanceToCamera的用處和上一個shader相同,不再復述了。
modelView矩陣把modelView的前三維更改為單位矩陣,事實上相當于讓modelView矩陣只能支持平移變換,那么如果貼圖所在平面一創建就對應攝像機的直視方向,自然就不會有旋轉偏移,也就達成了公告牌所想要的效果。
fragment shader
#version 430 coreout vec4 FRAGMENT_COLOR;in VS_OUT {vec2 TexCoords; } fs_in;uniform vec4 u_Diffuse = vec4(1.0, 1.0, 1.0, 1.0); uniform sampler2D u_DiffuseMap; uniform vec2 u_TextureTiling = vec2(1.0, 1.0); uniform vec2 u_TextureOffset = vec2(0.0, 0.0);void main() {FRAGMENT_COLOR = texture(u_DiffuseMap, u_TextureOffset + vec2(mod(fs_in.TexCoords.x * u_TextureTiling.x, 1), mod(fs_in.TexCoords.y * u_TextureTiling.y, 1))) * u_Diffuse; }texture是采樣函數,對u_DiffuseMap這張圖用后一個參數(一般視作uv)進行采樣,得到的值作為顏色返回。
總結
本篇文章講了三個shader的實現,也順帶講了一些glsl的知識,還是比較豐富的。如果有一定圖形學基礎,看懂這個并不困難,還是要好好努力啊。
總結
以上是生活随笔為你收集整理的<2021SC@SDUSC>开源游戏引擎Overload代码分析五:OvEditor——RawShaders.cpp的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度文库复制
- 下一篇: 小知识·OTG工作原理