tinyrenderer-三角形光栅化和背面剔除

画空心三角形

根据之前的画线算法,可以很简单画出一个空心三角形,对三角形三个顶点,按顺序分别首尾画连线就可以

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color) { 
    line(t0, t1, image, color); 
    line(t1, t2, image, color); 
    line(t2, t0, image, color); 
}
// ...
Vec2i t0[3] = {Vec2i(10, 70),   Vec2i(50, 160),  Vec2i(70, 80)}; 
Vec2i t1[3] = {Vec2i(180, 50),  Vec2i(150, 1),   Vec2i(70, 180)}; 
Vec2i t2[3] = {Vec2i(180, 150), Vec2i(120, 160), Vec2i(130, 180)}; 
triangle(t0[0], t0[1], t0[2], image, red); 
triangle(t1[0], t1[1], t1[2], image, white); 
triangle(t2[0], t2[1], t2[2], image, green);

在这里插入图片描述

填充三角形

好的三角形算法需要具备:

  1. 简单并快速
  2. 对称的,图片不依赖于传给绘图函数的顶点的顺序(顺时针画和逆时针画是一样的三角形)
  3. 如果两个三角形有共同顶点,由于光栅化舍入,他们之间应该没有孔

传统方法是扫线法:

  1. 对三角形的顶点按y排序
  2. 同时栅格化三角形左右两边
  3. 在左右边界点间画线

三个点t0,t1,t2按y排序后,以中间顶点t1做一条水平线可以将三角形分为上下两个部分,都是三角形,并且以水平横线分割。这样可以对上下两个三角形做相同的画横线填充处理
填充横线是按y轴递增,根据y轴递增值求对应左右两个边界的x值(线性求值)
注意三角形里画横线自己实现,不要调用line函数,因为横线不需要去求多余的斜率增长那些,只要x轴水平递增,y不变
按自己的思路实现了一下

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color, bool isSolid) {
    if (isSolid) {
        if (t0.y > t1.y) std::swap(t0, t1);
        if (t0.y > t2.y) std::swap(t0, t2);
        if (t1.y > t2.y) std::swap(t1, t2);
        for (int y = t0.y; y < t1.y; y++) {
            float ratioA = (y - t0.y) / (float)(t2.y - t0.y + 1);
            float ratioB = (y - t0.y) / (float)(t1.y - t0.y + 1);
            Vec2i tA = t0 + (t2 - t0) * ratioA;
            Vec2i tB = t0 + (t1 - t0) * ratioB;
            if (tA.x < tB.x) {
                for (int x = tA.x; x <= tB.x; x++) {
                    image.set(x, tA.y, color);
                }
            }
            else {
                for (int x = tB.x; x <= tA.x; x++) {
                    image.set(x, tA.y, color);
                }
            }
        }
        for (int y = t1.y; y <= t2.y; y++) {
            float ratioA = (y - t0.y) / (float)(t2.y - t0.y + 1);
            float ratioB = (y - t1.y) / (float)(t2.y - t1.y + 1);
            Vec2i tA = t0 + (t2 - t0) * ratioA;
            Vec2i tB = t1 + (t2 - t1) * ratioB;
            if (tA.x < tB.x) {
                for (int x = tA.x; x <= tB.x; x++) {
                    image.set(x, tA.y, color);
                }
            }
            else {
                for (int x = tB.x; x <= tA.x; x++) {
                    image.set(x, tA.y, color);
                }
            }
        }
        image.set(t2.x, t2.y, color);
    }
    else {
        line(t0.x, t0.y, t1.x, t1.y, image, color);
        line(t1.x, t1.y, t2.x, t2.y, image, color);
        line(t2.x, t2.y, t0.x, t0.y, image, color);
    }
}

和官方代码对比了一下,逻辑是一样的

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color) { 
    if (t0.y==t1.y && t0.y==t2.y) return; // I dont care about degenerate triangles 
    // sort the vertices, t0, t1, t2 lower−to−upper (bubblesort yay!) 
    if (t0.y>t1.y) std::swap(t0, t1); 
    if (t0.y>t2.y) std::swap(t0, t2); 
    if (t1.y>t2.y) std::swap(t1, t2); 
    int total_height = t2.y-t0.y; 
    for (int i=0; i<total_height; i++) { 
        bool second_half = i>t1.y-t0.y || t1.y==t0.y; 
        int segment_height = second_half ? t2.y-t1.y : t1.y-t0.y; 
        float alpha = (float)i/total_height; 
        float beta  = (float)(i-(second_half ? t1.y-t0.y : 0))/segment_height; // be careful: with above conditions no division by zero here 
        Vec2i A =               t0 + (t2-t0)*alpha; 
        Vec2i B = second_half ? t1 + (t2-t1)*beta : t0 + (t1-t0)*beta; 
        if (A.x>B.x) std::swap(A, B); 
        for (int j=A.x; j<=B.x; j++) { 
            image.set(j, t0.y+i, color); // attention, due to int casts t0.y+i != A.y 
        } 
    } 
}
triangle(t0[0], t0[1], t0[2], image, red, false);
triangle(t1[0], t1[1], t1[2], image, white, true);
triangle(t2[0], t2[1], t2[2], image, green, true);

在这里插入图片描述

使用Bounding Box填充三角形

找到三角形的包围盒,及对比三个顶点的minX,minY,maxX,maxY。
遍历包围盒里的每个点,是否在三角形内,确定是否填充颜色
伪代码

triangle(vec2 points[3]) { 
    vec2 bbox[2] = find_bounding_box(points); 
    for (each pixel in the bounding box) { 
        if (inside(points, pixel)) { 
            put_pixel(pixel); 
        } 
    } 
}

找包围盒,只需要三角形三个顶点分别对比获取最大最小的x,y就行。
判断点是否在三角形内,官方用的是重心坐标的性质判断

Vec3f barycentric(Vec2i *pts, Vec2i P) { 
    Vec3f u = Vec3f(pts[2][0]-pts[0][0], pts[1][0]-pts[0][0], pts[0][0]-P[0])^Vec3f(pts[2][1]-pts[0][1], pts[1][1]-pts[0][1], pts[0][1]-P[1]);
    /* `pts` and `P` has integer value as coordinates
       so `abs(u[2])` < 1 means `u[2]` is 0, that means
       triangle is degenerate, in this case return something with negative coordinates */
    if (std::abs(u.z)<1) return Vec3f(-1,1,1);
    return Vec3f(1.f-(u.x+u.y)/u.z, u.y/u.z, u.x/u.z); 
} 
void triangle(Vec2i *pts, TGAImage &image, TGAColor color) { 
    Vec2i bboxmin(image.get_width()-1,  image.get_height()-1); 
    Vec2i bboxmax(0, 0); 
    Vec2i clamp(image.get_width()-1, image.get_height()-1); 
    for (int i=0; i<3; i++) { 
        bboxmin.x = std::max(0, std::min(bboxmin.x, pts[i].x));
	bboxmin.y = std::max(0, std::min(bboxmin.y, pts[i].y));

	bboxmax.x = std::min(clamp.x, std::max(bboxmax.x, pts[i].x));
	bboxmax.y = std::min(clamp.y, std::max(bboxmax.y, pts[i].y));
    } 
    Vec2i P; 
    for (P.x=bboxmin.x; P.x<=bboxmax.x; P.x++) { 
        for (P.y=bboxmin.y; P.y<=bboxmax.y; P.y++) { 
            Vec3f bc_screen  = barycentric(pts, P); 
            if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) continue; 
            image.set(P.x, P.y, color); 
        } 
    } 
} 

我这里直接用的是向量叉乘判断

bool insideTriangle(Vec2i* pts, int x, int y) {
    Vec2f a = Vec2f(pts[1].x - pts[0].x, pts[1].y - pts[0].y);
    Vec2f b = Vec2f(pts[2].x - pts[1].x, pts[2].y - pts[1].y);
    Vec2f c = Vec2f(pts[0].x - pts[2].x, pts[0].y - pts[2].y);
    Vec2f pa = Vec2f(x - pts[0].x, y - pts[0].y);
    Vec2f pb = Vec2f(x - pts[1].x, y - pts[1].y);
    Vec2f pc = Vec2f(x - pts[2].x, y - pts[2].y);
    bool flag = pa.x * a.y - pa.y * a.x < 0;
    if (flag != pb.x * b.y - pb.y * b.x < 0) return false;
    if (flag != pc.x * c.y - pc.y * c.x < 0) return false;
    return true;
}
void triangle(Vec2i* pts, TGAImage& image, TGAColor color) {
    int minX = image.get_width() - 1;
    int minY = image.get_height() - 1;
    int maxX = 0;
    int maxY = 0;
    for (int i = 0; i < 3; i++) {
        minX = std::min(minX, pts[i].x);
        maxX = std::max(maxX, pts[i].x);
        minY = std::min(minY, pts[i].y);
        maxY = std::max(maxY, pts[i].y);
    }
    minX = std::max(0, minX);
    maxX = std::min(maxX, image.get_width() - 1);
    minY = std::max(0, minY);
    maxY = std::min(maxY, image.get_height() - 1);
    for (int x = minX; x <= maxX; x++) {
        for (int y = minY; y <= maxY; y++) {
            if (insideTriangle(pts, x, y)) {
                image.set(x, y, color);
            }
        }
    }
}

得到的效果是一样的
在这里插入图片描述

平面阴影渲染

用随机颜色填充模型的三角形
在这里插入图片描述
在这里插入图片描述
用黑白色做为亮度来代替随机色。
定义一束平行光源,水平朝-z的方向
根据叉乘求出模型每个三角形的法线向量。根据光源方向和法线的夹角求出光照强度
根据点乘定义,光源向量点乘三角形法线向量,越接近0,说明光源越和法线垂直,则越和三角形平面平行,三角形越暗。反之越接近于1,越亮。
(类似布林冯漫反射方程原理,只是 k d k_d kd系数为白色,忽略了光强随距离的减弱)
根据原理实现了一下代码

Model* model = new Model("obj/african_head.obj");
    float maxNum = model->getMaxNum();
    Vec3f lightDir(0, 0, -1);
    for (int i = 0; i < model->nfaces(); i++) {
        std::vector<int> face = model->face(i);
        Vec2i vp[3];
        Vec3f vf[3];
        for (int j = 0; j < 3; j++) {
            Vec3f v0 = model->vert(face[j]);
            vp[j] = Vec2i((v0.x / maxNum + 1.) * 800 / 2., (v0.y / maxNum + 1.) * 800 / 2.);
            vf[j] = v0;
        }
        //triangle(vp, image, TGAColor(rand() % 255, rand() % 255, rand() % 255, 255));
        Vec3f n = (vf[1] - vf[0]) ^ (vf[2] - vf[0]);
        n.normalize();
        float I =  n * lightDir;
        if (I < 0) {
        triangle(vp, image, TGAColor(-I * 255, -I * 255, -I * 255, 255));
    }

注意我这里和官方代码不同的是 I < 0 I < 0 I<0, 说明 > 0 >0 >0 时三角形背向光源方向,直接剔除
并且颜色为 − I × 255 -I{\times}255 I×255
因为我求的三角形是按逆时针排序才法线朝外

Vec3f n = (vf[1] - vf[0]) ^ (vf[2] - vf[0]);

官方是

Vec3f n = (world_coords[2]-world_coords[0])^(world_coords[1]-world_coords[0]);

正好相反

效果如下
在这里插入图片描述
发现三角形边缘有黑边显示,怀疑是之前的判断点是否在三角形内,只有 < 0 <0 <0,没有 ≤ 0 {\leq}0 0,所以在三角形边上的点被忽略了,改下叉乘结果判断

bool flag = pa.x * a.y - pa.y * a.x <= 0;
    if (flag != pb.x * b.y - pb.y * b.x <= 0) return false;
    if (flag != pc.x * c.y - pc.y * c.x <= 0) return false;

最后正常了
在这里插入图片描述
其他模型效果
在这里插入图片描述
项目跟随练习代码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/475307.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 批量读取文件夹中图像

两种方法 一、用PIL库 import os from PIL import Imagedef read_images(folder_path):images []for filename in os.listdir(folder_path):if filename.endswith((.png, .jpg, .jpeg, .bmp, .tif, .tiff)):img_path os.path.join(folder_path, filename)image Image.open…

MES管理系统在生产计划与排产调度流程中的应用

在现代工业生产中&#xff0c;MES管理系统已经成为企业优化生产流程、提升生产效率的重要工具。MES管理系统在生产计划与排产调度中的具体应用&#xff0c;不仅能够帮助企业更好地控制生产过程&#xff0c;还能实现资源的合理配置&#xff0c;从而提高企业的整体竞争力。 首先&…

简单几点让你清楚VR全景制作方式,快来免费学习!

VR全景展示作为当下一种新型的宣传方式&#xff0c;能够有效解决商家企业的展示、宣传推广成本高的问题&#xff0c;也成为了实体门店获客引流的全新方式&#xff0c;助力行业实现低成本、高效率的宣传推广。 而从实际全景成品效果来看&#xff0c;不仅没有“美颜嫌疑”&#x…

蓝桥杯-模拟-航班时间

题目 思路 去时到达外地的时间-去时离开本地的时间 时区差时飞行时间 回时到达本地的时间-回时离开外地的时间 -时区差时飞行时间 故二者加起来即可得到飞行时间 代码 # 去时到达外地的时间-去时离开本地的时间 时区差时飞行时间 # 回时到达本地的时间-回时离开外地的时间 -…

初阶数据结构之---二叉树链式结构(二叉树的构建,二叉树的前序,中序,后序和层序遍历,计算二叉树结点个数,第k层结点个数,叶子结点个数,判断是否为完全二叉树)

引言 本篇博客是初阶数据结构树的收尾&#xff0c;将会讲掉基本二叉树链式结构的具体内容和实现&#xff0c;包括二叉树的构建&#xff0c;前序遍历&#xff0c;中序遍历&#xff0c;后序遍历和层序遍历&#xff0c;计算二叉树结点个数&#xff0c;第k层结点个数&#xff0c;二…

为什么高铁提前三分钟停止检票?

为什么高铁提前三分钟停止检票&#xff1f; 高铁&#xff0c;作为现代交通方式的代表&#xff0c;以其高速、便捷、舒适的特点受到了广大乘客的青睐。然而&#xff0c;在乘坐高铁的过程中&#xff0c;乘客们可能会遇到一个问题&#xff1a;为什么高铁会提前三分钟停止检票呢&a…

Vue.js+SpringBoot开发服装店库存管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服装档案模块2.4 服装入库模块2.5 服装出库模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 角色表3.2.2 服装档案表3.2.3 服装入库表3.2.4 服装出库表 四、系统展示五、核心代码5.…

Redis 又双叒叕改开源协议了,微软提前推出高性能替代方案 Garnet

Redis 官宣&#xff1a;是的&#xff0c;我们又改开源协议了 3 月 20 号&#xff0c;Redis 商业公司 CEO Rowan Trollope 在公司官方博客上宣布了一项重大变革。Redis 核心软件将从 BSD 3-Clause 许可证过渡到双重许可证模式&#xff0c;这一变化将从 Redis v7.4 版本开始&…

Java 应用程序监控

Java 监控涉及监控在 Java 上运行的应用程序的关键性能指标&#xff0c;以及 支持 Java 应用程序的服务器。Java 监控可以帮助优化 Java 应用程序的性能&#xff0c;发现和识别以下问题&#xff1a; 导致常见的应用程序问题&#xff0c;并在问题影响最终用户之前解决问题。 Ja…

美联储3月会议来袭,市场对6月开启降息或仍较为乐观

KlipC报道&#xff1a;美国联邦储备委员会结束了为期两天的货币政策会议&#xff0c;宣布仍继续将联邦基金利率目标区间维持在5.25%-5.50%之间不变。 值得一提的是会议声明声明几乎只字未改&#xff0c;唯一改动的是在描述就业市场时&#xff0c;将“新增就业过去一年有所放缓但…

解决Oracle VM VirtualBox无法与Windows互相复制粘贴的教程

说明&#xff1a;要实现从Windows上复制然后在VM VirtualBox上粘贴&#xff0c;必须要在VM VirtualBox上下载virtualbox-guest-dkms 第一步&#xff1a; 第二步&#xff1a; 按CtrlALTT打开命令终端输入 sudo apt-get install virtualbox-guest-dkms 然后重启 第三步&…

Linux下QT界面小程序开发

背景&#xff1a;需要在linux不同环境下可以测试我们的读卡器设备 搭建本地linux开发环境&#xff08;本来想VS里开发然后通过SSH的方式在linux下编译&#xff0c;但是工具链一直没搞起来&#xff0c;所以我是在ubuntu里安装的QT Creator工具直接开发的&#xff09;&#xff1b…

(一)手把手教你如何通过ARM DesignStart计划在FPGA上搭建一个Cortex-M3软核

&#xff08;一&#xff09;手把手教你如何通过ARM DesignStart计划在FPGA上搭建一个Cortex-M3软核 一、ARM DesignStart计划 1.1 如何下载ARM DesignStart Cortex-M3相关文件 ​ 关于ARM DesignStart计划的介绍:ARM DesignStart计划——私人定制一颗ARM处理器 - 知乎 (zhih…

Android ViewPager不支持wrap_content的原因

文章目录 Android ViewPager不支持wrap_content的原因问题源码分析解决 Android ViewPager不支持wrap_content的原因 问题 <androidx.viewpager.widget.ViewPagerandroid:id"id/wrap_view_pager"android:layout_width"match_parent"android:layout_he…

快速画流程图

使用在线工具&#xff1a;PlantUML PlantUML 官网:https://plantuml.com/ 中文官网:https://plantuml.com/zh/ 使用步骤如下&#xff1a; 1、拷贝一个完成函数&#xff1a; int func_init(const char *tag) {if (tag ! NULL) {printf("set TAG :%s", tag);}print…

登录校验:JWT令牌、Filter、Interceptor

JWT&#xff1a; 全称&#xff1a;JSON Web Token 定义了一种简洁的、自包含的格式&#xff0c;用于在通信双方以json数据格式安全的传输信息&#xff0c;由于数字签名的存在&#xff0c;这些信息是可靠的。 组成&#xff1a; Header(头部)&#xff1a;&#xff08;“alg&q…

每日学习笔记:C++ STL 的set、multiset

定义 (自动排序&#xff0c;可指定自定义排序准则) 排序准则 特点 &#xff08;不同通过迭代器直接修改元素值&#xff0c;只能先删除后添加&#xff09; 操作函数 运行期指定排序准则

【项目实践】VS配置Qt

文章目录 前言版本使用具体步骤1&#xff09;安装Qt或者添加删除组件2&#xff09;VS安装Qt Visual Studio Tools 如何使用遇到的问题双击ui文件编辑报错 前言 最近因为一个项目&#xff0c;需要使用Qt&#xff0c;本来想使用Python的&#xff0c;但是由于另外一个第三方的库是…

Linux:vim编辑器的使用

1.Vim的安装 Linux默认是安装了的vim&#xff0c;未安装则可以使用以下指令安装。 yum install vim -y 2.Vim的四种模式 &#xff08;1&#xff09;命令模式 使用VIM编辑器时&#xff0c;默认处于命令模式。在该模式下可以移动光标位置&#xff0c;可以通过快捷键对文件内容…

路灯单灯控制器 智慧路灯杆智能照明新宠

路灯单灯控制器是现代城市管理中非常重要的设备之一。它们不仅可以提供照明功能&#xff0c;还可以通过智能控制系统实现远程监控和调节。   路灯单灯控制器通过感知环境亮度和运行状态&#xff0c;实现对路灯的智能控制。它使用先进的传感器技术&#xff0c;能够实时感知路灯…