GAMES101作业(03)- Pipeline and Shading(Phone/纹理/位移/贴图着色器的实现与插值的使用)

作业来自官网

总览

在这次编程任务中,我们会进一步模拟现代图形技术。我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与 Fragment Shader,并且支持了纹理映射。

1 rasterize_triangle(const Triangle& t)

在此处实现与作业 2 类似的插值算法,实现法向量、颜色、纹理颜色的插值。

解答

大体上和上一个作业差不多,注意插值算法,源代码框架已经给出了interpolate插值函数,调用即可。

注意

auto[]是C++17的结构体绑定的用法, 简单理解就是结构体的成员变量会按顺序赋给auto中括号里的序列。
可以在vscode的C++扩展里更改配置,让其支持C++17的语法。

//还要带插值
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) 
{
    
    
    //获取t的3个顶点的向量值,存储到数组中。
    auto v = t.toVector4();
    Vector4f v_[3];
    for(int i = 0;i<3;i++){
    
    
        v_[i]={
    
    v[i].x(),v[i].y(),v[i].z(),v[i].w()};
    }
    // 先找包围盒
    float min_x,max_x,max_y,min_y;
    min_x=std::min(std::min(v[0].x(),v[1].x()),v[2].x());
    min_y=std::min(std::min(v[0].y(),v[1].y()),v[2].y());
    max_x=std::max(std::max(v[0].x(),v[1].x()),v[2].x());
    max_y=std::max(std::max(v[0].y(),v[1].y()),v[2].y());
    //循环找每一个像素
    for (int x = min_x; x < max_x; x++){
    
    
        for (int y = min_y; y < max_y; y++){
    
               
            if(insideTriangle((float)x+0.5f,(float)y+0.5f,v_)){
    
    
                //获得重心坐标的参数
                auto[alpha, beta, gamma] = computeBarycentric2D((float)x+0.5f,(float)y+0.5f, t.v);
                //获得zp(此部分在原代码框架中给出)
                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(zp<depth_buf[get_index(x,y)]){
    
    
                // TODO: Interpolate the attributes:
                    auto interpolated_color = interpolate(alpha,beta,gamma,t.color[0],t.color[1],t.color[2],1.0f);
                    //法线要归一化
                    auto interpolated_normal = interpolate(alpha,beta,gamma,t.normal[0],t.normal[1],t.normal[2],1.0f).normalized();
                    auto interpolated_texcoords = interpolate(alpha,beta,gamma,t.tex_coords[0],t.tex_coords[1],t.tex_coords[2],1.0f);
                    auto interpolated_shadingcoords = interpolate(alpha,beta,gamma,view_pos[0],view_pos[1],view_pos[2],1.0f);

                    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);
                    depth_buf[get_index(x,y)]=zp;
                    
                    set_pixel(Vector2i(x,y),pixel_color);
                }
            }
           
        }
    }
}

2 get_projection_matrix()

将你自己在之前的实验中实现的投影矩阵填到此处,此时你可以运行 ./Rasterizer output.png normal 来观察法向量实现结果。

解答

把以前的粘过来即可。

注意

远平面拉伸时把最后一行向量改成了(0,0,-1,0)修改了以前上下颠倒的问题。

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
    
    
    
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
    //求zNear和eye_fov高度
    float height=tan(eye_fov/2)*abs(zNear)*2.0;
    //由寬高比求寬度
    float width = aspect_ratio*height;
    //縮放到正則立方體
    projection<<
    2.0/width,0,0,0,
    0,2.0f/height,0,0,
    0,0,2.0f/(zFar-zNear),0,
    0,0,0,1;
    //將立方體中心平移到原點(xy方向無需平移)
    Eigen::Matrix4f tmp=Eigen::Matrix4f::Identity();
    tmp<<1,0,0,0,
    0,1,0,0,
    0,0,1,-(zNear+zFar)/2.0,
    0,0,0,1;
    projection=projection*tmp;
    //進行遠平面的拉伸
    tmp<<zNear,0,0,0,
    0,zNear,0,0,
    0,0,zNear+zFar,-zNear*zFar,
    0,0,-1,0;
    projection=projection*tmp;
    return projection;
}

## 结果

3 phong_fragment_shader()

实现 Blinn-Phong 模型计算 Fragment Color。

扫描二维码关注公众号,回复: 13464725 查看本文章

解答

  1. 计算的是最后呈现的rgb颜色,只需要按照公式计算环境光、漫反射、高光然后相加即可。
  2. 光源的结构体第二个向量是光强,3维向量代表rgb通道的分量。

注意

  1. 注意环境光不要放在循环里,它算一遍即可。
  2. 高光最后要进行p次幂。
  3. 是和0max之后在p次幂,不是先p次幂再max…(弄错了之后我后来扰动法线的时候一堆不对的光点。)
Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
    
    
    //环境光Ambient系数
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    //漫反射Diffuse系数
    Eigen::Vector3f kd = payload.color;
    //高光Specular系数
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    //定义两个光源
    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;

    Eigen::Vector3f result_color = {
    
    0, 0, 0};
    
    Eigen::Vector3f ambient={
    
    0,0,0};
    Eigen::Vector3f diffuse={
    
    0,0,0};
    Eigen::Vector3f specular={
    
    0,0,0};
    for (auto& light : lights)
    {
    
    
        //获得物体与光源距离的平方
        float r_squared=(light.position-payload.view_pos).squaredNorm();
        //计算每一个光的漫反射
        diffuse+=kd.cwiseProduct(light.intensity/r_squared)*MAX(0,normal.dot(((light.position-point).normalized())));
        //获取入射与反射光的半程向量
        auto h=((light.position-point).normalized()+(eye_pos-point).normalized()).normalized();
        //计算高光(这里一定要注意p次幂)
        specular+=ks.cwiseProduct(light.intensity/r_squared)*std::pow(std::max(0.0f, normal.dot(h)), p);
    }
    //计算环境光(注意cwiseProduct函数是对应点相乘)
    ambient=ka.cwiseProduct(amb_light_intensity);
    //将三种光相加,获得结果
    result_color=ambient+diffuse+specular;
    return result_color * 255.f;
}

结果

环境光:
在这里插入图片描述
漫反射:
在这里插入图片描述
高光:
在这里插入图片描述
相加后的最终Bling-Phone模型结果:

在这里插入图片描述

4 texture_fragment_shader()

在实现 Blinn-Phong的基础上,将纹理颜色视为公式中的 kd,实现 Texture Shading Fragment Shader.

解答

  1. 使用uv坐标调用纹理get_color函数。
  2. uv坐标是rasterize_triangle函数中插值得到的。
  3. 用对应点纹理的颜色替换漫反射kd系数。

注意

有些uv不在0到1内(可能是插值的锅),容易出现段错误,加个限定范围就好了。

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
    
    
    Eigen::Vector3f return_color = {
    
    0, 0, 0};
    if (payload.texture)
    {
    
    
        // std::cout<<payload.tex_coords.x()<<" "<<payload.tex_coords.y()<<std::endl;
        //这里一直段错误,一输出发现uv坐标居然有大于1的,所以这么处理(应该是纹理坐标插值时有一点微弱的超界)
        return_color=payload.texture->getColor(MIN(payload.tex_coords.x(),1),MIN(payload.tex_coords.y(),1));
    }
    Eigen::Vector3f texture_color;
    texture_color << return_color.x(), return_color.y(), return_color.z();

    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = texture_color / 255.f;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    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};
    Eigen::Vector3f ambient={
    
    0,0,0};
    Eigen::Vector3f diffuse={
    
    0,0,0};
    Eigen::Vector3f specular={
    
    0,0,0};
    for (auto& light : lights)
    {
    
    
        //获得物体与光源距离的平方
        float r_squared=(light.position-payload.view_pos).squaredNorm();        
        //计算每一个光的漫反射
        diffuse+=kd.cwiseProduct(light.intensity/r_squared)*MAX(0,normal.dot(((light.position-point).normalized())));
        //获取入射与反射光的半程向量
        auto h=(light.position-point+eye_pos-point).normalized();
        //计算高光
        specular+=ks.cwiseProduct(light.intensity/r_squared)*std::pow(std::max(0.0f, normal.dot(h)), p);
    }
    //计算环境光(注意cwiseProduct函数是对应点相乘)
    ambient=ka.cwiseProduct(amb_light_intensity);
    //将三种光相加,获得结果
    result_color=ambient+diffuse+specular;
    return result_color * 255.f;
}

结果

在这里插入图片描述

5 bump_fragment_shader()

在实现 Blinn-Phong 的基础上,仔细阅读该函数中的注释,实现 Bump mapping。

解答

重点 TBN矩阵和凹凸贴图
这里是我这次作业最卡人的地方!!!

  1. 先根据原本的法线来获得TBN向量,形成TBN变换矩阵,这个矩阵是将切线空间坐标转换到模型空间中用的!(TBN其实是切线空间的基向量,这一步实际上应该是做了一个基变换。)
  2. 注意是凹凸贴图,不是法线贴图,所以要先求du、dv然后才能获得法线。

注意

  1. 注意归一化。
  2. 最后获得的是扰动过的法线,在下一个函数中,就是基于此再计算的。
  3. main函数里少东西,大概,没设置hmap纹理。后面位移贴图也有这问题。
Eigen::Vector3f bump_fragment_shader(const fragment_shader_payload& payload)
{
    
    
    //注意这里payload关联的贴图应该是凹凸贴图
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    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.2, kn = 0.1;

    //n是原来模型空间的法线
    Eigen::Vector3f n=normal;
    float x = n.x(),y=n.y(),z=n.z();
    //计算得到法线空间的基向量t和b(法线空间中,n与模型空间一致)
    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=n.cross(t);
    Eigen::Matrix3f TBN;
    TBN.col(0)=t;
    TBN.col(1)=b;
    TBN.col(2)=n;
    // std::cout<<"TBN矩阵:"<<TBN<<std::endl;
    float u=payload.tex_coords.x(),v=payload.tex_coords.y();
    float w=payload.texture->width,h=payload.texture->height;
    //注意这里是.norm 求范数
    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);
    //将它转换到模型空间中
    // std::cout<<"转换前的法线:"<<normal<<std::endl;

    normal = (TBN*ln).normalized();
    // std::cout<<"转换后的法线:"<<normal<<std::endl;
    Eigen::Vector3f result_color = {
    
    0, 0, 0};
    result_color = normal;

    return result_color * 255.f;
}

结果

在这里插入图片描述

6 displacement_fragment_shader()

在实现 Bump mapping 的基础上,实现 displacement mapping。

解答

  1. 所谓位移着色器,就是要确实的让每个点先移动,然后再计算着色。这个移动的根据,就是原本的(不是计算后的,我这错了之后凹凸的过分)法线和凹凸贴图。
  2. 注意这些把bump着色器和phone着色器拼在一起即可。

注意

这里才真的能看出之前算的对不对。

Eigen::Vector3f displacement_fragment_shader(const fragment_shader_payload& payload)
{
    
    
    
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    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.2, kn = 0.1;
    
    //n是原来模型空间的法线
    Eigen::Vector3f n=normal;
    float x = n.x(),y=n.y(),z=n.z();
    //计算得到法线空间的基向量t和b(法线空间中,n与模型空间一致)
    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=n.cross(t);
    Eigen::Matrix3f TBN;
    TBN.col(0)=t;
    TBN.col(1)=b;
    TBN.col(2)=n;
    // std::cout<<"TBN矩阵:"<<TBN<<std::endl;
    float u=payload.tex_coords.x(),v=payload.tex_coords.y();
    float w=payload.texture->width,h=payload.texture->height;
    //注意这里是.norm 求范数
    float dU=kh*kn*(payload.texture->getColor(u+1.0/w,v).norm()-payload.texture->getColor(u,v).norm());
    float dV=kh*kn*(payload.texture->getColor(u,v+1.0/h).norm()-payload.texture->getColor(u,v).norm());
    //获得切线空间中法线的坐标
    Eigen::Vector3f ln(-dU,-dV,1);
    ln.normalize();
    //将它转换到模型空间中
    // std::cout<<"转换前的法线:"<<normal<<std::endl;
    point = point + kn*n*(payload.texture->getColor(u,v).norm());
    normal = (TBN*ln).normalized();
    // TODO: Implement displacement mapping here
    // Position p = p + kn * n * h(u,v)

    
    Eigen::Vector3f result_color = {
    
    0, 0, 0};

    Eigen::Vector3f ambient={
    
    0,0,0};
    Eigen::Vector3f diffuse={
    
    0,0,0};
    Eigen::Vector3f specular={
    
    0,0,0};
    for (auto& light : lights)
    {
    
    
        //获得物体与光源距离的平方
        float r_squared=(light.position-point).squaredNorm();
        //计算每一个光的漫反射
        diffuse+=kd.cwiseProduct(light.intensity/r_squared)*MAX(0,normal.dot(((light.position-point).normalized())));
        //获取入射与反射光的半程向量
        auto h=((light.position-point).normalized()+(eye_pos-point).normalized()).normalized();
        //计算高光(这里一定要注意p次幂)
        Eigen::Vector3f light_dir = light.position - point;
		Eigen::Vector3f view_dir = eye_pos - point;
        specular+= ks.cwiseProduct(light.intensity/r_squared)*std::pow(std::max(0.0f, normal.dot(h)), p);
    }
    //计算环境光(注意cwiseProduct函数是对应点相乘)
    ambient=ka.cwiseProduct(amb_light_intensity);
    //将三种光相加,获得结果
    result_color=ambient + diffuse + specular;

    return result_color * 255.f;
}

结果

最开始两次由于point计算和高光计算有偏差,得到了奇妙的效果。
位移+高光错误

高光错误
修改后:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43399489/article/details/121425381