0.渲染管道
渲染管道通过EBO向顶点着色器(Vertex Shader)输入模型顶点坐标、法线向量、贴图坐标等信息。
//EBO示例代码
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
GL_STATIC_DRAW);
cpu计算的光源坐标、模型矩阵、相机矩阵、透视矩阵等数据通过uniform传入渲染管道中。
//uniform示例代码
int vertexColorLocation = glGetUniformLocation(shaderProgram,
"ourColor");
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
然后片元着色器(Fragment Shader)通过从顶点着色器和Uniform传入的贴图坐标、顶点坐标和渲染颜色等信息,逐个计算每一个片元的渲染颜色值,WebGL、Three.js、Cesium等的自定义Shader主要就是自定义片元着色器的GLSL代码。
//顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos; // position has attribute position 0
out vec4 vertexColor; // specify a color output to the fragment shader
void main()
{
gl_Position = vec4(aPos, 1.0); // we give a vec3 to vec4’s constructor
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // output variable to dark-red
}
//片元着色器
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // input variable from vs (same name and type)
void main()
{
FragColor = vertexColor;
}
通常一个渲染管道(ShaderProgram,包含一套顶点着色器和片元着色器代码)负责渲染一个独立的物体,在渲染多个物体时,需依次绑定这些物体使用的渲染管道(也可能使用相同的渲染管道),并向管道传入属于该物体的模型顶点坐标、变换矩阵、顶点法线向量等信息。
1.模型及相机位姿变换
渲染管线中一共有5种坐标系,主要的3种是本地坐标系:记录三维模型顶点坐标;世界坐标系:记录三维场景中的物体的位姿;相机坐标系:从相机观察视角记录物体位姿。经过一系列转换可将模型渲染到相机成像平面。
Vclip = Mprojection ·Mview · Mmodel ·Vlocal
Vlocal是模型文件中存储的顶点坐标,Mmodel是根据模型在世界坐标系中的位姿计算的变换矩阵,Mview是根据相机在世界坐标系中的位姿计算的相机变换矩阵,Mprojection是根据相机内参计算的透视投影矩阵,将模型投影到平面进行渲染。
//cpu计算用于渲染的位姿矩阵
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(-55.0f),
glm::vec3(1.0f, 0.0f, 0.0f));
glm::mat4 view = glm::mat4(1.0f);
// note that we’re translating the scene in the reverse direction
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f,
100.0f);
//顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// note that we read the multiplication from right to left
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
2.光照
光照主要包含3种类型,环境光:即无直射光时的暗光颜色;漫反射光:直射光对粗糙物体的光照颜色;镜面反射光:直射光对镜面物体的光照颜色。三种光组合起来模拟真实环境的光照效果。
环境光:
//片元着色器片段
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
环境光渲染效果,白色方块示意光源颜色
漫反射光:
漫反射光首先计算光源到片元位置的光照方向,然后结合从VBO传入的顶点法线向量,计算出光线入射片元平面的角度,片元的法线向量和入射光线方向的角度越大,片元的光照亮度越弱。
//顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 FragPos;
out vec3 Normal;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
}
片元着色器中光源坐标和光源颜色通过uniform传入
//片元着色器,漫反射+环境光
in vec3 Normal;
in vec3 FragPos;
uniform vec3 lightPos;
uniform vec3 lightColor;
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);
}
漫反射+环境光渲染效果
镜面反射光:
相比漫反射光照强度计算,镜面反射的光照强度计算需要多引入相机坐标,光源在片元上反射的射出方向和相机到片元的观察方向之间的角度越小,光照强度越大。
//片元着色器,漫反射+环境光+反射光
in vec3 Normal;
in vec3 FragPos;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 viewPos;
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}
上述代码中spec计算过程的数字32指的是当前片元的反射强度(光泽),光泽数字越大,镜面效果越强,即物体上的高光范围越小。
从顶点着色器通过out传入片元着色器in的顶点信息,例如上文中顶点坐标和顶点法线向量FragPos和Normal,会进行插值计算出片元处平滑的数据。上文光照渲染将顶点坐标、顶点法线向量、相机坐标和视点坐标传入到片元着色器中,在每一个片元中使用从顶点数据插值计算出的数据进行渲染能够得到平滑真实的效果,若将光照强度在顶点着色器中进行计算,片元着色器使用从顶点着色器插值估算的光照强度进行渲染,能够降低渲染开销,但真实性欠佳。
3.材质
材质描述的是模型片元对光照的反应,包含4个参数: 环境光颜色、漫反射颜色、镜面反射颜色和镜面反射光泽强度。
如下片元着色器代码将整个模型通过uniform设置为同一种材质
//片元着色器片段,整个物体通过uniform设置为同一种材质
#version 330 core
...
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
//设置三种情况光源颜色
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
uniform Material material;
void main()
{
...
// ambient
vec3 ambient = light.ambient * material.ambient;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse* (diff * material.diffuse);
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0),
material.shininess);
vec3 specular = light.specular* (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
材质贴图
材质贴图包含漫反射贴图和镜面反射贴图,漫反射贴图提供环境光和漫反射光照射的颜色,镜面反射贴图提供镜面反射的颜色,通过贴图的方式代替上文的单一材质。
//顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
TexCoords = aTexCoords;
}
//片元着色器
#version 330 core
in vec2 TexCoords;
in vec3 Normal;
in vec3 FragPos;
uniform vec3 viewPos;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
//设置三种情况光源颜色
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
uniform Material material;
void main()
{
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position- FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
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));
FragColor = vec4(ambient + diffuse + specular, 1.0);
}