[从零构建光栅渲染器] 3.隐藏面剃除 z-buffer(深度缓冲)

Z Buffer与Z值 Z buffer应该是大家最为熟悉的缓冲区类型,又称为depth buffer,即深度缓冲区,其中存放的是视点到每个像素所对应的空间点的距离衡量,称之为Z值或者深度值。 可见物体的Z值范围位于[0,1]区间,默认情况下,最接近眼睛的顶点(近裁减面上)其Z值为0.0,离眼睛最远的顶点(远裁减面上)其Z值为1.0.使用z buffer可以用来判断空间点的遮挡关系,著名的深度缓冲区算法(depth-b 阅读详情

非常感谢和推荐Sokolov的教程,Sokolov使用500行C++代码实现一个光栅渲染器。教程学习过程非常平滑,从画点、线和三角形开始教学,在逐步深入三维变换,投影,再到顶点着色器,片段着色器等等。教程地址:https://github.com/ssloy/tinyrenderer。Sokolov的教程为英文,我翻译了其文章。

在学习过程中,有些内容可能您可能云里雾里,这时就需要查阅《计算机图形学》的书籍了,这里面的算法和公式可以帮助您理解代码。

作者:尹豆(憨豆酒),联系我yindou97@163.com,熟悉图形学,图像处理领域,本章代码: https://github.com/douysu/computer-graphics-notes

本章运行结果图片

引言

你好,让我来给你介绍一下我的黑人朋友z-buffer。他会帮助我们摆脱隐藏面的我们上节课中的视觉假象。
图片
顺便提一下,我在课程中经常使用创建于Vidar Rapp的模型。他给了我许可去教学渲染课程并且允许我修改模型,但是我向你承诺接下来会加上眼睛。

好的,回到话题上。理论上在没有丢弃三角形的情况下我可以绘制所有的三角形。如果我从后到前正确的操作,前面的面会挡住后面的面。这个叫做画家算法painter’s algorithm。不幸的是,这个伴随着比较高的计算资源:每一次摄像机移动,我们都得对场景进行重新排序。然后,全是动态场景。。。。这不是主要的问题,主要的问题是并非总是能够确定正确的顺序。

让我们尝试渲染一个简单的场景

想象一个只有三个三角形的简单场景:摄像机从上而下,我们将这些彩色的三角形投影到一个白色的屏幕上:
图片
这个渲染看起来像这样:
图片
蓝色面-在红色的后面还是前面?画家算法在这里是不起作用的。它会将蓝色的面分割成两部分。(一部分在红色前面,一部分在红色后面)然后在红色前面的蓝色部分还会被绿色分割成两部分-一部分在绿色前面,一部分在绿色后面。。。。我认为你会有烦恼:场景中有数百万的三角形,进行计算是非常昂贵的,它可能使用BSP树 BSP trees 去解决。顺便提一下,这个数据结构对于相机的移动是不变的,但是它真的很混乱。人生苦短,不能乱了阵脚。

更简单的例子:让我们减小一维度,Y-buffer!!!

让我们丢掉一个维度,用黄色的平面去切割场景:
图片
我的意思是,现在我们的场景由三个线段构成,黄色平面和每个三角形的交线。最终的渲染具有正常的宽度,但高度为1像素。
图片
一如既往,这里是commit。我们的场景是二维的,所以使用我们第一节课写的line()方法去绘制它是非常简单的。

    { // just dumping the 2d scene (yay we have enough dimensions!)
        TGAImage scene(width, height, TGAImage::RGB);

        // scene "2d mesh"
        line(Vec2i(20, 34),   Vec2i(744, 400), scene, red);
        line(Vec2i(120, 434), Vec2i(444, 400), scene, green);
        line(Vec2i(330, 463), Vec2i(594, 200), scene, blue);

        // screen line
        line(Vec2i(10, 10), Vec2i(790, 10), scene, white);

        scene.flip_vertically(); // i want to have the origin at the left bottom corner of the image
        scene.write_tga_file("scene.tga");
    }

如果我们从侧面去看,我们的场景是这样的:
图片
让我们来渲染它。回顾一下,我们的渲染器只有1像素高。在我们的源码中,我创建16像素高的图像为了在高分辨率的屏幕上去查看, rasterize()方法在图像渲染中的第一行写入。

        TGAImage render(width, 16, TGAImage::RGB);
        int ybuffer[width];
        for (int i=0; i<width; i++) {
            ybuffer[i] = std::numeric_limits<int>::min();
        }
        rasterize(Vec2i(20, 34),   Vec2i(744, 400), render, red,   ybuffer);
        rasterize(Vec2i(120, 434), Vec2i(444, 400), render, green, ybuffer);
        rasterize(Vec2i(330, 463), Vec2i(594, 200), render, blue,  ybuffer);

所以,我声明了一个维度是(width,1)的数组ybuffer,数组初始化值为负无穷大、然后我使用ybuffer和渲染图像作为参数,这个方法是这样:

void rasterize(Vec2i p0, Vec2i p1, TGAImage &image, TGAColor color, int ybuffer[]) {
    if (p0.x>p1.x) {
        std::swap(p0, p1);
    }
    for (int x=p0.x; x<=p1.x; x++) {
        float t = (x-p0.x)/(float)(p1.x-p0.x);
        int y = p0.y*(1.-t) + p1.y*t;
        if (ybuffer[x]<y) {
            ybuffer[x] = y;
            image.set(x, 0, color);
        }
    }
}

真的非常简单,我从p0.x与p1.x之间开始迭代并且计算线段对应的y坐标。然后我用当前的x索引检查我们在数组ybuffer中得到了什么。如果当前的y值比ybuffer里面的值更加接近相机,我会将它绘制在屏幕上并且更新ybuffer中的值。

让我们一步步的看。在执行rasterize()以后,红色线段在内存中是这样的:

屏幕:
图片
ybuffer:
图片
品红代表着负无穷大。这些对应着我们没有接触到的屏幕。其他接触到的都是灰色。越浅代表越接近摄像机,越黑代表离摄像机越远。

然后我们绘制绿色的线段:

屏幕:
图片
ybuffer:
图片
最后是蓝色的。

屏幕:
图片
ybuffer:
图片
祝贺,我们在1D屏幕上绘了2D场景。让我们再一次欣赏一下渲染:
图片

回到3D

所以为了绘制在2D屏幕上,我们需要使用一个二维数组:

int *zbuffer = new int[width*height];

就我个人而言,我把一个二维的缓冲区打包成一维,转换起来很简单。

int idx = x + y*width;

转换成x,y:

int x = idx % width;int y = idx / width;

然后在代码中我迭代了所以三角形,然后用当前三角形和zbuffer的引用来调用rasterizer函数。

比较困难的就是如何计算想绘制像素的z值,让我们回顾我们怎样在y-buffer中计算的y值。

        int y = p0.y*(1.-t) + p1.y*t;

t变量的性质是什么?(1 - t, t)是点(x,y)相对于线段p0,p1的重心坐标:(x,y)= p0*(1 - t)+ p1 * t。因此,我的想法是采用三角形光栅化的重心坐标,对于我们要绘制的每一个像素,只需要将其重心坐标乘以我们光栅化的三角形顶点z值即可。

triangle(screen_coords, float *zbuffer, image, TGAColor(intensity*255, intensity*255, intensity*255, 255));

[...]

void triangle(Vec3f *pts, float *zbuffer, TGAImage &image, TGAColor color) {
    Vec2f bboxmin( std::numeric_limits<float>::max(),  std::numeric_limits<float>::max());
    Vec2f bboxmax(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
    Vec2f clamp(image.get_width()-1, image.get_height()-1);
    for (int i=0; i<3; i++) {
        for (int j=0; j<2; j++) {
            bboxmin[j] = std::max(0.f,      std::min(bboxmin[j], pts[i][j]));
            bboxmax[j] = std::min(clamp[j], std::max(bboxmax[j], pts[i][j]));
        }
    }
    Vec3f P;
    for (P.x=bboxmin.x; P.x<=bboxmax.x; P.x++) {
        for (P.y=bboxmin.y; P.y<=bboxmax.y; P.y++) {
            Vec3f bc_screen  = barycentric(pts[0], pts[1], pts[2], P);
            if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) continue;
            P.z = 0;
            for (int i=0; i<3; i++) P.z += pts[i][2]*bc_screen[i];
            if (zbuffer[int(P.x+P.y*width)]<P.z) {
                zbuffer[int(P.x+P.y*width)] = P.z;
                image.set(P.x, P.y, color);
            }
        }
    }
}

我们在上一节课中做了很小的改动,将隐藏的部分丢弃,真的太好了!这是渲染结果:
图片

源码在这里 here.

好的,我们对z进行了插值,我们还能做什么?

纹理!这是我们的作业。

翻译作者内容:我已经实现这个效果了,代码在这里可以找到:https://github.com/douysu/tinyrendererYD/tree/master/03_00_Texture

在.obj文件中有一行是以"vt u v”开始的,这是纹理坐标数组。"f x/x/x x/x/x x/x/x"中间的x是这个三角形的纹理坐标,将其插入三角形内,乘以纹理图像的宽度和高度,你会得到你需要放到渲染器中的颜色。

漫反射纹理可以在这里找到。 here.

我期望你能得到下面的结果:

图片

计算机图形学实体消隐:Z-Buffer算法原理与软件渲染实现详解 在计算机图形学中,隐藏面消除是生成真实感三维图形的关键技术,它确保在二维屏幕上只显示观察者可见的表面。其核心原理是通过深度测试判断物体间的遮挡关系,Z-Buffer算法因其简单高效成为行业标准。该技术通过维护深度缓冲区,逐像素比较物体距离,解决了复杂场景的渲染顺序问题,极大提升了图形渲染的准确性与效率。在游戏开发、三维建模和虚拟现实等应用场景中,实体消隐是构建沉浸式视觉体验的基础。本文结合头歌实践平台项目,深入解析Z-Buffer算法的实现细节,涵盖深度测试、透视校正插值等关键环节,并探讨Z-Fightin 阅读详情

相关推荐

【OpenGL】透视和ZBuffer

除了Graphic Pipeline外,ZBuffer是我最想介绍的部分了,因为有一些很酷实现都依赖于此(比如Shadow、Field Of View、SSOA)。先提出问题吧:是什么环节下的产物,有什么特性,可以用在哪些地方。为了具有说服力,我们以公式推导的形式给出结论。公式推导利用了中学的知识“相似三角形法则”,虽然有些繁琐但并不复杂。另外,文章的组织流程大致是沿着Graphic Pipeline的顺序进行的,其中剖析了一个正方形面片的显示过程。这个过程大致为:点->三角形->光栅化->透视矩阵->属性

stalendp的专栏 1万+

tinyrenderer-zBuffer隐藏面剔除

tinyrenderer渲染器实现 学习笔记

Mhypnos的博客 408

【信息科学与工程学】【数据科学】数据科学领域 第四篇 数据处理05 图形理论与图数据

本表格系统性地整理了分子运动图形图像模拟中涉及的全部核心数学方程分子力学基础:包括力场能量项、键合与非键相互作用的详细表达式分子运动方程:刚体运动、柔性分子运动、约束动力学算法溶剂效应:隐式与显式溶剂模型,连续介质方法量子化学方法:薛定谔方程、分子轨道理论、密度泛函理论分子动力学:积分算法、增强采样技术、自由能计算方法分子对接:打分函数、搜索算法、结合模式分析光谱模拟:红外、拉曼、圆二色性、NMR等光谱的计算方法反应动力学:过渡态理论、势能面、非绝热过程模拟分子可视化。

weixin_49199313的博客 1332

生动形象的解释下碰撞的分类(弹性碰撞、非弹性碰撞)以及他们在射击游戏中的实际应用

类型动量守恒动能守恒典型表现射击游戏应用弹性碰撞是是物体弹开子弹反弹、碎片反弹非弹性碰撞是否物体粘合/嵌入子弹击中敌人、爆炸冲击。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 2111

Z-Buffer消隐算法的Matlab实现

用Matlab实现的Z-Buffer算法,其中读取了一个wrl文件和obj文件

【AirSim+PX4】WSL2上运行PX4连接Airsim步骤

在 Linux 命令行中输入以下命令以添加(或改变)环境变量。注意,此处的 IP 地址应为。4.启动 sln 文件的调试以进入 UE4 编辑器,并在 Linux 中启动 PX4。

m0_53538421的博客 1505

设计自己的软渲染器4-光栅化与Z-buffer

这一次我们将使用光栅化与Z-Buffer来填充我们之前所画的线框模型中的三角形面。  光栅化 我们模型经过投影变换后的仍然只是几个顶点的位置信息而已,如果我们画三角形,想要的不仅仅是三个顶点,还想要确定填充三角形所需要的所有像素的信息(连续信息描述变换为离散信息描述),总结来说,光栅化就是确定一个几何图元所覆盖的所有像素及其信息。 如下图所示: 接下来我们将实现基于线扫描的光栅化三

渣渣 2183

【自动驾驶】汽车智能驾驶计算芯片

本文介绍汽车智能驾驶计算芯片的软硬件要求、 可靠性和安全性要求、 性能要求,和相应的试验方法。除了用于汽车智能驾驶, 其他领域的计算芯片可参照执行。

Mr.Cssust的博客 2631

系列教程4: 光栅化和Z-Buffer

系列教程4: 光栅化和Z-Buffer   我们跳过教程3,因为教程3主要是教我们加载Blender导出的模型网格。我们暂时不需要去做这些工作。依然以cube为例足以。   到目前为止,我们的渲染函数可以进行简单的线框渲染。我们现在开始看看如何用光栅化算法来填充三角形。然后,我们将看到如何用Z-buffer来避免将模型背面的面绘制到前面。     光栅化 有太多不同类型的光栅化算法

1459

模型优化系列——模型剪枝/量化概述

模型优化目的:通过减少模型大小,提高推理速度等使模型成功部署在各个硬件中并实时有效运作。

qq_61210574的博客 1293

NVIDIA Jetson AGX Xavier设置风扇转速、工作模式及性能监控

Xavier的风扇在开机之后是默认关闭的,每次重启都需要手动打开风扇,后续可能考虑修改bashrc文件进行自动开启 这个是显示当前风扇的转速,默认应该是0,表示关闭,最大是250 cat /sys/devices/pwm-fan/target_pwm 可以通过修改该文件的值来修改风扇转速 vim /sys/devices/pwm-fan/target_pwm 我一般使用的是100这个值 作者:ryan_ren 链接:https://www.jianshu.com/p/d3c2041f7f7d 来源:简书 著

weixin_43877080的博客 1万+

[从零构建光栅渲染器] 总结

相应文章目录: [从零构建光栅渲染器] 0.引言 [从零构建光栅渲染器] 1.Bresenham 画线算法 [从零构建光栅渲染器] 2.三角形栅格化和背面剪裁 [从零构建光栅渲染器] 3.隐藏面剃除 z-buffer(深度缓冲) [从零构建光栅渲染器] 4.透视投影 [从零构建光栅渲染器] 5.移动摄像机 [从零构建光栅渲染器] 6. 顶点和片元着色器的工作原理 实现效果 说的话: 呼~ 光栅渲染器的内容基本结尾了。在学习整理过程中,查阅不少的资料和数学公式,其中对我帮助最大的就是《计算机图形学》、Lea

尹豆的博客 642

使用R语言实现循环神经网络RNN进行文本生成

首先,让我们回顾一下循环神经网络(Recurrent Neural Networks,RNN)的基本概念。RNN是一种深度学习技术,特别适合处理序列数据,如时间序列和文本数据。RNN的关键特性是它的隐藏状态,这个状态可以捕捉到当前输入之前的信息。这使得RNN能够利用前面的信息来影响后续的输出,从而对序列数据进行有效的处理。

斌擎科技 724

Kronos K线股票预测指南:从数据到预测曲线的最短路径

想提前知道一只股票的 K 线接下来几十根会走出什么形状?自己搭时序预测模型确实麻烦——预处理、特征、选型、训练全要动手,结果还未必好看。Kronos K线预测就是为此跳过这些环节:这个开源的金融市场基础模型在超过 45 个全球交易所的 K 线数据上预训练过,你只需给它一段历史蜡烛图,它直接生成未来的价格与成交量预测曲线。这篇文章从环境安装讲到用自己的数据微调,全部跑通路径如下。 ## Krono

gitblog_01174的博客 341

置顶必读(1) | 2026最新首发,全网最全 Spring Boot 学习宝典(附思维导图)

从0到1以知识点+实例+项目的教学模式对SpringBoot框架由浅入深进行学习,快来跟bug菌一起学习吧。

**My Coding Family** 16万+

Vue + ECharts:从零构建智慧农业数据监控大屏(附完整源码)」 「超实用!一步步实现智慧农业大屏,完整代码+讲解」 「前端新手友好:Vue + ECharts 打造炫酷农业数据大屏」 「

Vue + ECharts:从零构建智慧农业数据监控大屏(附完整源码)」 「超实用!一步步实现智慧农业大屏,完整代码+讲解」 「前端新手友好:Vue + ECharts 打造炫酷农业数据大屏」 「前端可视化项目实战:智慧农业监控大屏开发详解」 「农作物可视化监控:用 Vue 和 ECharts 构建大屏应用」 「从零开始开发一个智慧农业数据大屏(代码+图表展示)」 「前端新手也能实现!智慧农业监控大屏开发教程」 「用 Vue 和 ECharts 搭建动态数据大屏:详细图文教程」 「学习前端大屏开发:

sky10100100的博客 1533

深度缓冲

http://blog.csdn.net/sck5711/article/details/4056957   1. 什么是深度缓冲   2. 使用深度缓冲 使用深度缓冲     Direct3D立即模式教程中提供了有关使用深度缓冲的更多的内容。             一个深度缓冲,统称被称为 z-buffer或w-

any 3003

在 TASKPROCESSING 上为 ABAP OData 服务添加多个系统别名:从原理到落地的完整实践

本文面向SAP Gateway与ABAP开发者,详细讲解如何在TASKPROCESSING OData服务上添加系统别名。首先介绍了系统别名的概念与设计初衷,阐述了添加别名的业务场景需求,包括多系统接入、用户分流和灰度发布等。随后提供了标准配置步骤,从SPRO路径到具体操作说明,并分享了三个实际案例说明如何实现跨区域统一待办、按用户角色分流和灰度发布。最后给出了验证方法和常见问题解决方案,帮助开发者掌握这项基础但关键的配置技能,实现服务路由的可控管理。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 136

Rust-Sloth项目架构解析:从几何处理到终端光栅化的完整指南 [特殊字符]

Rust-Sloth是一个用Rust编写的独特3D软件光栅化渲染器,专为命令行终端设计!这个开源项目实现了从几何数据处理到终端光栅化渲染的完整3D渲染流程,让你在终端中就能欣赏到令人惊叹的3D模型动画效果。无论是初学者还是3D图形爱好者,都能通过这个项目深入了解软件光栅化的核心技术原理。 [![Rust-Sloth 3D渲染示例](https://raw.gitcode.com/gh_mirro

gitblog_01040的博客 211

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

技术突破:用Rust实现终端ASCII艺术3D渲染引擎

在探索Rust语言系统编程和3D图形学结合的创新实践中,Rust-Sloth项目展示了一种独特的技术实现:基于纯软件的3D光栅化渲染器,专为命令行终端设计。这个开源项目通过ASCII字符艺术在终端中实现完整的3D模型渲染,为开发者提供了一个理解计算机图形学基础原理的绝佳实践平台。 ## 技术挑战:在终端中实现3D渲染的架构难题 传统3D渲染依赖GPU硬件加速和复杂的图形API,而Rust-Sl

gitblog_00121的博客 1090

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

three.js学习(5):Renderer

Renderer渲染器的工作是用来将物体绘制到屏幕上。 1.WebGLRenderer (1)构造函数: WebGLRenderer(parameters:object) –parameters–(可选参数)定义渲染器的行为。 –canvas–提供一个渲染器绘制输出的canvas和DOMElement属性对应。如果没有传这个参数,会创建一个新canvas。 –context – 可用于将渲染器附加...

无聊de热水的博客 6979

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Opengl-面剔除(一种优化方式)

前言 我们已经可以正常的渲染一个物体也好加载一个模型也好,比如立方体,可以正常的渲染,然后我们还可以挪动摄像机来观察每一个面,但是,有一个问题想一想。我们挪动相机进入了立方体里面就会发现里面也渲染了。这。。问题出来了。可能很多东西我们都不希望或者根本没想着把里面也渲染的那么好。。我们看不到还浪费这个资源去计算渲染干嘛? 面剔除 OpenGL能够检查所有面向(Front Fac...

alex1992azh的博客 705

learnopengl学习笔记——面剔除(face culling)

尝试在脑子中想象一个3D立方体,数数你从任意方向最多能同时看到几个面。如果你的想象力不是过于丰富了,你应该能得出最大的面数是3。你可以从任意位置和任意方向看向这个球体,但你永远不能看到3个以上的面。所以我们为什么要浪费时间绘制我们不能看见的那3个面呢?如果我们能够以某种方式丢弃这几个看不见的面,我们能省下超过50%的片段着色器执行数! 我们如何知道一个物体的某一个面不能从观察者视角看到呢? 如果我们想象任何一个闭合形状,它的每一个面都有两侧,每一侧要么面向用户,要么背对用户。如果我们能够只绘制面向观察者的面

kiss_the_rian的博客 835

【3D数学】欧拉角

什么是欧拉角?(借鉴于LearnOpenGL) 欧拉角使可以表示三维空间中任何旋转的三个值,一共有三种欧拉角,俯仰角,偏航角,和滚转角。欧拉角经常应用于3D场景,可以表示摄像机的姿态。下面的图片展示了它们的含义: 俯仰角(仰视角):绕X轴旋转的角,如图所示经常代表飞机的仰角或者俯角,如果以人的头部为例,代表仰视或者俯视。 偏航角(左右角):绕Y旋转的角,如图所示经常代表飞机的左右旋转角,

尹豆的博客 1万+
上一篇: [从零构建光栅渲染器] 2.三角形栅格化和背面剪裁
下一篇: [从零构建光栅渲染器] 4.透视投影
尹豆(douysu)
博客等级 码龄9年 151粉丝 41原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值