LearnOpenGL lighting中的PBR
教程:光照 - LearnOpenGL CN (learnopengl-cn.github.io)
源代码¶
1.1.pbr.vs¶
#version 330 core
layout (location = 0) in vec3 aPos; //点位置(模型坐标系)
layout (location = 1) in vec3 aNormal; //法向量(模型坐标系)
layout (location = 2) in vec2 aTexCoords; //UV
out vec2 TexCoords;
out vec3 WorldPos;
out vec3 Normal;
uniform mat4 projection; //投影矩阵
uniform mat4 view; //视图矩阵
uniform mat4 model; //模型矩阵
void main()
{
TexCoords = aTexCoords; //UV
WorldPos = vec3(model * vec4(aPos, 1.0)); //点位置(世界坐标系)
Normal = mat3(model) * aNormal; //法向量(世界坐标系)
//注:mat3(model)是为了去掉平移值,只做旋转与缩放等变换,因为它是向量,不是坐标点
gl_Position = projection * view * vec4(WorldPos, 1.0); //点位置(标准化设备坐标)
}
1.1.pbr.fs¶
#version 330 core
out vec4 FragColor;
in vec2 TexCoords; //UV
in vec3 WorldPos; //片元位置(世界坐标)
in vec3 Normal; //法向量(世界坐标)
//材质参数
// material parameters
uniform vec3 albedo; //反照率
uniform float metallic; //金属度
uniform float roughness; //粗糙度
uniform float ao; //环境光遮蔽
// lights
uniform vec3 lightPositions[4]; //灯光位置
uniform vec3 lightColors[4]; //灯光颜色(实际上不是颜色,而是光的辐射通量,用RGB三色编码来表示)
uniform vec3 camPos; //摄像机位置(世界坐标系)
const float PI = 3.14159265359;
// ----------------------------------------------------------------------------
//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 nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
// ----------------------------------------------------------------------------
//计算Schlick-GGX
float GeometrySchlickGGX(float NdotV, float roughness)
{
//k是roughness的重映射,本代码使用直接光照
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
// ----------------------------------------------------------------------------
//几何函数Smith
// 微平面间相互遮蔽的比率,这种相互遮蔽会损耗光线的能量
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;
}
// ----------------------------------------------------------------------------
//菲涅尔函数
// 被反射的光线对比光线被折射的部分所占的比率
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
// ----------------------------------------------------------------------------
void main()
{
vec3 N = normalize(Normal); //法向量(世界坐标系)
vec3 V = normalize(camPos - WorldPos); //光线出射方向、视线方向(指向摄像机,世界坐标系)
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)
//计算正常入射时的反射率
//1. 如果介质电(如塑料)使用F0为0.04
//2. 如果是金属,F0=albedo(金属工作流程)
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
//反射率方程
// reflectance equation
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i)
{
// calculate per-light radiance
vec3 L = normalize(lightPositions[i] - WorldPos); //光线入射方向(指向光源,世界坐标系)
vec3 H = normalize(V + L); //半程向量
float distance = length(lightPositions[i] - WorldPos); //与光源的距离
float attenuation = 1.0 / (distance * distance); //光源衰减程度(距离越远,衰减越严重)
vec3 radiance = lightColors[i] * attenuation; //辐射率,光源打在此片元上的总能量
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness); //法线方程
float G = GeometrySmith(N, V, L, roughness); //几何函数
vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0); //菲涅尔函数
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zero
vec3 specular = numerator / denominator; //Cook-Torrance BRDF 镜面反射部分的值
// kS is equal to Fresnel
//kS(镜面反射系数) = 菲涅尔项
vec3 kS = F;
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
//为了能量守恒,kD+kS应等于1
//因此kD(漫反射)
vec3 kD = vec3(1.0) - kS;
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
//非金属才有漫反射(金属没有漫反射),因此kD需乘上逆金属度
kD *= 1.0 - metallic;
// scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// add to outgoing radiance Lo
//lambert = albedo / PI
Lo += (kD * albedo / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
}
// ambient lighting (note that the next IBL tutorial will replace
// this ambient lighting with environment lighting).
//环境照明,在下一个IBL中教程中,将用环境光来实现
vec3 ambient = vec3(0.03) * albedo * ao;
//最终的颜色
vec3 color = ambient + Lo;
// HDR tonemapping
//HDR技术之tone mapping
color = color / (color + vec3(1.0));
// gamma correct
//gamma矫正
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}
main.cpp¶
//...
int main()
{
// build and compile shaders
// -------------------------
Shader shader("1.1.pbr.vs", "1.1.pbr.fs");
shader.use();
shader.setVec3("albedo", 0.5f, 0.0f, 0.0f);
shader.setFloat("ao", 1.0f);
// lights
// ------
glm::vec3 lightPositions[] = {
glm::vec3(-10.0f, 10.0f, 10.0f),
glm::vec3( 10.0f, 10.0f, 10.0f),
glm::vec3(-10.0f, -10.0f, 10.0f),
glm::vec3( 10.0f, -10.0f, 10.0f),
};
glm::vec3 lightColors[] = {
glm::vec3(300.0f, 300.0f, 300.0f),
glm::vec3(300.0f, 300.0f, 300.0f),
glm::vec3(300.0f, 300.0f, 300.0f),
glm::vec3(300.0f, 300.0f, 300.0f)
};
int nrRows = 7;
int nrColumns = 7;
while (!glfwWindowShouldClose(window))
{
for (int row = 0; row < nrRows; ++row)
{
shader.setFloat("metallic", (float)row / (float)nrRows);
for (int col = 0; col < nrColumns; ++col)
{
// we clamp the roughness to 0.05 - 1.0 as perfectly smooth surfaces (roughness of 0.0) tend to look a bit off
// on direct lighting.
shader.setFloat("roughness", glm::clamp((float)col / (float)nrColumns, 0.05f, 1.0f));
//...
renderSphere();
}
}
}
return 0;
}