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

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

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

本文源码 :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. 跟我一起学python(1):占位符

    模板 格式化字符串时,Python使用一个字符串作为模板.模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的格式.Python用一个tuple将多个值传递给模板,每个值对应一个格式符 ...

  2. java线程与内核线程的关系,及怎么定义ThreadPoolExecutor相关参数

    p.p1 { margin: 0; font: 12px Menlo } p.p1 { margin: 0; font: 12px Menlo } p.p2 { margin: 0; font: 12 ...

  3. java中jna使用回调实现事件监听器——观察者模式

    (1)C接口中的接口头文件 #pragma once #include <iostream> #include <vector> #include <core/core. ...

  4. sql操作数据库(3)-->外键约束、数据库表之间的关系、三大范式、多表查询、事务

    外键约束 在新表中添加外键约束语法: constraint 外键约束名称 foreign key(外键的字段名称) references 主表表名(主键字段名) 在已有表中添加外键约束:alter t ...

  5. 在onelogin中使用OpenId Connect Implicit Flow

    目录 简介 OpenId Implicit Flow 创建onelogin的配置 页面的运行和请求流程 关键代码 总结 简介 onelogin支持多种OpenId Connect的连接模式,上一篇文章 ...

  6. oop的三大特性和传统dom如何渲染

    OOP的三大特性是什么: 封装 :就是将一个类的使用和实现分开,只保留部分接口和方法与外部联系继承:子类自动继承其父级类中的属性和方法,并可以添加新的属性和方法或者对部分属性和方法进行重写.继承增加了 ...

  7. Manjaro Linux 5.9.11-3安装和配置全局截图工具FlameShot教程

    背景说明 截图工具是日常适用频率较高的一种系统工具,在Linux下也有不少常用截图工具,如deepin-screenshot等,但是今天我们要介绍的是FlameShot--一款更加精致的Linux全局 ...

  8. Python pip install 默认路径修改。

    pip动不动就下载数百M的文件.这些文件默认在C:盘,那么为了节省空间需要修改这些路径: 打开cmd命令窗口.输入: python -m site C:\Users\hewei>python - ...

  9. 【Linux】关于CentOS系统中,文件权限第11位上是一个点的解读

    ------------------------------------------------------------------------------------------------- | ...

  10. 【Linux】iptables的内核模块问题大坑!

    系统环境 CentOS 6.5 今天本来可以平静的度过一天,正品味着下午茶的美好,突然接到防火墙iptables的报警. 进入到服务器中,执行下面的命令查看,结果报错 /etc/init.d/ipta ...