光栅化三角形

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. 简单软件架构的一些好处zz

    简单软件架构的一些好处_大数据_Dan Luu_InfoQ精选文章 Wave 是一家价值 17 亿美元的公司,拥有 70 名工程师,该公司的产品是一款加减数字的 CRUD 应用程序.为了与此保持一致, ...

  2. [python]Markdown图片引用格式批处理桌面应用程序

    需求 使用python编写一个exe,实现批量修改图片引用,将修改后的文件生成为 文件名_blog.md.有一个编辑框,允许接收拖动过来md文件,拖入文件时获取文件路径,有一个编辑框编辑修改后的文件的 ...

  3. 黑苹果(Hackintosh) - 问题,修改CPU数量和内存数量后,系统重启失败

    1. 问题复现 安装完黑苹果后,内存默认的 1个处理器2个核心.2G内存,发现不够用. 于是,修改了 VMware 对此系统的 硬件配置 内存: 2G -> 8G 处理器:1个处理器 -> ...

  4. kubernetes更改nodePort模式下的默认端口范围

    使用nodePort模式,官方默认范围为30000-32767,详见Service官方文档. NodePort 类型如果将 type 字段设置为 NodePort,则 Kubernetes 控制平面将 ...

  5. Qt音视频开发30-Onvif事件订阅

    一.前言 能够接收摄像机的报警事件,比如几乎所有的摄像机后面会增加报警输入输出接口,如果用户外接了报警输入,则当触发报警以后,对应的事件也会通过onvif传出去,这样就相当于兼容了所有onvif摄像机 ...

  6. Datawhale AI 夏令营-天池Better Synth多模态大模型数据合成挑战赛-baseline复现与理解总结(更新中)

    在大数据.大模型时代,随着大模型发展,互联网数据渐尽且需大量处理标注,为新模型训练高效合成优质数据成为新兴问题."天池 Better Synth - 多模态大模型数据合成挑战赛"应 ...

  7. 一种调试 线段树 / Treap / Splay / 左偏树 / LCT 等树形结构的技巧

    前言 如果我们需要观察程序运行过程中,某一个变量.某一个序列的变化情况,你可以在修改的地方打断点 debug,或者直接在需要的地方输出就行了. 但是对于一些树形结构,我们不好将其直观地呈现出来,常常只 ...

  8. CDS标准视图:功能位置可用标签 I_FUNCNLLOCALTERNATIVELABEL

    视图名称:功能位置可用标签 I_FUNCNLLOCALTERNATIVELABEL 视图类型:基础 视图代码: 点击查看代码 @EndUserText.label: 'Functional Locat ...

  9. IdentityServer4中的核心类

    启动配置器IIdentityServerBuilder 可以把它理解为一个IServiceCollection的容器,它商品有几个扩展方法,方便我们用来注册ids使用到的相关服务,为啥不直接扩展ISe ...

  10. w3cschool-Lua编程入门

    https://www.w3cschool.cn/nhycto/ https://www.w3cschool.cn/cf_web/cf_web-dvxc32qu.html 1. Lua 基础知识 (1 ...