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

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

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

本文源码 :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. 回顾maven项目的spring boot相关知识点

    2021新年快乐! 在参加完研究生考试后,感觉像是放下了一个大负担,但并不能就此以为什么都结束了.反而,当我今天去看了一下之前老师带领我们班级做的一个maven项目,感觉像是第一次看到这个,十分陌生. ...

  2. LAMP搭建示例

    lamp介绍 其实就是由Linux+Apache+Mysql/MariaDB+Php/Perl/Python的一组动态网站或者服务器的开源软件,除Linux外其它各部件本身都是各自独立的程序,但是因为 ...

  3. 第14章节 BJROBOT karto 算法构建地图【ROS全开源阿克曼转向智能网联无人驾驶车】

    建地图前说明:请确保你的小车已经校正好 IMU.角速度.线速度,虚拟机配置好 ROS 网络的前提进行,否则会造成构建地图无边界.虚拟机端无法正常收到小车主控端发布的话题数据等异常情况!! 1.把小车平 ...

  4. python之shelve、xml、configparser模块

    一.shelve模块 shelve模块比pickle模块简单,只有一个open函数,返回类似字典的对象,可读可写;key必须为字符串,而值可以是python所支持的数据类型 import shelve ...

  5. SpringCloud 源码系列(6)—— 声明式服务调用 Feign

    SpringCloud 源码系列(1)-- 注册中心 Eureka(上) SpringCloud 源码系列(2)-- 注册中心 Eureka(中) SpringCloud 源码系列(3)-- 注册中心 ...

  6. 在.NET Core中使用Channel(二)

    在我们之前的文章中,看了一些非常简单的例子来说明Channel是如何工作的,我们看到了一些非常漂亮的特性,但大多数情况下它与其他某某Queue实现非常相似.让我们进入一些更高级的话题.我说的是高级,但 ...

  7. RPC框架学习+小Demo实例

    一.什么是RPC协议? 全称:远程过程调度协议 效果:使消费者向调用本地方法一样调用远程服务方法,对使用者透明 目前常用:Dubbo.Thirft.Sofa.... 功能: 建立远程通信(socket ...

  8. SpringgBoot父子工程的创建

    知识:SpringBoot父子工程创建 花开堪折直需折,莫待无花空折枝 开始之前,非常非常有必要了解一下关于以及的区别,这样才可以在进行创建maven父子工程种避免一些不必要的意外错误. depend ...

  9. springboot异常处理之404

    ps: 推荐一下本人的通用后台管理项目crowd-admin 以及newbee-mall增强版,喜欢的话给个star就好 源码分析 在springboot中默认有一个异常处理器接口ErrorConto ...

  10. tf.argmax(vector,axis)函数的使用

    1.返回值 vector为向量,返回行或列的最大值的索引号: vector为矩阵,返回值是向量,返回每行或每列的最大值的索引号. 2.参数 vector为向量或者矩阵 axis = 0 或1 0:返回 ...