如果你喜欢我写的文章,可以把我的公众号设为星标 ,这样每次有更新就可以及时推送给你啦。

前面两天画了点和线,今天我们来画一个最简单也是最强大的面——三角形

本文主要讲解三角形绘制算法的推导和思路(只涉及到一点点的向量知识),最后会给出代码实现,大家放心的看下去就好。

本文源码 :toyRenderer-day3-draw-triangle

1.如何画一个三角形?

在正式开始这一小节前,我们先想一下如何利用上一节的画线算法绘制一个实心的三角形。

假设现在平面内有三个不共线的点组成一个三角形,我们可以利用上一节的直线算法轻易的连接三角形的三条边,这时候我们会生成一个空心的封闭的三角形。

那么这时候问题就转换为,如何把这个空心的三角形变为一个实心的三角形?

我想大家这时候已经有思路了,就是一行一行地扫描像素,把两个边界点之间的像素全部涂满上色就可以了。


day03_scanLineDrawTriangle

这个方法肯定是可以的,但是实现不是很优雅,也不是业内的主流实现方式。因为基于行扫描的算法不是本文的重点,所以详细的推导和代码实现就不提供了,感兴趣的同学可以自己尝试实现一下。

2.利用向量叉乘画三角形

开始本节前先简单复习一下向量叉乘的几何意义。

2.1 数学推导

在三维空间中,两个三维向量 和 做叉乘,会得到一个和已知两个向量垂直的新向量 。

既然叉乘产生的是一个新向量,那么它肯定有个方向,我们一般用右手定则来判断:将右手食指指向 的方向、中指指向 的方向,则此时拇指的方向即为 的方向。


右手定则

综上所述,我们可以对向量叉乘做一个严谨的定义:

其中 表示 和 在它们所定义的平面上的夹角()。 和 是向量 和 的模长,而 则是一个与 、 所构成的平面垂直的单位向量,方向由右手定则决定。

有上面的理论,我们就可以判断两个向量的相对位置:

  • 向量叉乘 向量,如果值为,则表示 向量在 向量
  • 向量叉乘 向量,如果值为,则表示 向量在 向量
  • 向量叉乘 向量,如果值为,则表示 向量与 向量共线

会判断两条线的相对位置了,我们可以做个理论迁移,利用向量叉乘判断点和三角形的位置关系

例如下面这里例子,对于三角形 来说,把三条边看作 、 、 三条首尾相连的向量,平面内有一个点 ,我们通过向量叉乘来判断相对位置:


day03_cross_product
  • ,值为,故 在 左侧
  • ,值为,故 在 左侧
  • ,值为,故 在 左侧

综合以上三个限制条件,我们可以判断 在 内。

如果上面三个计算中有值为负的情况,说明 在三角形外;如果有值为 0 的情况,说明 在三角形的边或顶点上。

2.2 代码实现

理论基础复习完了,我们就可以写代码了。代码实现相当简单,我们构建一个函数 crossProduct,传入三角形的三个顶点和平面上的任意一点 ,然后根据四个顶点构建出向量计算叉乘就可以了:

// 利用叉乘判断是否在三角形内部
Vec3i crossProduct(Vec2i *pts, Vec2i P) {
    // 构建出三角形 ABC 三条边的向量
    Vec2i AB(pts[1].x - pts[0].x, pts[1].y - pts[0].y);
    Vec2i BC(pts[2].x - pts[1].x, pts[2].y - pts[1].y);
    Vec2i CA(pts[0].x - pts[2].x, pts[0].y - pts[2].y);
    
    // 三角形三个顶点和 P 链接形成的向量
    Vec2i AP(P.x - pts[0].x, P.y - pts[0].y);
    Vec2i BP(P.x - pts[1].x, P.y - pts[1].y);
    Vec2i CP(P.x - pts[2].x, P.y - pts[2].y);
    
    return Vec3i(AB^AP, BC^BP, CA^CP);
}

代码非常的简单,我们跑一个简单的例子验证一下:

void drawSingleTriangle() {
    // 图片的宽高
    int width  = 200;
    int height = 200;

    TGAImage frame(width, height, TGAImage::RGB);
    Vec2i pts[3] = {Vec2i(10, 10), Vec2i(150, 30), Vec2i(70, 160)};

    Vec2i P;
    // 遍历图片中的所有像素
    for (P.x = 0; P.x <= width - 1; P.x++) {
        for (P.y = 0; P.y <= height - 1; P.y++) {
            Vec3i bc_screen  = crossProduct(pts, P);

            // bc_screen 某个分量小于 0 则表示此点在三角形外(认为边也是三角形的一部分)
            if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) {
                continue;
            }
            
            image.set(P.x, P.y, color);
        }
    }
    
    frame.flip_vertically();
    frame.write_tga_file("output/day03_cross_product_triangle.tga");
}

看输出图像,我们已经成功绘制了一个三角形:


day03_cross_product_triangle

触不及防的安利:大家可以看我头像关注

【十天自制软渲染器】DAY 03:画一个三角形(向量叉乘算法 & 重心坐标算法)的更多相关文章

  1. 【十天自制软渲染器】DAY 01:图形学学习建议与环境搭建

    推荐直接阅读博客原文,更新更及时,阅读体验更佳 「十天自制软渲染器」这个标题我承认标题党了.在对图形学一无所知的情况下想十天自制一个软渲染器,就好似一节课没上过却试图一个晚上看完<30 天精通 ...

  2. 【十天自制软渲染器】DAY 02:画一条直线(DDA 算法 & Bresenham’s 算法)

    推荐关注公众号「卤蛋实验室」或访问博客原文,更新更及时,阅读体验更佳 第一天我们搭建了 C++ 的运行环境并画了一个点,根据 点 → 线 → 面 的顺序,今天我们讲讲如何画一条直线. 本文主要讲解直线 ...

  3. CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL

    CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL +BIT祝威+悄悄在此留下版了个权的信息说: 开始 本文用step by step的方式,讲述如何使 ...

  4. 基于物理渲染的渲染器Tiberius计划

    既然决定实现一个光栅化软件渲染器,我又萌生了一个念头:实现一个基于物理渲染的渲染器.

  5. Django:之Sitemap站点地图、通用视图和上下文渲染器

    Django中自带了sitemap框架,用来生成xml文件 Django sitemap演示: sitemap很重要,可以用来通知搜索引擎页面的地址,页面的重要性,帮助站点得到比较好的收录. 开启si ...

  6. 基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构

    事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒 ...

  7. 29.渲染器Renderer

    什么是渲染器     渲染器就是将服务器生成的数据格式转为http请求的格式   渲染器触发及参数配置 在DRF配置参数中,可用的渲染器作为一个类的列表进行定义 但与解析器不同的是,渲染器的列表是有顺 ...

  8. 用 windows GDI 实现软光栅化渲染器--gdi3d(开源)

    尝试用windows GDI实现了一个简单的软光栅化渲染器,把OpenGL渲染管线实现了一遍,还是挺有收获的,搞清了以前一些似是而非的疑惑. ----更新2015-10-16代码已上传.gihub地址 ...

  9. Restful framework【第十篇】响应器(渲染器)

    基本使用 -响应器(一般用默认就可以了) -局部配置 renderer_classes=[JSONRenderer,] -全局配置 'DEFAULT_RENDERER_CLASSES': ( 'res ...

随机推荐

  1. Thymeleaf Shiro标签

    记录一下 guest标签 <shiro:guest> </shiro:guest> 用户没有身份验证时显示相应信息,即游客访问信息. user标签 <shiro:user ...

  2. 对象、对象监视器、同步队列、执行线程关系(synchronized的实现细节或原理)

    synchronized在使用的时候底层细节你了解吗,相信很多同学对细节很少关注:比如竞争失败了的线程怎么安置,每个对象的监视器,线程执行synchronized时,其实是获取对象的监视器才能进入同步 ...

  3. 企业集群架构-03-NFS

    NFS 目录 NFS NFS基本概述 NFS应用场景 NFS实现原理 NFS总结 NFS服务端安装 环境准备 服务端安装NFS 服务端NFS配置 服务端开机自启 服务端验证配置 NFS客户端挂载卸载 ...

  4. JavaScript--总结一(变量+数据类型+运算符)

    JavaScript是什么? 是一门脚本语言(不需要编译,直接执行) 是一门解释性语言 是一门动态类型的语言 是一门基于对象的语言 JavaScript分为三个部分 1.ECMAScript  标准- ...

  5. 第六章节 BJROBOT 动态导航壁障

    导航前说明:一定要确保你小车在构建好地图的基础上进行! 1.把小车平放在你想要构建地图区域的地板上,打开资料里的虚拟机,打开一个终端, ssh 过去主控端启动 roslaunch znjrobot b ...

  6. Java中jna的用法

    (1)jna是对jni的封装,让java使用者能更好的使用本地的动态库 (2)使用jna需要下载jna的jar包,该jar包就是对jni的封装,所以在调用效率上来讲,jna是要比jni低一点的,不过对 ...

  7. SpringBoot整合Shiro权限框架实战

    什么是ACL和RBAC ACL Access Control list:访问控制列表 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系 ...

  8. java.io.NotSerializableException: org.apache.kafka.clients.consumer.ConsumerRecord

    kafka 与spark集成 序列化问题 sparkConf.set("spark.serializer", "org.apache.spark.serializer.K ...

  9. Spring框架之spring-webmvc源码完全解析

    Spring框架之spring-webmvc源码完全解析 Spring框架提供了构建Web应用程序的全功能MVC模块.Spring MVC分离了控制器.模型对象.分派器以及处理程序对象的角色,支持多种 ...

  10. Databricks 第6篇:Spark SQL 维护数据库和表

    Spark SQL 表的命名方式是db_name.table_name,只有数据库名称和数据表名称.如果没有指定db_name而直接引用table_name,实际上是引用default 数据库下的表. ...