光栅化三角形

Scanline rendering(扫描线渲染),一个老式的算法

  • 按y轴坐标进行排序,我这里采取降序,ay > by > cy
  • 同时光栅化三角形的左右两边
  • 绘制水平线段,连接左右边界点
不理解的可以看这里
这个很好理解,从最高的顶点出发,同步绘制此顶点连接的两条边,在每条边渲染的y值都发生一次变化,此时y值相同,绘制其连线即可。

通过排序可以做到:在逻辑上ay就是最大的,这样可以减少大量没有必要的工作。

这是一张之前失败的图片,应该有一些辅助效果。

这里记录一下我最初的尝试

友情提示:该代码有些伤眼,请谨慎观看:

先是对y坐标进行了降序,ay > by > cy;

//y坐标降序
if(ay < by) { std::swap(ax, bx); std::swap(ay, by); }
if(ay < cy) { std::swap(ax, cx); std::swap(ay, cy); }
if(by < cy) { std::swap(bx, cx); std::swap(by, cy); }
之后我采取了先绘制三条边的策略,(现在看来是选了个不得了的策略)
在之前我们绘制一条直线时,需要求得两点steep,所以这次绘制三条直线,我计算了三次
没错,我计算了三个steep,这使我不得不考虑swap将会导致的混乱:
我选择了简单暴力的方法,copy一份。

//副本
int ax_copy = ax;
int ay_copy = ay;
int bx_copy = bx;
int by_copy = by;
int cx_copy = cx;
int cy_copy = cy;

//直线,消gap
//ab
bool steep_ab_gap = std::abs(bx - ax) < std::abs(by - ay);
if(steep_ab_gap){
std::swap(ax,ay);
std::swap(bx,by);
}
//ac
bool steep_ac_gap = std::abs(cx - ax_copy) < std::abs(cy - ay_copy);
if(steep_ac_gap){
std::swap(ax_copy,ay_copy);
std::swap(cx,cy);
}
//bc
bool steep_bc_gap = std::abs(cx_copy - bx_copy) < std::abs(cy_copy - by_copy);
if(steep_bc_gap){
std::swap(bx_copy,by_copy);
std::swap(cx_copy,cy_copy);
}
在消除缺口后,需要使光栅化从左至右进行,这样可以让我少写一些重复的代码。
不过这次需要存储大小比较的结果,而不直接交换,我不想打破y的排序,先以实现为先

//x大小确定
//ab
bool size_x1 = (bx < ax);
//ac
bool size_x2 = (cx < ax_copy);
//bc
bool size_x3 = (cx_copy < bx_copy);
好了,现在我可以以更水平的方式从左至右光栅化三条边,这也导致了一个问题,我无法统一每条边是按y或是x进行光栅化。
嘿,我又有了一个简单暴力的方法,我把每一次y+1或者x+1的第一个像素坐标保存,
在同步光栅化边和填充的问题中,我选择了将他们分开进行。 我需要三个std::vector<Vector3>来存储每条边需要的像素点

//pixel坐标
std::vector<Vector3> a_b;
std::vector<Vector3> a_c;
std::vector<Vector3> b_c;
接下来就是line()函数的改版,c_line(),引入了参数std::vector<Vector3>& tri_pixl

//创建了Vector3 temp,赋给引用的std::vector<Vector3>& tri_pixl
void c_line(int ax,int ay,int cx,int cy,bool size_x,bool steep_gap,TGAImage &framebuffer,TGAColor color,std::vector& tri_pixl){ int ierror = 0;
int y = ay;
Vector3 temp; if(size_x){
for(int x = ax; x > cx; x --){
//渲染,存储
if(steep_gap){
framebuffer.set(y,x,color);
//取点
ierror += 2 * std::abs(cy - ay);
if(ierror > (ax - cx)){
y += cy > ay ? 1 : -1;
ierror -= 2 * (ax - cx);
}
//存储
temp.x = y;
temp.y = x - 1;
tri_pixl.push_back(temp);
}
else{
framebuffer.set(x,y,color);
ierror += 2 * std::abs(cy - ay);
if(ierror > (ax - cx)){
y += cy > ay ? 1 : -1;
ierror -= 2 * (ax - cx); temp.x = x - 1;
temp.y = y;
tri_pixl.push_back(temp);
}
}
}
}
else{
for(int x = ax; x < cx; x ++){
if(steep_gap){
framebuffer.set(y,x,color);
ierror += 2 * std::abs(cy - ay);
if(ierror > (cx - ax)){
y += cy > ay ? 1 : -1;
ierror -= 2 * (cx - ax);
} temp.x = y;
temp.y = x + 1;
tri_pixl.push_back(temp);
} else{
framebuffer.set(x,y,color);
ierror += 2 * std::abs(cy - ay); if(ierror > (cx - ax)){
y += cy > ay ? 1 : -1;
ierror -= 2 * (cx - ax); temp.x = x + 1;
temp.y = y;
tri_pixl.push_back(temp);
}
}
}
}
}
现在我有了每条边每行每列第一个光栅化的像素点的坐标,只需要按行或按列填充即可

//填充
void straight_line(int ax,int ay,int bx,int by,TGAImage &framebuffer,TGAColor color){
if(ax > bx){ std::swap( ax, bx );}
for(int x = ax; x <= bx; x++){
framebuffer.set(x,ay,color);
}
}

遍历


size_t i = 0;
size_t i_bc = 0;
for(;i < a_b.size(); i ++){ straight_line(a_b[i].x, a_b[i].y, a_c[i].x, a_c[i].y, framebuffer, color); } for(i_bc = 0;i_bc < b_c.size(); i ++, i_bc ++){ straight_line(b_c[i_bc].x, b_c[i_bc].y, a_c[i].x, a_c[i].y, framebuffer, color); }




进行扫描线渲染,作者写了新的triangle函数

首先进行了冒泡排序,采用的升序,不过我用的降序。


//verts.y排序,冒泡
if(ay < by) { std::swap(ax, bx); std::swap(ay, by); }
if(ay < cy) { std::swap(ax, cx); std::swap(ay, cy); }
if(by < cy) { std::swap(bx, cx); std::swap(by, cy); }

扫描线渲染,我们需要两边的像素点作为开始和结束,b点将成为我们的分界点,用来区分ab,bc直线。

在b点水平切割,绘制它的上半部分

我们这次不选择去更换x或y轴,忽视间隙的产生,计算左右边界,遍历填充

这里有我的理解
仔细想一下,假设我们每次y --,那么可能会遇到y - 1,而x + n的情况,导致y和y-1在水平上有n - 1个间隙,之前绘制直线我们会等到在y填补完这些间隙后,才会跳到y - 1,进行第x + n的绘制。
现在我们进行填充,会直接从左边界绘制到有边界,不需要去考虑到下一行会有几个间隙了,因为都被填充了。

int total_height = ay - cy;
if(ay != by){
int segment_height = ay - by;
for(int y = ay; y >= by; y --){
int x_l = ax + ((ax - bx) * (y - ay) / segment_height);
int x_r = ax + ((ax - cx) * (y - ay) / total_height);
for(int x = std::min(x_l, x_r); x <= std::max(x_l, x_r); x ++){ framebuffer.set(x, y, color);}
}
}

之后是下半部分


if(by != cy){
for(int y = by; y >= cy; y--){
int segment_height = by - cy;
int x_l = bx + ((bx - cx) * (y - by) / segment_height);
int x_r = ax + ((ax - cx) * (y - ay) / total_height);
for(int x = std::min(x_l, x_r); x <= std::max(x_l, x_r); x ++){ framebuffer.set(x, y, color);}
}
}

扫描线光栅化是老式的方案,现在我们可以采取现代一些的光栅化方法

  • 先找一个包围盒,有三角形的坐标很好计算,选取最小和最大的坐标即可


    //计算包围盒
    int bbminx = std::min(std::min(ax,bx),cx);
    int bbminy = std::min(std::min(ay,by),cy);
    int bbmaxx = std::max(std::max(ax,bx),cx);
    int bbmaxy = std::max(std::max(ay,by),cy);
  • 之后计算包围盒内点在不在三角形内,有很多算法,这里采取计算重心坐标。

    这里通过计算PAB,PBC,PCA占ABC面积的权重来获取重心坐标,这里暂时不进一步介绍。


    #pragma omp parallel for
    for(int x = bbminx; x <= bbmaxx; x++){
    for(int y = bbminy; y <= bbmaxy; y++){
    //重心坐标
    double alpha = signed_triangle_area(x,y,bx,by,cx,cy) / total_area;
    double beta = signed_triangle_area(x,y,ax,ay,bx,by) / total_area;
    double gamma = signed_triangle_area(x,y,cx,cy,ax,ay) / total_area;
    if(alpha < 0 || beta < 0 || gamma < 0) continue;
    framebuffer.set(x,y,color);
    }
    }
  • 如果需要考虑正反面的情况,要进行一次裁剪

    注:这次裁剪将会删除所有背向三角形


    double total_area = signed_triangle_area(ax,ay,bx,by,cx,cy);
    if (total_area < 1) return;//删除覆盖少于一pixel的三角形

记录:tinyrenderer---1.2 Rasterizing the boundary的更多相关文章

  1. [Swift]LeetCode5. 最长回文子串 | Longest Palindromic Substring

    Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...

  2. Matlab 进阶学习记录

    最近在看 Faster RCNN的Matlab code,发现很多matlab技巧,在此记录: 1. conf_proposal  =  proposal_config('image_means', ...

  3. 嵌入式 uboot以及kernel添加看门狗临时记录(个人记录未整理乱)

    Uboot_Kernerl_Add_Watch_Dog: U-Boot 2010.06 (Nov 01 2013 - 15:28:44) DRAM:  128 MiBCheck spi flash c ...

  4. Leetcode 记录(101~200)

    Now, I want to just use English to explain the problem, it's about two month before the interview, s ...

  5. hadoop 安装过程记录

    1)首先配置好了四个linux虚拟机 root pwd:z****l*3 关闭了防火墙 开通了 sshd服务 开通了 ftp服务 配置了 jdk 1.8 配置好了互信 (之前配置的过程忘了!--检查了 ...

  6. leveldb 学习记录(七) SSTable构造

    使用TableBuilder构造一个Table struct TableBuilder::Rep { // TableBuilder内部使用的结构,记录当前的一些状态等 Options options ...

  7. LVM基础详细说明及动态扩容lvm逻辑卷的操作记录

    LVM概念:---------------------------------------------------------------------------------------------- ...

  8. uml 图学习记录

    UML类图与类的关系详解   2011-04-21 来源:网络   在画类图的时候,理清类和类之间的关系是重点.类的关系有泛化(Generalization).实现(Realization).依赖(D ...

  9. sharc dsp 学习记录1---2014-07-30

    从今天开始记录学习sharc dsp过程中的点点滴滴吧.   DPI:Digital Peripheral Interface DAI:Digital Audio Interface   SHARC ...

  10. ZCU板级调试Bug记录

    本帖用以记录在ZCU102板级调试间遇到的Bug. 1.PL端的AXI总线在读取DDR中的数据的时候,在一个burst内不能跨越page boundary.跨越page boundary会在该burs ...

随机推荐

  1. Docker 容器运行一个 web 应用

    Docker 容器安装和基础使用请看上一篇 Docker 容器运行一个 web 应用 使用 docker 构建一个 web 应用程序. docker pull training/webapp # 载入 ...

  2. OpenWrt安装腾讯云DDNS插件

    1.插件介绍 OpenWRT TencentDDNS插件是一款腾讯云研发的,自动映射动态公网IP至用户指定的DNSPod域名解析记录的官方插件. 标题 名称 中文名称 腾讯云DDNS插件 英文名称 l ...

  3. .NET 响应式编程 System.Reactive 系列文章(一):基础概念

    .NET 响应式编程 System.Reactive 系列文章(一):基础概念 引言 在现代软件开发中,处理异步事件和数据流已经成为常见的需求,比如用户输入.网络请求.传感器数据等.这些数据流通常是无 ...

  4. Solution Set -「NOIP Simu.」20221014

    \(\mathscr{A}\sim\)「Unknown」tothecrazyones   有 \(n\) 堆石子, 第 \(i\) 堆有 \(a_i\) 个. Alice 和 Bob 轮流抓取, Al ...

  5. 深入LinkedBlockingQueue实现原理

    学习BlockingQueue之LinkedBlockingQueue实现原理   一:概念 LinkedBlockingQueue是一个用链表实现的有界阻塞队列.此队列的默认和最大长度为 Integ ...

  6. 文章学习:基于AVX-512指令集的同态加密算法中大整数运算性能优化与突破

    学习文章:英特尔×同态科技 | 基于AVX-512指令集的同态加密算法中大整数运算性能优化与突破 文章 人工智能的安全隐患 ChatGPT的成功大部分来源于海量的数据支撑和丰富的数据维度,基于13亿参 ...

  7. 『Python底层原理』--CPython 虚拟机

    在 Python 编程的世界里,我们每天都在使用 python 命令运行程序,但你是否曾好奇这背后究竟发生了什么? 本文将初步探究 CPython(Python 中最流行的实现)的一些内部机制,为了更 ...

  8. STM32IO口模拟IIC时序

    正点原子IIC讲解:https://www.bilibili.com/video/BV1o8411n7o9/?spm_id_from=333.337.search-card.all.click& ...

  9. react报错Can't resolve 'react' in 'E:\reactweb\preact\my-app\node_modules\react-dom\cjs'

    执行如下: npm install -g react npm install react --save 类似这种依赖项(react,react-dom 等)报错,哪个报错执行哪个即可 执行上述两句就 ...

  10. 2025年,Fantastic-admin 这款后台框架将继续引领潮流

    前言 大言不惭的取了这个标题,但作为开发了 4 年多的 Fantastic-admin 的作者,回顾这一路走来,从一开始被指责抄袭,到现在拥有数百名付费用户和几十家付费企业.我认为我的开发理念应该是得 ...