games101 作業3分析 詳解bump mapping

dyccyber發表於2024-08-10

games101 作業3分析 詳解bump mapping

程式碼分析

整體程式碼結構 其實變化還是不大 主要是引入了vertexshader(什麼都沒做) 與 fragmentshader(使用了不同的著色方法 直接用法線作為rgb 使用blingphong光照模型 紋理貼圖 bumpmapping displacementmapping)

主要變化在光柵化部分 著色計算使用特定的fragmentshader
這裡我之前寫的程式碼有問題 boundingbox沒有覆蓋邊界 導致第一次出來的牛牛不同的三角形之間有縫隙hhh
注意這裡使用的w值不再是之前錯誤的1 而是真實深度 但是我感覺這裡插值使用的公式還是沒有矯正過的 不過整體影響不大

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) 
{
    //這裡不要使用toVector4 會導致深度值直接賦值為1
    auto v = t.v;
    int XMin = std::min(std::min(v[0].x(), v[1].x()), v[2].x());
    int XMax = std::max(std::max(v[0].x(), v[1].x()), v[2].x());
    int YMin = std::min(std::min(v[0].y(), v[1].y()), v[2].y());
    int YMax = std::max(std::max(v[0].y(), v[1].y()), v[2].y());
    for (int x = XMin; x <= XMax; x++) {
        for (int y = YMin; y <= YMax; y++) {
            int index = get_index(x, y);
            if (insideTriangle(x + 0.5, y + 0.5, t.v)) {
                auto [alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v);
                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[index]) {
                    depth_buf[index] = zp;
                    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);
                    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);
                    //frame_buf[index] = t.getColor();
                    fragment_shader_payload FragShader(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
                    FragShader.view_pos = interpolated_shadingcoords;
                    auto pixel_color = fragment_shader(FragShader);
                    Vector2i point; 
                    point << x, y;
                    set_pixel(point, pixel_color);
                }
            }

        }
    }

下面draw函式中還是有一些細節
比如透視除法 w分量保留了z值
用view_Pos來儲存真實的世界座標 因為光照計算必須是在三維空間中計算的 所以這裡要儲存
法線矯正 防止model矩陣中物體進行了非等比縮放

void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList) {

    float f1 = (50 - 0.1) / 2.0;
    float f2 = (50 + 0.1) / 2.0;

    Eigen::Matrix4f mvp = projection * view * model;
    for (const auto& t:TriangleList)
    {
        Triangle newtri = *t;
        //使用三維空間中的座標獲取著色點座標
        std::array<Eigen::Vector4f, 3> mm {
                (view * model * t->v[0]),
                (view * model * t->v[1]),
                (view * model * t->v[2])
        };

        std::array<Eigen::Vector3f, 3> viewspace_pos;

        std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
            return v.template head<3>();
        });

        Eigen::Vector4f v[] = {
                mvp * t->v[0],
                mvp * t->v[1],
                mvp * t->v[2]
        };
        //Homogeneous division
        for (auto& vec : v) {
            vec.x()/=vec.w();
            vec.y()/=vec.w();
            vec.z()/=vec.w();
        }
        //法線校正
        Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();
        Eigen::Vector4f n[] = {
                inv_trans * to_vec4(t->normal[0], 0.0f),
                inv_trans * to_vec4(t->normal[1], 0.0f),
                inv_trans * to_vec4(t->normal[2], 0.0f)
        };

        //Viewport transformation
        for (auto & vert : v)
        {
            vert.x() = 0.5*width*(vert.x()+1.0);
            vert.y() = 0.5*height*(vert.y()+1.0);
            vert.z() = vert.z() * f1 + f2;
        }

        for (int i = 0; i < 3; ++i)
        {
            //screen space coordinates
            newtri.setVertex(i, v[i]);
        }

        for (int i = 0; i < 3; ++i)
        {
            //view space normal
            newtri.setNormal(i, n[i].head<3>());
        }

        newtri.setColor(0, 148,121.0,92.0);
        newtri.setColor(1, 148,121.0,92.0);
        newtri.setColor(2, 148,121.0,92.0);

        // Also pass view space vertice position
        rasterize_triangle(newtri, viewspace_pos);
    }
}

理論分析

bling-phong模型 與 紋理對映的理論都比較簡單 這裡不再贅述
有關紋理定址 與 紋理過大/過小的問題 大概會再寫一篇 參考一下別的資料
重點分析一下 bumpmapping的理論
其實normal mapping 和bumpmapping 很像 Bump Mapping 使用高度圖來擾動法線 Normal Mapping 使用法線貼圖來直接定義表面法線 理論上normal mapping 能夠實現更好的細節 這兩者都是為了使用更少的三角形 來實現更多的細節:
不使用bump mapping
img
使用bump mapping
img

首先 可以參考一下learnopengl的資料:https://learnopengl-cn.github.io/05 Advanced Lighting/04 Normal Mapping/
簡單總結一下: 為什麼我們要使用切線空間來儲存法線?
如果直接儲存物體的法線資訊 那麼物體只要發生變化 我們的法線貼圖就沒辦法使用了 但是在切線空間下儲存 我們只要每次求出一個TBN矩陣 然後進行變換就可以將切線空間的法線轉換到世界空間 然後計算光照了
關於TBN矩陣就是對應著切線空間的三個基向量 在世界空間中的座標 這樣進行空間轉換
推導方面 需要求出切線 以及副切線 可以看看learnopengl中的推導 本次作業中切線的求解我沒看太懂 感覺是做了個近似?
得到了TBN矩陣 如何使用:

我們直接使用TBN矩陣,這個矩陣可以把切線座標空間的向量轉換到世界座標空間。因此我們把它傳給片段著色器中,把透過取樣得到的法線座標左乘上TBN矩陣,轉換到世界座標空間中,這樣所有法線和其他光照變數就在同一個座標系中了。

這也是本次課程中的做法
現在唯一的問題就是我們如何求出這個法線座標
而games101課程中介紹的就是我們如何求經過bump mapping得到的法線座標
即經過高度圖來擾動點的位置之後 利用偏導數來近似切線 進一步得到法線:
img
img

但其實這樣也是一個近似的結果
pbr中的推導是這樣的:
img
上述是點的位置發生了變化 d(u,v)代表著高度變化 n(u,v)是表面法線 本來應該是個標量 但是作業中用的是rgb形式的 所以要求它的範數

我們對上述求偏導實際的近似結果應該是:
img

作業中相當只使用了uv座標的變化來近似 也是因為我們資訊有限嘛
詳細分析見:https://www.pbr-book.org/4ed/Textures_and_Materials/Material_Interface_and_Implementations#NormalMapping

這裡我們求得的其實是shading normal 而我們之前用的tbn中的n是surface normal 著色是計算我們要用高度擾動之後的位置以及法線(shading normal) 而高度擾動之後的位置計算 我們要使用surface normal來計算 這點很重要 我看到有些網上的答案計算是錯誤的

實際解決

注意所有的方向向量 都要歸一化!

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.x(), payload.tex_coords.y());
    }
    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};

    for (auto& light : lights)
    {
        
        Eigen::Vector3f ambient_color = ka.cwiseProduct(amb_light_intensity);
        double distance = (light.position - point).norm();
        Eigen::Vector3f light_dir = (light.position - point).normalized();
        Eigen::Vector3f diffuse_color = kd.cwiseProduct(light.intensity) / (distance * distance) * std::max(normal.normalized().dot(light_dir), 0.0f);
        Eigen::Vector3f half_vector = (light_dir + (eye_pos - point).normalized()).normalized();
        Eigen::Vector3f specular_color = ks.cwiseProduct(light.intensity) / (distance * distance) * std::pow(std::max(normal.normalized().dot(half_vector), 0.0f), p);
        result_color += (ambient_color + diffuse_color + specular_color);
    }

    return result_color * 255.f;
}

Eigen::Vector3f phong_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;

    Eigen::Vector3f result_color = {0, 0, 0};
    for (auto& light : lights)
    {

       
        Eigen::Vector3f ambient_color = ka.cwiseProduct(amb_light_intensity);
        double distance = (light.position - point).norm();
        Eigen::Vector3f light_dir = (light.position - point).normalized();
        Eigen::Vector3f diffuse_color = kd.cwiseProduct(light.intensity) / (distance * distance) * std::max(normal.normalized().dot(light_dir),0.0f);
        Eigen::Vector3f half_vector = (light_dir + (eye_pos - point).normalized()).normalized();
        Eigen::Vector3f specular_color = ks.cwiseProduct(light.intensity) / (distance * distance) * std::pow(std::max(normal.normalized().dot(half_vector),0.0f), p);
        result_color += (ambient_color + diffuse_color + specular_color);
    }

    return (result_color) * 255.f;
}

//
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;
    
    
    Eigen::Vector3f tagent;
    tagent << normal.x() * normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
        std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
        normal.z()* normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2));
    Eigen::Vector3f bitangent = normal.cross(tagent);
    Eigen::Matrix3f TBN;
    TBN.col(0) = tagent.normalized();
    TBN.col(1) = bitangent.normalized();
    TBN.col(2) = normal.normalized();

    float width = 1.0f / payload.texture->width;
    float height = 1.0f / payload.texture->height;
    float dU = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y()).norm() -
        payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm());
    float dV = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y() + height).norm() -
        payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm());
    //計算擾動之後的位置
    point += kn * normal * payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm();
    Eigen::Vector3f ln;
    ln << -dU, -dV, 1;
    Eigen::Vector3f shading_normal = (TBN * ln).normalized();
    

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

    for (auto& light : lights)
    {
        
        Eigen::Vector3f ambient_color = ka.cwiseProduct(amb_light_intensity);
        double distance = (light.position - point).norm();
        Eigen::Vector3f light_dir = (light.position - point).normalized();
        Eigen::Vector3f diffuse_color = kd.cwiseProduct(light.intensity) / (distance * distance) * std::max(shading_normal.normalized().dot(light_dir), 0.0f);
        Eigen::Vector3f half_vector = (light_dir + (eye_pos - point).normalized()).normalized();
        Eigen::Vector3f specular_color = ks.cwiseProduct(light.intensity) / (distance * distance) * std::pow(std::max(shading_normal.normalized().dot(half_vector), 0.0f), p);
        result_color += (ambient_color + diffuse_color + specular_color);

    }

    return result_color * 255.f;
}


Eigen::Vector3f bump_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;

   
    Eigen::Vector3f tagent;
    tagent << normal.x() * normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
        std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2)),
        normal.z()* normal.y() / std::sqrt(std::pow(normal.x(), 2) + std::pow(normal.z(), 2));
    Eigen::Vector3f bitangent = normal.cross(tagent);
    Eigen::Matrix3f TBN;
    TBN.col(0) = tagent.normalized();
    TBN.col(1) = bitangent.normalized();
    TBN.col(2) = normal.normalized();

    float width = 1.0f / payload.texture->width;
    float height = 1.0f / payload.texture->height;
    float dU = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y()).norm() -
        payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm());
    float dV = kh * kn * (payload.texture->getColor(payload.tex_coords.x() + width, payload.tex_coords.y() + height).norm() -
        payload.texture->getColor(payload.tex_coords.x(), payload.tex_coords.y()).norm());

    Eigen::Vector3f ln;
    ln << -dU, -dV, 1;
    normal = (TBN * ln).normalized();
    Eigen::Vector3f result_color = {0, 0, 0};
    result_color = normal;

    return result_color * 255.f;
}

結果展示:
img

img

img
img

img

相關文章