games101作业3,实现bump,displacement

前言

首先我想解释一段代码,我感觉我自己这种说法最能说服我

auto[alpha,beta,gamma]=computeBarycentric2D(x+0.5,y+0.5,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;

首先这里的zp求的是透视变化前的深度,透视变化后深度也不应该会有变化,透视变化的时候,z的内容为z*z,w的内容为z。同时draw的内容中,有修正z分量的内容:

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();
}

然后z的值会被修正为其原本的深度(ps:这里我有点乱,但是主要是需要结合透视修正公式来看)所以其实这里的v[i].z()就是等于v[i].w()。其中Z的值就是透视投影修正值

法线贴图

这里首先就是深度的计算以及,对三角形内部的像素的插值计算。

Triangle类中给我们提供了颜色的属性,纹理坐标属性,法线属性。

Vector3f color[3]; //color at each vertex;
Vector2f tex_coords[3]; //texture u,v
Vector3f normal[3]; //normal vector for each vertex

所以就是分别对他们进行插值计算,这里的alpha,beta,gamma都用了透视修正运算

这里还对view_pos进行了插值运算,得到像素点在view空间的坐标

auto interpolated_color=interpolate(alpha*Z/v[0].w(),bata*Z/v[1].w(),gamma*Z/v[2].w(),t.color[0],t.color[1],t.color[2],1);
auto interpolated_texcoords=interpolate(alpha*Z/v[0].w(),bata*Z/v[1].w(),gamma*Z/v[2].w(),t.tex_coords[0],t.tex_coords[1],t.coords[2],1);
auto interpolated_normal=interpolate(alpha*Z/v[0].w(),beta*Z/v[1].w(),gamma*Z/v[2].w(),t.normal[0],t.normal[1],t.normal[2],1);
auto interpolated_shadingcoords=interpolate(alpha*Z/v[0].w(),beta*Z/v[1].w(),gamma*Z/v[2].w(),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;
auto pixel_color = fragment_shader(payload);
Eigen::Vector2i p;
p<<x,y;
set_pixel(p,pixel_color);

然后就是填写投影矩阵

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
    float angle=eye_fov/2/180.0f*MY_PI;
    float h=std::fabs(std::tan(angle)*zNear);
    float w=std::fabs(h*aspect_ratio);
    float lh=-h;
    float lw=-w;
    Eigen::Matrix4f proj,opt,opm;
    proj<<zNear,0,0,0,
    0,zNear,0,0,
    0,0,zNear+zFar,-zNear*zFar,
    0,0,1,0;
    opt<<2.0f/(w-lw),0,0,0,
    0,2.0f/(h-lh),0,0,
    0,0,2.0f/(zNear-zFar),0,
    0,0,0,1;
    opm<<0,0,0,-(w+lw)/2.0f,
    0,0,0,-(h+lh)/2.0f,
    0,0,0,-(zNear+zFar)/2.0f,
    0,0,0,1;
    Eigen::Matrix4f projection=Eigen::Matrix4f::Identity();
    return opt*opm*proj*projection; 
    // TODO: Use the same projection matrix from the previous assignments

}

出现了这个错误,开始调试

发现是路径问题,然后现在是只有黑色图像。

查了一下,法线纹理,应该是对的

cv::cvtColor(image_data, image_data, cv::COLOR_RGB2BGR);
cv::imshow("test",image_data);
cv::waitKey(0);

证明纹理读入正确

好的透视投影矩阵写错了,opm矩阵一开始对角线上全为0了,这个debug的过程就留在这里,感觉挺有意思的。。。。。。

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
    float angle=eye_fov/2/180.0f*MY_PI;
    float h=std::fabs(std::tan(angle)*zNear);
    float w=std::fabs(h*aspect_ratio);
    float lh=-h;
    float lw=-w;
    Eigen::Matrix4f proj,opt,opm;
    proj<<zNear,0,0,0,
    0,zNear,0,0,
    0,0,zNear+zFar,-zNear*zFar,
    0,0,1,0;
    opt<<2.0f/(w-lw),0,0,0,
    0,2.0f/(h-lh),0,0,
    0,0,2.0f/(zNear-zFar),0,
    0,0,0,1;
    opm<<1,0,0,-(w+lw)/2.0f,
    0,1,0,-(h+lh)/2.0f,
    0,0,1,-(zNear+zFar)/2.0f,
    0,0,0,1;
    Eigen::Matrix4f projection=Eigen::Matrix4f::Identity();
    return opt*opm*proj*projection; 
    // TODO: Use the same projection matrix from the previous assignments

}

结果

牛牛倒过来了。

但是它的旋转矩阵是绕y轴旋转140度。打印zNear和zFar出来的结果是0.1和50!!!!逆天,按照我们所认为的z轴的方向应该是正对屏幕向外,那么深度越深应该是-50才对。所以这里的z的正方向向屏幕里面。(这里没懂),但是我想如果翻转也很简单,只需要x,y的坐标取反就可以了,相当于绕z轴180度旋转。

float angle=eye_fov/2/180.0f*MY_PI;
float h=-std::fabs(std::tan(angle)*zNear);
float w=-std::fabs(h*aspect_ratio);
float lh=-h;
float lw=-w;

得到了正牛牛

实现Blinn-Phong

解释一下三个参数在这里什么意思

环境光

Eigen::Vector3f amb_light_intensity{10, 10, 10};//环境光照强度
Eigen::Vector3f amb_color=amb_light_intensity*ka;//环境光颜色

漫反射光照

auto light_pos=light.position;
auto light_intensity=light.intensity;
auto light_vect=(light_pos-point).normalized();//光线的方向单位向量
normal=normal.normalized();
float r=(light_pos-point).norm();//求距离r
Eigen::Vector3f diffuse_color;
diffuse_color=kd.cwiseProduct((light_intensity/r/r)*std::max(0.0f,light_vect.dot(normal)));//漫反射光

反射光

auto light_pos=light.position;
auto light_intensity=light.intensity;
auto light_vect=(light_pos-point).normalized();//光线的方向单位向量
normal=normal.normalized();
Eigen::Vector3f view_vect=(eye_pos-point).normalized();
Eigen::Vector3f h=(view_vect+light_vect).normalized();
Eigen::Vector3f specular_color=ks.cwiseProduct((light_intensity/r/r)*std::pow(std::max(0.0f,h.dot(normal)),p));//高光

最后组合在一起

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 amb_color=amb_light_intensity.cwiseProduct(ka);//环境光颜色

    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};
    result_color+=amb_color;
    for (auto& light : lights)
    {
        auto light_pos=light.position;
        auto light_intensity=light.intensity;
        auto light_vect=(light_pos-point).normalized();//光线的方向单位向量
        normal=normal.normalized();
        float r=(light_pos-point).norm();//求距离r
        Eigen::Vector3f diffuse_color;
        diffuse_color=kd.cwiseProduct((light_intensity/r/r)*std::max(0.0f,light_vect.dot(normal)));//漫反射光
        Eigen::Vector3f view_vect=(eye_pos-point).normalized();
        Eigen::Vector3f h=(view_vect+light_vect).normalized();
        Eigen::Vector3f specular_color=ks.cwiseProduct((light_intensity/r/r)*std::pow(std::max(0.0f,h.dot(normal)),p));//高光
        result_color+=specular_color;
        result_color+=diffuse_color;
        // 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.
        
    }

    return result_color * 255.f;
}

结果

实现纹理影响光照中的kd(漫反射系数)

首先看纹理怎么读入的

Eigen::Vector3f return_color = {0, 0, 0};
if (payload.texture)
{
    // TODO: Get the texture value at the texture coordinates of the current fragment

}
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;

这里的texture_color就是纹理的颜色权重,那么看这个应该是从payload.texture中读取的。

Texture类中有一个getColor的类,于是可以使用这个来获取纹理的颜色。

Eigen::Vector3f getColor(float u, float v)
{
    auto u_img = u * width;
    auto v_img = (1 - v) * height;
    auto color = image_data.at<cv::Vec3b>(v_img, u_img);
    return Eigen::Vector3f(color[0], color[1], color[2]);
}

于是根据每个像素的纹理坐标对应得到纹理值,然后运用到kd。最后结合上面的光照的使用。

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f return_color = {0, 0, 0};
    if (payload.texture)
    {
        return_color+=payload.texture->getColor(payload.tex_coords[0],payload.tex_coords[1]);
        // TODO: Get the texture value at the texture coordinates of the current fragment

    }
    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 ambient_color=ka.cwiseProduct(amb_light_intensity);
    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};
    result_color+=ambient_color;
    for (auto& light : lights)
    {
        auto light_pos=light.position;
        auto light_intensity=light.intensity;
        auto light_vec=(light_pos-point).normalized();
        auto view_vec=(eye_pos-point).normalized();
        normal=normal.normalized();
        float r=(light_pos-point).norm();
        Eigen::Vector3f h=(light_vec+view_vec).normalized();
        Eigen::Vector3f diffuse_color=kd.cwiseProduct((light_intensity/r/r)*std::max(0.0f,light_vec.dot(normal)));
        Eigen::Vector3f specular_color=ks.cwiseProduct((light_intensity/r/r)*std::pow(std::max(0.0f,h.dot(normal)),p));
        result_color+=diffuse_color;
        result_color+=specular_color;
        // 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.

    }

    return result_color * 255.f;
}

结果

凹凸贴图参考

从凹凸贴图到法线(注意,不是法线贴图)的计算方法:高度场

凹凸贴图是1维的,里面的信息是高度场,所以也叫高度场纹理

高度场纹理是编码每个Texel(纹素)的高度(高度贴图越亮,代表的高度越高)

H_g是高度贴图给定Texel(纹素)的高度(可以理解为颜色,高度值越大,颜色越亮)

H_a是给定Texel(纹素)正上方Texel的高度

H_r是给定Texel(纹素)正右方Texel的高度

归一化后的法线就是通过下面这个公式得出

这里的法线方向是切向空间的法线方向(有些地方也说成是纹理空间,你也可以理解为uv空间,因为Tex图是通过uv坐标映射到模型表面上的)

切向空间(Tangent Space)简介

切向空间是与模型表面每一点紧密相关联的局部坐标系统。它的构成基于表面的法线向量(通常指向表面的垂直方向),以及切线向量(Tangent)和副切线向量(Bitangent 或 Bi-normal)。

法线向量 (Normal): 表面的垂直方向。

切线向量 (Tangent): 在纹理坐标的U方向(通常是水平方向)上与表面相切。

副切线向量 (Bitangent): 在纹理坐标的V方向(通常是垂直方向)上与表面相切。

切向空间法线的作用

切向空间法线主要用于法线贴图,这是一种特殊的纹理贴图,用于存储与基础几何形状的表面法线相比有细微偏移的法线向量。这些法线向量不是在世界坐标系统中表达的,而是在与表面每一点相对应的切向空间中表达的。这使得它们可以简单地表达局部的凹凸变化,而不必关心整体的对象方向。

是的,切向空间中的法线确实是相对于纹理表面的三维向量,它描述了纹理表面的局部凹凸变化。在切向空间中,法线向量通常以三维格式表示,其中:

X分量 和 Y分量 描述了纹理表面在水平和垂直方向上的倾斜程度,通常是由相邻像素或texel的高度差计算得出。

Z分量 常设为1,表示法线在局部表面的垂直分量,也即基础表面法线的方向。

解释和应用

在法线贴图中,这些法线通常被编码为颜色值,其中红色通道代表X分量,绿色通道代表Y分量,蓝色通道代表Z分量。这样,一张看似普通的彩色图像实际上包含了复杂的法线信息,每个像素的颜色值代表着纹理表面在该点的法线方向。

实际效果

在渲染过程中,这些切向空间的法线向量被用于计算光照和阴影,以模拟更为复杂和细致的表面细节,如凹陷、凸起等,增强视觉效果的真实感和深度。使用切向空间的法线而非直接在世界空间或物体空间中处理法线,可以简化计算过程,使之更为高效,同时便于在不同的物体和场景中重用同一法线贴图。

这里要特别注意在切向空间内,法线的z值基本上都为正(其实可以为负,但是几乎不会有人这么做),所以为什么我们看到的法线贴图蓝色居多

蓝色表示法线贴图基本没有改变模型的法线信息,而那些非蓝色的地方才是替换后法线改变最严重的地方,具体原因还请往下看

这个法线是有符号的,若要存贮到RGB中,就必须进行范围压缩

(有符号值:有正负的值;无符号值:只有正值)

将法线通过RGB格式存储为法线贴图:通过压缩范围

因为归一化的法线向量Normal是有符号值,范围是[-1,1]; 而贴图中RGB是无符号值,范围是 [0,1] ,因此就需要范围压缩

还是直接看凹凸贴图,TBN矩阵

uv在uv坐标就是2个相互垂直的方向向量,但是在世界坐标中,它代表什么呢?

答案是:它代表物体表面的切线方向(但是是正交化前)

作业

没怎么看懂,大概就是下面的定义:TBN空间和TBN矩阵

切线空间定义于每一个顶点之中,是由切线(TangentTangentTangent),副切线(BiTangentBiTangentBiTangent),顶点法线(NormalNormalNormal)以模型顶点为中心的坐标空间。normalMapnormalMap

这里相当于重新设置了像素的法线

float kh = 0.2, kn = 0.1;
float x=normal.x();
float y=normal.y();
float z=normal.z();
Eigen::Vector3f t={(x*y/std::sqrt(x*x+z*z)),std::sqrt(x*x+z*z),z*y/std::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.x();
float v=payload.tex_coords.y();
float w=payload.texture->width;
float h=payload.texture->height;
float du=kh*kn*(payload.texture->getColor(u+1.0f/w,v).norm()-payload.texture->getColor(u,v).norm());//这里没懂
float dv=kh*kn*(payload.texture->getColor(u,v+1.0f/h).norm()-payload.texture->getColor(u,v).norm());
Eigen::Vector3f ln={-du,-dv,1};
normal=(TBN*ln).normalized();//最终的像素的法线

此时单纯观察法线

观察法线变化后的作用

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 ambient_color=ka.cwiseProduct(amb_light_intensity);//环境光照
    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;
    float x=normal.x();
    float y=normal.y();
    float z=normal.z();
    Eigen::Vector3f t={(x*y/std::sqrt(x*x+z*z)),std::sqrt(x*x+z*z),z*y/std::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.x();
    float v=payload.tex_coords.y();
    float w=payload.texture->width;
    float h=payload.texture->height;
    float du=kh*kn*(payload.texture->getColor(u+1.0f/w,v).norm()-payload.texture->getColor(u,v).norm());//这里没懂
    float dv=kh*kn*(payload.texture->getColor(u,v+1.0f/h).norm()-payload.texture->getColor(u,v).norm());
    Eigen::Vector3f ln={-du,-dv,1};
    normal=(TBN*ln).normalized();//最终的像素的法线
    // TODO: Implement bump mapping here
    // Let n = normal = (x, y, z)
    // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
    // Vector b = n cross product t
    // Matrix TBN = [t b n]
    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    // Vector ln = (-dU, -dV, 1)
    // Normal n = normalize(TBN * ln)


    Eigen::Vector3f result_color = {0, 0, 0};
    // result_color=normal;
    result_color+=ambient_color;
    for(auto& light:lights){
        auto light_pos=light.position;
        auto light_intensity=light.intensity;
        float r=(light_pos-point).norm();
        auto light_vec=(light_pos-point).normalized();
        auto view_vec=(eye_pos-point).normalized();
        auto h=(light_vec+view_vec).normalized();
        auto diffuse_color=kd.cwiseProduct((light_intensity/r/r)*std::max(0.0f,light_vec.dot(normal)));
        auto specular_color=ks.cwiseProduct((light_intensity/r/r)*std::pow(std::max(0.0f,h.dot(normal)),p));
        result_color+=diffuse_color;
        result_color+=specular_color;
    }

    return result_color * 255.f;
}

观察光照

更改displacement实现金属光泽

它改变了point

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值