大量动画模型渲染的性能优化
最近在对支持的项目组进行性能优化工作,其中遇到的大量动画角色同屏渲染的问题,经过一些调研和实践,正好就大量动画渲染性能优化技术进行一些讨论。 骨骼模型由骨骼数据加骨骼动画进行驱动,每一帧动画系统会计算出当前帧的骨骼的变换矩阵,然后通过蒙皮的方式来计算每一个顶点的实时位置,最后渲染系统会根据蒙皮之后的顶点数据进行渲染。具体蒙皮的计算方法,不同的蒙皮技术会有不同的时机和实现方式。 CPU SkinCPU蒙皮指在CPU端先计算好每个顶点的蒙皮位置,然后再将顶点数据发送到GPU端进行渲染,GPU端的顶点buffer里保存的是蒙皮后的顶点数据。我们知道CPU是擅长逻辑控制而不善于大量运算的,因此CPU蒙皮会严重的影响CPU性能,特别是场景中存在大量动画模型的时候,CPU基本会被动画更新,蒙皮计算和数据传输所占满,甚至会无法运行。 GPU Skin骨骼蒙皮会逐顶点进行计算,在顶点数量比较多的时候,总的运算量是非常大的,但是我们注意到,每个顶点的计算是相互独立的,也就是说他们是并行的。这种大量的并行计算,那天然就适合在GPU端进行。这种在GPU端进行蒙皮计算的就叫做GPU蒙皮。 VS Skin...
深入理解GPU(三)性能优化
前两章主要介绍了GPU的渲染管线和硬件架构,本文针对GPU的性能优化做一些简单的讨论。 DrawCall对于性能的影响GPU是工作在内核空间的,应用层跟GPU打交道是通过图形API和GPU的驱动来完成的,驱动的调用会有一个用户空间到内核空间的转换。以DX为例,用户程序在CPU端提交一个DrawCall, 数据的流程是:APP > DX runtime > User mode driver > Dxgknl > Kernel mode driver > GPU,经过这一连串的调用,才能到达GPU,所有GPU之前的这些流程都是在GPU端执行的。所以DrawCall数量增加,增加的往往是CPU端的时间开销。这也就是为什么最新的现代图形API,如DX12,Vulkan等,都会将驱动层做薄的原因。而主机平台因为有特殊的驱动优化,因此CPU和GPU的交互性能开销是很低的,游戏性能也就更强。 DrawCall命令的开销并不是单纯的绘制命令本身,而在于DrawCall绑定的数据(Shader,Buffer,Texture)和渲染状态(Render State)设置的...
深入理解GPU(二)硬件架构
上篇文章介绍了GPU的渲染管线,这是从渲染的流程层面介绍了GPU渲染的过程,本文的内容深入到GPU的硬件架构,从硬件层面介绍GPU的组成和工作原理。参考文献有多篇非常深入详细的文章,值得学习。 什么是GPU?GPU的全称是Graphics Processing Unit,图形处理单元。最初的GPU是专门用于绘制图形图像和处理图元数据的特定芯片。 如上图所示,展示了GPU和CPU的硬件差异: CPU的核心数量少,每个核心都有控制单元,内存设计上是大缓存,低延迟。CPU擅长分支控制和逻辑运算,而不适合海量的数据计算。 GPU则计算单元非常多,多个计算单元共享一个控制单元。内存设计上是追求高带宽,可以接受高延迟。GPU适合海量的数据并发计算的场景。 桌面GPU 物理架构GPU的微观结构因不同厂商,不同架构都会有所差异,但是核心的部件,概念以及运行机制大同小异,桌面级的GPU产商有NVIDIA,AMD,移动端的GPU包括PowerVR,Mali和Andreno。以NVIDIA的桌面级GPU为例,历代的GPU包括Tesla,Fermi, Maxwell,Kepler和Turing架构。...
纹理压缩技术详解
纹理压缩技术游戏中重要且常用的技术,最近在做纹理压缩相关的工作,正好深入的学习下纹理压缩技术的底层原理。关于纹理压缩技术有一篇非常全面的综述论文《TEXTURE COMPRESSION TECHNIQUES》, 对应有一篇中文的翻译文章。本文是上述文章的学习总结。 纹理压缩背景游戏中使用纹理是把二维图像映射到三维表面,图像中单个像素叫做Texel。游戏中使用的贴图不仅可以存储颜色,还可以存储法线,高度等信息,贴图需要占用大量的内存,游戏中超过一半的内存被纹理占用,而且纹理大小也会对带宽造成影响,直接影响耗电,因此需要使用纹理压缩,对内存,带宽和耗电同时进行优化,尤其在移动端设备上尤为重要。 通常情况下纹理是一张二维的图像,但是传统的压缩算法(RLE,LZW等)却和流行的贴图压缩格式(JEPG,PNG等)并不适合纹理压缩,主要原因是贴图需要随机访问Texel,即只需要访问用到的纹理部分。而传统的图片压缩是需要针对整个纹理进行解压的。 因此大多数的压缩方案都会将原始的图片分割为固定大小的块,称做Tile,然后针对每个Tile进行独立的压缩。在评估一个纹理压缩方案的时候主要考虑以下几...
深入理解GPU(一) 渲染管线
使用一个东西,却不明白它的道理,并不高明. 做图形学最重要的就是跟GPU打交道,利用GPU来实现各种效果。但是之前一直只停留在比较上层的使用上,对于GPU的底层和硬件架构知之甚少。借用侯捷老师的一句话“使用一个东西,却不明白它的道理,并不高明”。于是便花了些时间深入学习了GPU的相关知识,做些记录。 腾讯技术工程的官方号上有一篇详细介绍GPU的文章:《GPU 渲染管线和硬件架构浅谈》,总结的非常全面,反复看了好多遍,也是本篇文章的主要参考资料。知乎平台上讲解NVIDIA GPU架构的系列文章非常详细的介绍了各代架构的GPU及其硬件架构,非常值得一读。 RTR4开篇第一二章的内容就是介绍的GPU硬件架构和渲染管线,这里我们也按照这个顺序: 第一篇将主要介绍图形学中的GPU渲染管线,包括桌面端和移动端; 第二章主要介绍GPU的硬件架构; 第三章主要介绍GPU编程和优化方法相关。 GPU渲染管线所谓的GPU渲染管线,就是有一堆的模型数据(点,线,三角形等),经过GPU端的一系列的流水线处理,最终得到屏幕上的二维图像的流程。跟工业生产的流水线一样,GPU的管线各个部分也是并行处理...
游戏性能优化
最近在Youtube上看到一个很不错的讲游戏性能优化的系列视频,花了几个小时学习了一下。作者从游戏性能优化的准则,优化的方向做了较为详细的介绍,以及介绍了性能分析工具的使用,并结合Unity和Unreal的示例场景来实际分析和优化游戏性能。由于视频时长限制,很多方面讲解的不是很深入,这里对视频里印象较深的部分做一些总结,同时也作为性能分析的一个学习资源笔记,后续有时间可以针对各个方面进行更深一步的探索。 游戏优化准则谁来优化? 游戏开发实际是策划,美术,程序等各个不同职能的人来共同完成的,那么对于游戏的优化而言,作者认为不是由某个人来完成,而是每个人都需要了解和参与。美术需要知道哪些能做,哪些不能做,哪些效果需要,哪些效果没那么重要。程序需要知道哪些可以做,哪些做不了,哪些可以做的更好。 游戏性能优化是一个非常大的课题,实际过程中不同的项目,由于美术资源,玩法,画面效果,以及设备平台的不同,甚至是引擎的不同,需要面对的优化问题可能都是不一样的,因此没有所谓的统一的优化方法,而是需要具体问题具体对待。 下面这句话是贯穿整个系列视频的指导准则 **先分析,再优化** 这句话听起来也...
学习资源汇总
日拱一卒,功不唐捐 学习资源汇总,持续更新中… 技术C++图形学 Physically Based Rendering:计算机图形学领域(尤其是光线追踪与基于物理的渲染方向)的经典教科书与实践指南,被誉为该领域的 “圣经” 之一,适合有一定的图形学开发经验的技术人员。(目前该书的电子版已经全部免费) Real Time Rendering 4th: 这是一本图形学的宝典,与其说是书,更像是图形学技术的技术目录。涵盖了现代渲染技术、图形编程原理和实时可视化技术等内容,从基本的数学基础到前沿游戏使用的高级技术都有涉及。目前还有中文翻译的资源。 博客 Arseny Kapoulkine: 前Roblox的图形学技术研究员,是pugixml,meshoptimizer,volk,等开源库的作者,会在个人博客和Youtube频道分享编程相关知识。 Jendrik Illner:现任Santa Monica Studio的高级渲染程序员,曾再EA担任渲染工程师,与寒霜引擎团队合作下一代渲染后端。专注于引擎设计、优化以及基于 GPU 的流水线技术。他整理的Weekly Newslet...
C++内存管理之std::alloc源码分析
书接上回。侯捷老师的教程里对G2.9的std::alloc的运行模式和源码都进行了非常精彩的讲解。虽然这个allocator的历史比较久了,但是其中的设计思想对于后续的内存分配还是有比较重要的参考意义。这里就老师课程中的内容做一些总结记录。 std::alloc的运作模式上图表示了std::alloc的运作模式示意图,std::alloc使用了一条16个元素大小的数组来管理链表,不同索引的元素管理不同大小的链表。其中#0链表记录的是最小的8个bytes的链表,而#15链表记录是最大的128bytes的链表,相邻的链表元素大小相差8bytes。这里需要注意的是:该链表仅维护分配128Bytes以内的内存块,大于128Bytes的则将直接使用malloc直接分配空间。 嵌入式指针这里每个细分的区块使用的嵌入式指针来节省内存的使用,每个被管理的自由区块头部使用嵌入式指针指向其他的链接的区块,而一旦区块被分配给用户,那么这段内存就可以直接被使用,因此不会造成内存的浪费。这种嵌入式指针的思想在很多的分配器中都有使用。 以下直接通过截取PPT内容来看下std::alloc的内存分配 1、在申...
C++内存管理
最近在学习侯捷老师的《C++内存管理》课程,对C++底层的内存管理机制有了更深的一些理解,这里记录一下主要的学习内容和相关问题。 C++程序里使用memory的多种方式如上图所示,C++有多种直接或者间接控制内存的方式,相关的总结如下:这里主要区分的是 new 和 operator new new是C++的表达式(expressions),也就是C++在编译的时候会将表达式展开为多行代码语句,因为是表达式,所以new不可以重载。 operator new是C++的函数,所以该函数是可以被重载的。 下图是C++的new expression的内部逻辑可以看到针对new expression,编译器实际在背后做了以下几件事: 首先通过operator new申请一块内存,内存大小根据complex类来定。 然后将申请的内存指针强制转换为目标类型指针,这里的是Complex*类型。 最后调用目标类型complex的构造函数构造对象。这里注意的是:通过pc->Complex::Complext(1,2)这种方式调用构造函数,只有编译器可以做,用户是不能这么做的。 与new表...
Hello Haony
欢迎来到我的自留地。 大学的时候尝试写过一些博客,那时候是在博客园写一些技术博客,把自己鼓捣折腾一些小玩意的教程分享在上面,那些分享也确实帮助到了一些人。但是后面忙其他事情的时候慢慢的博客也断更了。 现在工作已经四年多快五年了,对于工作的认知也从当初的懵懵懂懂到现在的有了些许体会。很多时候发现写点东西记录下来,比在脑海中去回想更加有实在的意义。于是我便搞了一个自己的博客网站,不光写写技术博客,更多的是记录自己的学习,思考和体验的过程。 这是本博客网站的第一篇,记录一下小小的起点。 -2025年9月16日晚

