在寫完OpenGL項目後,再次回顧並總結一下用到的一些知識。
注意此博文可能需要先入門,否則可能有些難懂
我做了一個簡易的皮卡丘版的跳一跳,有興趣的可以看一下我的Github:Jumping Pikachu
整個項目的一個亮點是在跳躍失敗了以後會產生爆破的效果,這個效果需要用到幾何着色器。
關於着色器
我整個項目的學習鏈接:LearnOpenGL
在OpenGL中,任何事物都在3D空間中,而屏幕和窗口卻是2D像素數組,3D座標轉爲2D座標的處理過程叫做圖形渲染管線
而在圖形渲染管線也分爲好幾個步驟,以下爲圖示
其中三個着色器就是把數據處理成另一類數據然後交給下一個着色器繼續處理,最後出來就是我們要的內容了。
頂點着色器(vs) 是最初用來處理頂點座標的,一般會對座標做一些計算
幾何着色器(gs) 是對原座標進行一些修改,個人感覺適用於一些特效
片段着色器(fs) 是對圖形進行塗色,並最終輸出。
注意:這三個順序是固定的,其中幾何着色器並不是一定要用的。
頂點着色器
一般一個點儲存了它的三維座標、三維法向量和二維的紋理座標。
其中法向量是爲了計算光線的,而紋理座標則是爲了紋理貼圖。
以下的代碼是我項目中的,其中out是傳到後面一個着色器的東西,而gl_Position是該點的輸出。
version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
out VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 texCoords;
} vs_out;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
vs_out.Normal = mat3(transpose(inverse(model))) * aNormal;
vs_out.texCoords = aTexCoords;
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
幾何着色器
集合着色器可以對座標進行一些修改,而我的項目中皮卡丘爆破的場景就是通過這個進行修改的。
爆破的效果是通過對每一個碎片向它的法向量移動一段距離而獲得的,一下是教程中的爆破效果。
以下是我項目的代碼
#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 texCoords;
} gs_in[];
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
uniform float time;
vec4 explode(vec4 position, vec3 normal)
{
float magnitude = 2.0;
vec3 direction;
vec4 temp;
if (time > 0.0) {
direction = normal * (-log(1.0- time)) * magnitude;
temp = position + vec4(direction, 0.0);
} else {
temp = position;
}
return temp;
}
vec3 GetNormal()
{
vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position);
vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position);
return normalize(cross(a, b));
}
void main() {
vec3 normal = GetNormal();
gl_Position = explode(gl_in[0].gl_Position, normal);
Normal = gs_in[0].Normal;
FragPos = gs_in[0].FragPos;
TexCoords = gs_in[0].texCoords;
EmitVertex();
gl_Position = explode(gl_in[1].gl_Position, normal);
Normal = gs_in[1].Normal;
FragPos = gs_in[1].FragPos;
TexCoords = gs_in[1].texCoords;
EmitVertex();
gl_Position = explode(gl_in[2].gl_Position, normal);
Normal = gs_in[2].Normal;
FragPos = gs_in[2].FragPos;
TexCoords = gs_in[2].texCoords;
EmitVertex();
EndPrimitive();
}
片段着色器
片段着色器就是最後進行上色的部分,由於顏色的亮度和光照有關,因此其中包含計算光照和顏色。
對於不同反射等計算是比較困難的,我就借鑑了教程上的部分。
#version 330 core
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform DirLight dirLight;
uniform Material material;
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
void main()
{
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 result = CalcDirLight(dirLight, norm, viewDir);
FragColor = vec4(result, 1.0);
}
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
return (ambient + diffuse + specular);
}
總體來說,着色器雖然較難入手,但是着色器可以很有效的幫助我們進行變換等其他操作,尤其是十分優秀的幾何着色器。