GAMES101学习笔记 作业2,作业3

**

作业2

**

因为Zbuffer这一章比较简单,就不细说,直接写作业。今天的任务是进行光栅化。

1:创建二维BoundingBox

    BoundingBox bbox;
    bbox.xmin = std::min(t.v[0][0],std::min(t.v[1][0],t.v[2][0]));
    bbox.xmax = std::max(t.v[0][0],std::max(t.v[1][0],t.v[2][0]));
    bbox.ymin = std::min(t.v[0][1],std::min(t.v[1][1],t.v[2][1]));
    bbox.ymax = std::max(t.v[0][1],std::max(t.v[1][1],t.v[2][1]));

2:检查当前点是否在三角形内,做叉积看sign是否相同

static bool insideTriangle(float x, float y, const Vector3f* _v)
{   
    Vector3f dir[3]; // PA,PB,PC
    Vector3f pos = Vector3f(x,y,1);
    float crossNum[3];
    for(int i = 0 ; i < 3 ;++i){
        dir[i] = pos - _v[i];
    }

    for(int i = 0 ; i < 3 ;++i){
         int j = (i + 1) % 3;
         crossNum[i] = (dir[i][0]) * (dir[j][1]) - (dir[j][0]) * (dir[i][1]); // x1y2-x2y1
    }

    for(int i = 0 ; i < 3 ;++i){
        int j = (i + 1) % 3;
        if(sign(crossNum[i]) * sign(crossNum[j]) <= 0){
            return false;
        }
    }
    return true;
}

3:对三角形内部的元素进行处理。这里我连MSAA的代码一块贴上了。在申请buffer的时候,帧缓冲不变,深度buffer大小是原来的4倍。在判断是否在三角形内部时,判断当前像素的四个采样点。如果在三角形内部,则求解插值z,并更新深度缓冲。最后的颜色我是取了frameBuffer的颜色进行lerp。

void rst::rasterizer::MSAA(float x, float y, const Triangle& t, int& count){
    auto v = t.toVector4();
    auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
    float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
    float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
    z_interpolated *= w_reciprocal;

    int zx = floor(2 * x); int zy = floor(2 * y);
    float z_depth = depth_buf[get_depth_index(zx,zy)];
    if(z_interpolated < z_depth){
        depth_buf[get_depth_index(zx,zy)] = z_interpolated;
        count++;
    }
}

void rst::rasterizer::rasterize_triangle(const Triangle& t) {
    //省略BoundingBox...
    for(int y = bbox.ymin ; y <= bbox.ymax ; ++y)
    {
        for(int x = bbox.xmin ; x <= bbox.xmax; ++x)
        {
            int count = 0;
            if(insideTriangle(x + 0.25f,y + 0.25f,t.v)){
                MSAA(x + 0.25f,y + 0.25f, t, count);
            }
            if(insideTriangle(x + 0.75f,y + 0.25f,t.v)){
                MSAA(x + 0.75f,y + 0.25f, t, count);
            }
            if(insideTriangle(x + 0.75f,y + 0.75f,t.v)){
                MSAA(x + 0.75f,y + 0.75f, t, count);
            }
            if(insideTriangle(x + 0.25f,y + 0.75f,t.v)){
                MSAA(x + 0.25f,y + 0.75f, t, count);
            }
            if(count > 0)
                set_pixel(Vector3f(x,y,0),t.getColor() * count / 4.0f + (4 - count) * frame_buf[get_index(x,y)] / 4.0f);
        }
    }
}

4:结果对比
GAMES101学习笔记 作业2,作业3_第1张图片
GAMES101学习笔记 作业2,作业3_第2张图片
可以明显看出前者的锯齿感明显。

**

纹理映射

**

前面的着色部分,可以放到作业三来讲。我直接跳过了。

对于一个纹理贴图,我们可以定义三角形三个顶点的纹理坐标,“扫描”时,根据插值坐标进行采样。关于插值方法,我们定义了重心坐标。也就是这个点在三角形内部的坐标。关于推导可以参考我之前写过的博客 --> 重心坐标推导。需要注意的是,重心坐标定义的空间。如果我们能够在三维空间中得到正确的插值,那么在投影到屏幕空间后,这个插值往往是错误的。例如对深度的插值,此时应该变为1/z的插值。具体大家可以搜索“透视校正插值相关内容”。这里我就不展开说了。

在采样时,我们给定插值后的坐标u,v。这个坐标对应的纹理上的一个点,此时我们采样的是一个像素。很显然在像素大小很小的时候,这种采样往往像素感严重。我们可以使用双线性插值的方法进行。算法也很简单,先对水平方向进行两次插值,将插值的结果在竖直方向上再进行一次插值。

采样纹理时,是屏幕空间的一个pixel取纹理中采样颜色。那么这个pixel对应到纹理上就是一个采样区域。对于远处的物体来说,这个区域对应到纹理中的区域会更大。此时如果直接根据uv进行采样,就会出现类似摩尔纹这样的失真。mipmap在一定程度上能够缓解这种问题。mipmap在导入纹理时,会额外生成长宽未原先纹理1/2,1/4。。等大小的图像。我们可以根据这个像素和相邻像素uv上的差异,近似的判断区域的大小来选择哪张mipmap。以此来提升这个pixel的采样区域。而且值得注意的是,它所带来的内存消耗仅仅是原先的4/3。同时,我们也可以根据计算的大小,来在mipmap的层与层之间进行插值。

之前的采样对应到纹理上是正方形这种理想情况。但是这种采样在有些情况下也会出现问题,例如实际对应的区域是个细长细长的长方形。因此可以采用各向异性过滤的方法。根据这个伸长的程度进行采样。当然这个成本是远大于mipmap的,但是对于各向异性过滤来说,越高程度的反而性能消耗增加的越少。

**

作业三

**

1:求出重心坐标,进行插值。

//我在这搞了一个超级低级恶心烦人的错,我插值颜色的时候有一项打错成normal了。。出来的图。。不说了。

for(int y = ymin ; y <= ymax ; ++y)
{
   for(int x = xmin ; x <= xmax; ++x)
   {
       if(insideTriangle(x + 0.5f,y+ 0.5f,t.v))
       {
           auto[alpha, beta, gamma] = computeBarycentric2D(x+ 0.5f,y+ 0.5f,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[get_index(x,y)]){
               depth_buf[get_index(x,y)] = zp;
               auto interpolated_color = alpha * t.color[0] + beta * t.color[1] + gamma * t.color[2];
               auto interpolated_normal = alpha * t.normal[0] + beta * t.normal[1] + gamma * t.normal[2];
               auto interpolated_texcoords = alpha * t.tex_coords[0] + beta * t.tex_coords[1] + gamma * t.tex_coords[2];
               auto interpolated_shadingcoords = alpha * view_pos[0] + beta * view_pos[1] + gamma * view_pos[2];

               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);
               set_pixel(Vector2i(x,y),pixel_color);
           }

       }
   }
}

2:Blinn - Phong

相对于phong,这里使用了半角向量。通过简单的向量加就能求解,相对于求解reflect计算量低。

//1:ambient = ka dot amb
Vector3f ambientCol = ka.cwiseProduct(amb_light_intensity);

//2:diffuse = kd * lightIntensity / (dis * dis) * max(0,n dot l)
Vector3f disDir = light.position - point;
float dis2 = disDir.dot(disDir);
Vector3f lightDir = (disDir).normalized();
Vector3f diffuse = kd.cwiseProduct(light.intensity/dis2 * std::max(0.0f,normal.dot(lightDir)));
//3:specular = ks * lightIntensity / (dis * dis) * pow(max(0,n dot half),p) 
Vector3f half = (viewDir + lightDir).normalized();
Vector3f specular = ks.cwiseProduct(light.intensity/dis2 * pow(std::max(0.0f,normal.dot(half)),p));

result_color += (ambientCol + diffuse + specular);

3:法线贴图
法线贴图没有改变坐标,而是通过修改法线来改变光照达到以低模伪装高模的样子。
这里的法线贴图和平时用的不太一样。现在用的基本上就是两个颜色通道来表示两个坐标,第三个坐标可以根据存储的两个进行计算。
这个“凹凸贴图”,在纹理中储存的信息是高度信息。我们拿到这个高度信息就可以根据当前位置和相邻位置的差去求法线。这个法线是在局部坐标下的。通过TBN矩阵转化到物体所在的空间。
关于TBN,大家可以自己去查查推导。我这里简单说下我的理解:
为什么要定义到切线空间? 我认为最大的作用就是法线可复用。对于相同的面,我们只需要定义uv而不用再加法线。
TBN矩阵的作用:可以想象在物体表面,展开贴图。此时我们需要将纹理坐标和物体的坐标相对应。也就是 物体上的向量 = TBN * 纹理上的向量。

 // TODO: Implement bump mapping here
 // Let n = normal = (x, y, z)
 Eigen::Vector3f n = normal;
 // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
 Eigen::Vector3f t ;
 t   << n[0] * n[1] / sqrt(n[0]*n[0] + n[2]*n[2])
     , sqrt(n[0]*n[0] + n[2]*n[2])
     , n[2] * n[1] / sqrt(n[0]*n[0] + n[2]*n[2]);
 // Vector b = n cross product t
 Eigen::Vector3f b = n.cross(t);
 // Matrix TBN = [t b n]
 Eigen::Matrix3f TBN;
 TBN << t[0] , b[0] , n[0] 
     , t[1] , b[1] , n[1]
     , t[2] , b[2] ,n[2];
 float u = payload.tex_coords[0]; 
 float v = payload.tex_coords[1];
 float w = payload.texture->width;
 float h = payload.texture->height;
 // dU = kh * kn * (h(u+1/w,v)-h(u,v))
 float hu1 = payload.texture->getColor(u + 1.0 / w,v).norm();
 float hu0 = payload.texture->getColor(u,v).norm();
 float du = kh * kn * (hu1 - hu0);
 // dV = kh * kn * (h(u,v+1/h)-h(u,v))
 float hv1 = payload.texture->getColor(u,v + 1.0 / h).norm();
 float hv0 = hu0;
 float dv = kh * kn * (hv1 - hv0);
 // Vector ln = (-dU, -dV, 1)
 Eigen::Vector3f ln ; 
 ln << -du , -dv , 1;
 // Normal n = normalize(TBN * ln)
 normal = TBN * ln;
 normal = normal.normalized();

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

4:displacement
这里就不多讲了,就是沿着法线方向扩一下frag的位置,从而修改后面的viewDir,lightDir等。注意:这里是在片元着色下,没有真正去改顶点。

你可能感兴趣的:(unity3d,图形学,unity)