TinyRenderer之绘制三角形及flatshading

画一个三角形有多种方式,但究其本质,还是要去判断一个点是否在三角形内

判断点是否在三角形内有很多种方法:

  1. 将三角形三个点由y值进行排序,从高到低,形成三条边,这时我们可以考虑三角形的左右,然后同一y值得左右两个点确定过一条直线,在这两个点之间的直线都进行填充,就得到了三角形的面片。

  1. 面积法:将三个点其中两个组合得与点连接形成三角形,计算各三角形面积和,看两者是否相等。面积计算。三边长+海伦公式,效率不高

  1. 重心坐标方法,通过三角形的三个顶点,我们可以得出给定的某一点的重心坐标,如果这个点的重心坐标(1-u-v, u, v)三通道均大于0,那么就可以认为该点在三角形内;

  1. 向量叉乘算法/顺时针逆时针判定法,其实究其本质我觉得跟重心坐标很像,但是这种方法并不需要准确求出重心坐标,而是通过判断将三角形边形成的逆时针方向向量与p点到向量起始点的向量叉乘,判断三者正负即可判断。

这里一般是采用重心插值算法,虽然计算中心坐标用来判断点在三角形内有点大材小用,但因为只需要一次叉乘就可求出重心坐标。并且该重心坐标还可以用于对顶点属性插值,而这是现代渲染管线都需要的。

数学推导:

这部分参考day3:画一个三角形 | 微信公众号@卤蛋实验室 (supercodepower.com)的推导,人家已经写的很好了。

总结是我们仅需要进行一次叉乘就可以得到我们想要数据了。此外这种方法对并行度很高的gpu来说很友好

代码如下:

Vec3fbarycentric(Vec3i* pts, Vec2i P){
Vec3iv1 = Vec3i(pts[1].x - pts[0].x, pts[2].x - pts[0].x, pts[0].x - P.x);
Vec3iv2 = Vec3i(pts[1].y - pts[0].y, pts[2].y - pts[0].y, pts[0].y - P.y);
Vec3iv = v1 ^ v2;
if(std::abs(v.z) < 1) return Vec3f(-1, 1, 1);
returnVec3f(1.0f - (v.x + v.y) / (float)v.z, v.x / (float)v.z, v.y / (float)v.z);
}
voidtriangle_barycentric(Vec3i* pts, std::vector<int>& zBuffer,TGAImage& image, const TGAColor& color) {
    //计算bounding box
    Vec2ibbox_min = Vec2i(width - 1, height - 1);
    Vec2ibbox_max = Vec2i(0, 0);
    for(int i = 0; i < 3; i++ ) {
        bbox_max.x= std::min(width - 1, std::max(bbox_max.x, pts[i].x));
        bbox_max.y= std::min(height - 1, std::max(bbox_max.y, pts[i].y));
        bbox_min.x= std::max(0, std::min(bbox_min.x, pts[i].x));
        bbox_min.y= std::max(0, std::min(bbox_min.y, pts[i].y));
    }
 
//遍历包围盒,判定在三角内的被着色
    for(int i = bbox_min.x; i <= bbox_max.x; i++) {
        for(int j = bbox_min.y; j <= bbox_max.y; j++) {
            Vec3fbaryc = barycentric(pts, Vec2i(i, j));
            if(baryc.x >= 0 && baryc.y >= 0 && baryc.z >= 0) {
                intdepth = baryc.x * pts[0].z + baryc.y * pts[1].z + baryc.z * pts[2].z;
                if(depth > zBuffer[i + j * width - 1]) {
                    image.set(i,j, color);
                    zBuffer[i+ j * width - 1] = depth;
                 }
             }        
         }
     }
 }

//让我们再加上渲染模型的函数
voidrenderModel(Model& model, TGAImage& img) {
std::vector<int>zBuffer(width * height, INT32_MIN);
for(int i = 0; i < model.nfaces(); i++) {
    autoface = model.face(i);
    Vec3fworldspace_coord[3];
    Vec3iscreenspace_coord[3];
    for(int j = 0; j < face.size(); j++) {
    //这里将存储在本地空间的模型(这里存的ndc)坐标变换到屏幕坐标,做了个简单的视口变换
        worldspace_coord[j]= model.vert(face[j]);
        screenspace_coord[j]= Vec3i((worldspace_coord[j].x + 1) * width / 2, (worldspace_coord[j].y + 1) *height / 2, (worldspace_coord[j].z + 1)* width / 2);
    }
    //三角形随机颜色绘制
    //triangle_barycentric(pts,img, TGAColor(rand() % 255, rand() % 255, rand() % 255, 255));
 
    //计算三角形法线
    Vec3fnormal = ((worldspace_coord[1] - worldspace_coord[0]) ^ (worldspace_coord[2] -worldspace_coord[0])).normalize();
    float intensity = -normal * lightDir; //简易的漫反射计算
    //绘制三角形
    if(intensity> 0)
        triangle_barycentric(screenspace_coord,zBuffer, img, TGAColor(intensity * 255, intensity * 255, intensity * 255,255));
    }
}

注意这里用到了一个简单的正交投影+视口变换,用一个简易的点乘代表简单的漫反射计算。可以看到嘴巴怪怪的,下一章会解决。

另外解释下这里用的是flatshading,也就是每个三角形面片计算了一个法线,用来着色,在这个三角形面片内都是同一个颜色,着色频率很低。

随机颜色三角形面片

Flatshading

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值