对应的课程是第九讲、第八讲、第七讲。
因为之前两次作业都是三角形,本次是有3D模型,作业框架在之前的基础上稍微增加了一些内容,其处理流程梳理如下:
TriangleList
数组里。shader
中的光的方向向量、视线的方向向量等都是需要基于真实坐标计算。bounding box
还有z-buffer
时有用;框架里列出了四种shader去完成,其中最重要的是布林冯shader还有凹凸贴图的shader。
fragment_shader_payload
作为函数输入。z
值,进行z-buffer
计算fragment_shader_payload
。fragment_shader_payload
的数据传进shader
里,即可求出该点的颜色。上面四个步骤中,前两个步骤不需要自己去做,第四个步骤前面的作业已经完成,因此本次作业的核心是完成shader的编写。
思考本流程,因为是对每一个三角形面都进行光栅化,因此貌似并不能并行?这只能cpu串行,那么怎么用gpu并行呢?希望以后来填这个坑。
具体的代码,该博客已经记录的比较完整GAMES101-现代计算机图形学学习笔记(作业03)。
此处记录一些自己犯过的易错点。
①图中所示的漫反射的光线向量、视线向量,一定注意,他们是跟光线、视线相反的方向!!都是物体表面指向光源和观测点。
②其次!这都是单位向量!!!后面求高光时要算半程向量,那个时候两个向量相加必须是单位向量。
③这个距离衰减,不能用向量l,因为它不能初始化。
高光和环境光都是全局不变的颜色,物体的颜色影响最大的是漫反射。如果有贴图,就是把贴图的颜色归一化(/255)后赋值为漫反射的系数Kd。
光栅化代码中所使用的view_pos
坐标其实在draw
函数里已经经过view
矩阵变换,也就是已经把相机移动原点了。所以按理说shader
中的观察点应该是原点,但是代码中还是给的相机的初始位置。
光栅化相关代码变成:
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
auto v = t.toVector4();
int x_min = (int)std::floor(std::min(v[0][0],std::min(v[1][0],v[2][0])));
int y_min = (int)std::floor(std::min(v[0][1],std::min(v[1][1],v[2][1])));
int x_max = (int)std::ceil(std::max(v[0][0],std::max(v[1][0],v[2][0])));
int y_max = (int)std::ceil(std::max(v[0][1],std::max(v[1][1],v[2][1])));
for (int x = x_min; x < x_max; ++x)
{
for (int y = y_min; y < y_max; ++y)
{
if (insideTriangle( (float)x+0.5, (float)y+0.5, t.v))
{
auto result = computeBarycentric2D(x, y, t.v);
float alpha;
float beta;
float gamma;
std::tie(alpha,beta,gamma) = result;
float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
zp *= Z;
if(depth_buf[get_index(x,y)] > zp)
{
depth_buf[get_index(x,y)] = zp;
auto interpolated_color = interpolate(alpha,beta,gamma,t.color[0],t.color[1],t.color[2],1);
//notice to do normalized
auto interpolated_normal = interpolate(alpha,beta,gamma,t.normal[0],t.normal[1],t.normal[2],1);
auto interpolated_texcoords = interpolate(alpha,beta,gamma,t.tex_coords[0],t.tex_coords[1],t.tex_coords[2],1);
auto interpolated_shadingcoords = interpolate(alpha,beta,gamma,view_pos[0],view_pos[1],view_pos[2],1);
fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
payload.view_pos = interpolated_shadingcoords;
//Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
auto pixel_color = fragment_shader(payload);
set_pixel(Eigen::Vector2i(x,y),pixel_color);
}
}
}
}
}
本段代码是纹理-布林冯模型,单纯的布林冯模型就只是把
texture_color
变成一个固定颜色值而已。
Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{
// TODO: Get the texture value at the texture coordinates of the current fragment
return_color = payload.texture->getColor(payload.tex_coords[0],payload.tex_coords[1]);
}
Eigen::Vector3f texture_color;
texture_color << return_color.x(), return_color.y(), return_color.z();
Eigen::Vector3f ka = Eigen::Vector3f(0.005f, 0.005f, 0.005f);
Eigen::Vector3f kd = texture_color / 255.f;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937f, 0.7937f, 0.7937f);
auto l1 = light{{20, 20, 20}, {500, 500, 500}};
auto l2 = light{{-20, 20, 0}, {500, 500, 500}};
std::vector<light> lights = {l1, l2};
Eigen::Vector3f amb_light_intensity{10, 10, 10};
Eigen::Vector3f eye_pos{0, 0, 10};
float p = 150;
Eigen::Vector3f color = texture_color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal;
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular*
// components are. Then, accumulate that result on the *result_color* object.
//光的方向
Eigen::Vector3f light_direc = (light.position - point).normalized();
//视线方向
Eigen::Vector3f eye_direc = (eye_pos - point).normalized();
//半程向量:视线方向和光线方向平均
Eigen::Vector3f half_vector = (light_direc + eye_direc).normalized();
//距离衰减因子
float r2 = (light.position - point).squaredNorm();
//环境光
Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
//漫反射
Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity / r2);
diffuse *= std::max(0.0f, normal.dot(light_direc));
//高光
Eigen::Vector3f specular = ks.cwiseProduct(light.intensity / r2);
specular *= std::pow(std::max(0.0f, normal.dot(half_vector)), p);
result_color += (ambient + diffuse + specular);
}
return result_color * 255.f;
}
这部分其实课程中并没有详细讲述。bump_fragment_shader
是把最后求得的法向量当成颜色,displacement_fragment_shader
是在上面求出新的法向量的基础上应用了布林冯模型。
选择这两个shader
时,main
函数中都会默认读取凹凸贴图。
凹凸向量是三个值,正好对应颜色RGB,所以可以放到一张图里。因为图里面只有UV坐标,跟3D模型怎么对应呢?
因为UV对应是物体的表面,也就是下图中所示的任意点都有一个表面坐标系称为TBN【tangant轴(T):切线轴;bitangent轴(B):副切线轴;及法线轴(N)】。
所以凹凸贴图里任意一(u,v)点的向量,都可以在物体表面放到该点的TBN坐标系中,凹凸贴图主要存储法向量,即N轴数据,所以在颜色空间中对应RGB中的B,也就是蓝色。
从这个图可以更好地表示,T轴和B轴,和贴图中的UV坐标轴方向是一致的。
shader数学基础之法线贴图切线空间这篇文章算是讲的还不错的。
Tutorial 13 : Normal Mapping这篇Opengl讲的也不错,但是……
感觉都跟代码框架里的对不上……
课程的BBS系统讨论区中,提供了一种快速计算TBN的方法。至少此方法计算的TBN三者是相互垂直的。作业框架里的三者都不是互相垂直的。
希望以后能填这个TBN的坑
作业中要求的bump_fragment_shader
代码,只不过是这个代码中不要布林冯部分的渲染,直接把normal
向量当成颜色而已。
Eigen::Vector3f displacement_fragment_shader(const fragment_shader_payload& payload)
{
Eigen::Vector3f ka = Eigen::Vector3f(0.005f, 0.005f, 0.005f);
Eigen::Vector3f kd = payload.color;
Eigen::Vector3f ks = Eigen::Vector3f(0.7937f, 0.7937f, 0.7937f);
auto l1 = light{{20, 20, 20}, {500, 500, 500}};
auto l2 = light{{-20, 20, 0}, {500, 500, 500}};
std::vector<light> lights = {l1, l2};
Eigen::Vector3f amb_light_intensity{10, 10, 10};
Eigen::Vector3f eye_pos{0, 0, 10};
float p = 150;
Eigen::Vector3f color = payload.color;
Eigen::Vector3f point = payload.view_pos;
Eigen::Vector3f normal = payload.normal;
float kh = 0.2f, kn = 0.1f;
// TODO: Implement displacement mapping here
float x = normal.x();
float y = normal.y();
float z = normal.z();
Eigen::Vector3f t(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
Eigen::Vector3f b = normal.cross(t);
Eigen::Matrix3f TBN;
TBN << t.x(), b.x(), normal.x(),
t.y(), b.y(), normal.y(),
t.z(), b.z(), normal.z();
float u = payload.tex_coords[0];
float v = payload.tex_coords[1];
float w = payload.texture->width;
float h = payload.texture->height;
float dU = kh * kn * (payload.texture->getColor(u + 1 / w, v).norm() - payload.texture->getColor(u, v).norm());
float dV = kh * kn * (payload.texture->getColor(u, v + 1 / h).norm() - payload.texture->getColor(u, v).norm());
Eigen::Vector3f ln(-dU, -dV, 1.0f);
point += (kn * normal * payload.texture->getColor(u, v).norm());
normal = (TBN * ln).normalized();
Eigen::Vector3f result_color = {0, 0, 0};
for (auto& light : lights)
{
// TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular*
//光的方向
Eigen::Vector3f light_direc = (light.position - point).normalized();
//视线方向
Eigen::Vector3f eye_direc = (eye_pos - point).normalized();
//半程向量:视线方向和光线方向平均
Eigen::Vector3f half_vector = (light_direc + eye_direc).normalized();
//距离衰减因子
float r2 = (light.position - point).squaredNorm();
//环境光
Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
//漫反射
Eigen::Vector3f diffuse = kd.cwiseProduct(light.intensity / r2);
diffuse *= std::max(0.0f, normal.dot(light_direc));
//高光
Eigen::Vector3f specular = ks.cwiseProduct(light.intensity / r2);
specular *= std::pow(std::max(0.0f, normal.dot(half_vector)), p);
result_color += (ambient + diffuse + specular);
}
return result_color * 255.f;
}