PBR基于物理的渲染可以實現(xiàn)更加真實的效果,其Shader值得分析一下。但PBR需要較多的基礎(chǔ)知識,不適合不會OpenGL的朋友。
一、PBR理論
PBR指基于物理的渲染,其理論較多,需要的基礎(chǔ)知識也較多,我在這就不再寫一遍了,具體可以參看:
LearnOpenGL PBR理論-英文 或者 LearnOpenGL PBR理論-中文
Overload也提供了這種材料,借助貼圖可以實現(xiàn)非常真實的材質(zhì)效果。下面這個例子的貼圖來自LearnOpenGL,大家可以自己去下載。
二、PBR Shader分析
頂點著色器
#shader vertex
#version 430 core
layout (location = 0) in vec3 geo_Pos;
layout (location = 1) in vec2 geo_TexCoords;
layout (location = 2) in vec3 geo_Normal;
layout (location = 3) in vec3 geo_Tangent;
layout (location = 4) in vec3 geo_Bitangent;
/* Global information sent by the engine */
layout (std140) uniform EngineUBO
{
mat4 ubo_Model;
mat4 ubo_View;
mat4 ubo_Projection;
vec3 ubo_ViewPos;
float ubo_Time;
};
/* Information passed to the fragment shader */
out VS_OUT
{
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
mat3 TBN;
flat vec3 TangentViewPos;
vec3 TangentFragPos;
} vs_out;
void main()
{
vs_out.TBN = mat3
(
normalize(vec3(ubo_Model * vec4(geo_Tangent, 0.0))),
normalize(vec3(ubo_Model * vec4(geo_Bitangent, 0.0))),
normalize(vec3(ubo_Model * vec4(geo_Normal, 0.0)))
);
mat3 TBNi = transpose(vs_out.TBN);
vs_out.FragPos = vec3(ubo_Model * vec4(geo_Pos, 1.0));
vs_out.Normal = normalize(mat3(transpose(inverse(ubo_Model))) * geo_Normal);
vs_out.TexCoords = geo_TexCoords;
vs_out.TangentViewPos = TBNi * ubo_ViewPos;
vs_out.TangentFragPos = TBNi * vs_out.FragPos;
gl_Position = ubo_Projection * ubo_View * vec4(vs_out.FragPos, 1.0);
}
頂點著色器基本與standard材質(zhì)一致,這里就不再分析了,具體可看standard材質(zhì)Shader
片元著色器:
#shader fragment
#version 430 core
/** 模型視圖矩陣、攝像機位置,使用UBO傳入 */
/* Global information sent by the engine */
layout (std140) uniform EngineUBO
{
mat4 ubo_Model;
mat4 ubo_View;
mat4 ubo_Projection;
vec3 ubo_ViewPos;
float ubo_Time;
};
/* 頂點著色器的輸出 */
/* Information passed from the fragment shader */
in VS_OUT
{
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
mat3 TBN;
flat vec3 TangentViewPos;
vec3 TangentFragPos;
} fs_in;
/* 光源數(shù)據(jù)用SSBO傳入 */
/* Light information sent by the engine */
layout(std430, binding = 0) buffer LightSSBO
{
mat4 ssbo_Lights[];
};
out vec4 FRAGMENT_COLOR;
uniform sampler2D u_AlbedoMap; // 反照率貼圖
uniform sampler2D u_MetallicMap; // 金屬度貼圖
uniform sampler2D u_RoughnessMap; // 粗糙度貼圖
uniform sampler2D u_AmbientOcclusionMap; // 環(huán)境光遮蔽貼圖
uniform sampler2D u_NormalMap; // 法線貼圖
uniform vec4 u_Albedo = vec4(1.0); // 反照率系數(shù),控制反照率貼圖的權(quán)重
uniform vec2 u_TextureTiling = vec2(1.0, 1.0);
uniform vec2 u_TextureOffset = vec2(0.0, 0.0);
uniform bool u_EnableNormalMapping = false; // 是否使用法線貼圖
uniform float u_HeightScale = 0.0;
uniform float u_Metallic = 1.0; // 金屬度
uniform float u_Roughness = 1.0; // 粗糙度
const float PI = 3.14159265359;
// 計算法向分布函數(shù)D,使用Trowbridge-Reitz GGX
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return num / denom;
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float num = NdotV;
float denom = NdotV * (1.0 - k) + k;
return num / denom;
}
// Smith’s method
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// 菲涅爾項,使用Fresnel-Schlick方程
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
/* 將32位數(shù)字變成RGBA顏色 */
vec3 UnPack(float p_Target)
{
return vec3
(
// CPU傳入的數(shù)據(jù)是0-255,轉(zhuǎn)換成0-1.0
float((uint(p_Target) >> 24) & 0xff) * 0.003921568627451,
float((uint(p_Target) >> 16) & 0xff) * 0.003921568627451,
float((uint(p_Target) >> 8) & 0xff) * 0.003921568627451
);
}
bool PointInAABB(vec3 p_Point, vec3 p_AabbCenter, vec3 p_AabbHalfSize)
{
return
(
p_Point.x > p_AabbCenter.x - p_AabbHalfSize.x && p_Point.x < p_AabbCenter.x + p_AabbHalfSize.x &&
p_Point.y > p_AabbCenter.y - p_AabbHalfSize.y && p_Point.y < p_AabbCenter.y + p_AabbHalfSize.y &&
p_Point.z > p_AabbCenter.z - p_AabbHalfSize.z && p_Point.z < p_AabbCenter.z + p_AabbHalfSize.z
);
}
/*光照衰減系數(shù),LearnOpenGL中有具體公式*/
float LuminosityFromAttenuation(mat4 p_Light)
{
const vec3 lightPosition = p_Light[0].rgb;
const float constant = p_Light[0][3];
const float linear = p_Light[1][3];
const float quadratic = p_Light[2][3];
const float distanceToLight = length(lightPosition - fs_in.FragPos);
const float attenuation = (constant + linear * distanceToLight + quadratic * (distanceToLight * distanceToLight));
return 1.0 / attenuation;
}
/* 盒狀環(huán)境光 */
vec3 CalcAmbientBoxLight(mat4 p_Light)
{
const vec3 lightPosition = p_Light[0].rgb;
const vec3 lightColor = UnPack(p_Light[2][0]);
const float intensity = p_Light[3][3];
const vec3 size = vec3(p_Light[0][3], p_Light[1][3], p_Light[2][3]);
return PointInAABB(fs_in.FragPos, lightPosition, size) ? lightColor * intensity : vec3(0.0);
}
/* 球狀環(huán)境光 */
vec3 CalcAmbientSphereLight(mat4 p_Light)
{
const vec3 lightPosition = p_Light[0].rgb;
const vec3 lightColor = UnPack(p_Light[2][0]);
const float intensity = p_Light[3][3];
const float radius = p_Light[0][3];
return distance(lightPosition, fs_in.FragPos) <= radius ? lightColor * intensity : vec3(0.0);
}
void main()
{
vec2 texCoords = u_TextureOffset + vec2(mod(fs_in.TexCoords.x * u_TextureTiling.x, 1), mod(fs_in.TexCoords.y * u_TextureTiling.y, 1));
vec4 albedoRGBA = texture(u_AlbedoMap, texCoords) * u_Albedo; // Albedo反照率貼圖數(shù)據(jù)
vec3 albedo = pow(albedoRGBA.rgb, vec3(2.2)); // 這種反照率處理方式與LearOpenGL一致
float metallic = texture(u_MetallicMap, texCoords).r * u_Metallic; // 金屬度
float roughness = texture(u_RoughnessMap, texCoords).r * u_Roughness; // 粗糙度
float ao = texture(u_AmbientOcclusionMap, texCoords).r; // 環(huán)境光遮蔽AO
vec3 normal;
if (u_EnableNormalMapping) // 是否使用法線貼圖
{
normal = texture(u_NormalMap, texCoords).rgb; // 法線貼圖的原始值
normal = normalize(normal * 2.0 - 1.0); // 法線貼圖矢量坐標(biāo)范圍變成-1到1
normal = normalize(fs_in.TBN * normal); // 變換到全局坐標(biāo)系下
}
else
{
normal = normalize(fs_in.Normal); // 使用頂點著色器輸出的法線
}
vec3 N = normalize(normal);
vec3 V = normalize(ubo_ViewPos - fs_in.FragPos); // 計算視線方向
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic); // 插值方式得到平面的基礎(chǔ)反射率F0
// reflectance equation
vec3 Lo = vec3(0.0);
vec3 ambientSum = vec3(0.0); // 環(huán)境光結(jié)果
for (int i = 0; i < ssbo_Lights.length(); ++i)
{
// 兩種環(huán)境光燈光
if (int(ssbo_Lights[i][3][0]) == 3)
{
ambientSum += CalcAmbientBoxLight(ssbo_Lights[i]);
}
else if (int(ssbo_Lights[i][3][0]) == 4)
{
ambientSum += CalcAmbientSphereLight(ssbo_Lights[i]);
}
else
{
// calculate per-light radiance
// 光源方向
vec3 L = int(ssbo_Lights[i][3][0]) == 1 ? -ssbo_Lights[i][1].rgb : normalize(ssbo_Lights[i][0].rgb - fs_in.FragPos);
vec3 H = normalize(V + L);// 半程向量
float distance = length(ssbo_Lights[i][0].rgb - fs_in.FragPos);
float lightCoeff = 0.0; // 最終到片元處的光強系數(shù)
switch(int(ssbo_Lights[i][3][0]))
{
case 0:
lightCoeff = LuminosityFromAttenuation(ssbo_Lights[i]) * ssbo_Lights[i][3][3]; // 點光源要考慮隨距離衰減
break;
case 1:
lightCoeff = ssbo_Lights[i][3][3]; // 方向光無衰減
break;
// 聚光燈的計算
case 2:
const vec3 lightForward = ssbo_Lights[i][1].rgb;
const float cutOff = cos(radians(ssbo_Lights[i][3][1]));
const float outerCutOff = cos(radians(ssbo_Lights[i][3][1] + ssbo_Lights[i][3][2]));
const vec3 lightDirection = normalize(ssbo_Lights[i][0].rgb - fs_in.FragPos);
const float luminosity = LuminosityFromAttenuation(ssbo_Lights[i]);
/* Calculate the spot intensity */
const float theta = dot(lightDirection, normalize(-lightForward));
const float epsilon = cutOff - outerCutOff;
const float spotIntensity = clamp((theta - outerCutOff) / epsilon, 0.0, 1.0);
lightCoeff = luminosity * spotIntensity * ssbo_Lights[i][3][3];
break;
}
vec3 radiance = UnPack(ssbo_Lights[i][2][0]) * lightCoeff;
// cook-torrance brdf
float NDF = DistributionGGX(N, H, roughness); // 法線分布函數(shù)
float G = GeometrySmith(N, V, L, roughness); // 幾何函數(shù)
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); // 菲涅爾項
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = numerator / max(denominator, 0.001);
// add to outgoing radiance Lo
float NdotL = max(dot(N, L), 0.0);
Lo += (kD * albedo / PI + specular) * radiance * NdotL;
}
}
vec3 ambient = ambientSum * albedo * ao;// 環(huán)境光最終貢獻
vec3 color = ambient + Lo; // 環(huán)境光與cook-torrance模型累加
// HDR色調(diào)映射
color = color / (color + vec3(1.0));
// gamma 矯正
color = pow(color, vec3(1.0/2.2));
FRAGMENT_COLOR = vec4(color, albedoRGBA.a); // alpha使用反照率貼圖
}
Fragment Shader大體分為三部分:
- 從貼圖中獲取反照率、金屬度、粗糙度、法線數(shù)據(jù)
- 計算燈光光照,環(huán)境光燈光只影響環(huán)境光;方向光、聚光燈、點光源會影響光強lightCoeff,最終的光照使用cook-torrance模型進行計算,公式可以參考LearnOpenGL
- 最后進行環(huán)境光與PBR模型結(jié)果進行疊加,并進行色調(diào)映射與gamma矯正,這里使用的公式在LearnOpenGL中都有的
總結(jié):
這個PBR Shader整體上與LearnOpenGL中的理論一致,看完LearnOpenGL之后再看這個Shader就比較簡單了。文章來源:http://www.zghlxwxcb.cn/news/detail-716063.html
完結(jié)總結(jié):
寫的這里,這個專欄暫時告一段落了,主要分析了Overload的Render模塊,其他的包括UI、物理引擎、音頻等模塊沒有涉及。Overload是一個Demo性質(zhì)的游戲引擎,其渲染涉只涉及最基礎(chǔ)的渲染方式,是對OpenGL簡單封裝,遠遠滿足不了實際游戲開發(fā)需求,只能作為渲染引擎入門。
另外,這個專欄的文章只聚焦一些細節(jié),對應(yīng)架構(gòu)涉及很少,因為本人發(fā)現(xiàn)架構(gòu)方面的文章參考性不大,一旦一個軟件定型架構(gòu)方面的改動很困難,讀了軟件架構(gòu)的文章也很難在工作中用上。故單純只介紹一個技術(shù)點反而可能拿來直接使用。最后希望能對大家有所幫助!文章來源地址http://www.zghlxwxcb.cn/news/detail-716063.html
到了這里,關(guān)于【Overload游戲引擎細節(jié)分析】PBR材質(zhì)Shader---完結(jié)篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!