每个人都有自己的知识体系。
Toggle navigation
Home
随笔
C#/.Net
树莓派 / Raspberry
皓月汉化组
Beego
Golang
OxideMod
apache
haproxy
windows
Java
Objective-C
日语/罗马音歌词/日语常识
MongoDB
python
电学
公告
Minecraft服务器-公告
NanoPi
C4D (CINEMA 4D)
生活
推流/m3u8/rtmp/rtsp
Unity3d
ffmpeg
数据结构
区块链
tarui
UnityForPSVita
About Me
Archives
Tags
Unity 中Texture2D高性能像素填充 探究
2024-07-31 16:14:15
86
0
0
akiragatsu
Unity在很多需求下,不得不进行颜色的像素级填充, 诸如:实时贴花功能,绘图功能开发,乃至非Native的情况下视频播放(比如视频是CPU计算出来的,而不是文件流或直播流,提交颜色),各种游戏模拟器,等画面显示; 亦或者,必须要通过CPU逻辑运算,得到一个图形,并展示到UI中,或某个贴图上时, 采用类似Rendertexture的方式,都就需要处理Textture2d的填充。 尤其在高频填充的情况下(指60帧或者更高刷新率绘制),我们就需要**考虑效率**了。 以下是我做Unity下街机模拟器画面显示开发的心得 ![title](/api/file/getImage?fileId=66a9f872bbb322d4dc00305d) * 最终我还要上PSVita,使用unity(113M的极限内存可用,还要预留性能给我的网络联机网络库,害只能用77M的模式,主频非超频也只有333Mhz,所以能省则省了) 省流版,总结,几种: **Plan1.** **使用SetPixelData< T > **(T[] data, int mipLevel, int sourceDataStartIndex = 0) **Plan2.** **GetNativeTexturePtr纹理指针**,对底层指针,进行绘制。如果你对DX11/DX9的绘制指针比较了解 ,可以使用Texture2d的GetNativeTexturePtr 获取指针,在C++扩展下进行操作。(跨平台不友好) **Plan3.** **低版本Unity使用 LoadRawTextureData< T >**(NativeArray<T> data),LoadRawTextureData(byte[] data) **Plan4.** **低版本Unity使用 LoadRawTextureData 的指针用法 (推荐)** LoadRawTextureData(IntPtr data, int size) **Plan5.** **交给shader** ## 好好好,咱们从低到高,说说各种用法的演进 #### **全局参数** ``` //这里是一个代码构建的Texture2D用于填充数据和提交 private Texture2D m_rawBufferWarper; //这里是UI上一个RawImage组件作为显示 private RawImage m_drawCanvas; ``` #### **逐像素填充(效率最低)** 这个是最常见的,低频率用一下,倒是也妹啥(乐),高频的话,废话,**别这样**,兄弟 ``` for(像素遍历...) { //低效率的设置单个颜色值 m_rawBufferWarper.SetPixel(x,y,color); //Apply提交 m_rawBufferWarper.Apply(); } ``` #### **Color颜色像素数组填充(效率稍好一点)** 那么,如果用整个数组直接填充到texture2d中,会好一些。 同样低频调用合适。 高频的话,我依旧**不建议**这样使用。 ``` //使用当先需要显示的结果颜色矩阵 Color[] Color[] color Arr = ...; m_rawBufferWarper.SetPixels(color); m_rawBufferWarper.Apply(); ``` #### **Color32颜色像素数组填充(同上,但是内存小一点,每个颜色是32位,依旧性能不太行)** ``` //使用当先需要显示的结果颜色矩阵 Color32[] ,但是color32相比之下效率高一丢丢 Color32[] color Arr = ...; //Color32需要用SetPixels32函数 m_rawBufferWarper.SetPixels32(color); m_rawBufferWarper.Apply(); ``` 以上方式,往往你需要把数据源转换成Color[]或Color32[]也会有开销 如果你数据源就是int[]或byte[]在使用过程中,不得转换。 btw,如果画帧数据源CallBack提交到主线程,要保证线程安全,不可避免的有拷贝,或者lock的开销。 So,咱们有没有更直接的办法呢? ## 正片开始 ## **Plan1.使用SetPixelData< T> : 直接填充指定颜色数组(开销小)** ``` Texture2D.SetPixelData<T>(T[] data, int mipLevel, int sourceDataStartIndex = 0) ``` **效率优良** 这种就可以直接把数据源进行传递,不用创建Color[],避免不必要的循环和转换,或者GC。 这里T可以直接指定你的数据源类型,保证Texture2D和原数据流对齐, 但是需要预先确定的事情比较关键: **1.数据源的通道的顺序.** 热知识:并不是所有数据源都是我们俗称的RGBA,有的是把A通道放在前面ARGB,还有RGB,比如MAME街机模拟器的显存中式BGRA32. 当然也还有更多组合。一定要确定。 否则通道错误,或色彩交叉。 这种时候,你就需要在创建texture2d时,指定好TextureFormat.RGBA32、BGRA32、ARGB32 等 **2.数据源颜色精度,定义数据类型** 在创建texture2d时指定好TextureFormat,这里有一个精度,比如RGBA32这种32后缀的就是32位的int颜色(十六进制颜色), 32位则是int[]序列,其中每一个int就是一个像素的颜色值, 32位也可以是byte[]序列,其中每4个为一组是一个像素颜色,(32个bit)。 不是32尾缀的TextureFormat,大部分都是float,这时候你可以定义类型为float[]序列,或者byte[]其中每sizeif(float)个byte 好,比如,我的正在开发MAME的Unity模拟器数据源是int[],然后通道顺序是B、G、R、A 且每个颜色值就是十六进制颜色的int数组int[] 那么 我们Texture2D 的TextureFormat.BGRA32 SetPixelData填充的泛型就指定为< T > ``` SetPixelData<int>(,,,) ``` **3.宽高对齐** 虽然这一点很简单,但是也要注意。 a.数组的大小 数组的大小 纹理Width x Height = 数据源数组大小 b.宽高对齐,即便数组大小对齐,宽高也是,否则画面错误。(比如 800*600,就不能是600*800) **示例:** ``` //创建texture m_rawBufferWarper = new Texture2D(mWidth, mHeight, TextureFormat.BGRA32, false); int mFrameData = ...;//这里来自于逐帧提交到主线程的每一帧颜色的副本 m_rawBufferWarper.SetPixelData<int>(mFrameData, 0); m_rawBufferWarper.Apply(); ``` ## **Plan2. 使用Texture2d->GetNativeTexturePtr绘制句柄** 首先我探索到了 Texture2d下GetNativeTexturePtr,他返回一个Intptr. 但这个并不是图像的句柄,而是和DX/VK 或别的绘制API直接挂钩的对象句柄。不熟悉建议不采用, 但是关于坑,提一嘴:texture2d对象,一定是渲染过一次之后,比如调用texture2d.Apply 或 Graphics.Blit之后,指针才取得到。 顺带使用指针直接调用Texture2d的创建重载指定指针,也很容易崩溃。 慎用~~ ## **Plan3. LoadRawTextureData< int > 快速加载** **低版本平替了属于是,** 低版本unity没有SetPixelData< T >,如果想达到等效的效率,你可以考虑使用LoadRawTextureData< int > 这个是动态把数据加载为RawTexture数据,放入texture2d。 有三种方式 ``` //直接传入数据 LoadRawTextureData(byte[] data) //JobSystem适用 LoadRawTextureData<T>(NativeArray<T> data) //(unsafe方法) //指定数据源指针,这个也推荐 LoadRawTextureData(IntPtr data, int size) ``` 在使用过程中,也要注意和SetPixelData一样 **数据源的通道的顺序,数据源颜色精度,定义数据类型 一定要对齐!** 这里先演示第二种NativeArray的方式,JobSystem适合,非JobSystem也可以用,我演示一下: LoadRawTextureData的NativeArray用法: ``` //确定这段时间区间的帧大小后,再初始化,Allocator.Persistent保证不同的作用域数据 NativeArray<int> nArr = new NativeArray<int>(mFrameData.Length,Allocator.Persistent); nArr.CopyFrom(mFrameData);//int[]拷贝到NativeArray<T> 比C#原生数组拷贝少20B左右的开销 m_rawBufferWarper.LoadRawTextureData<int>(nArr); m_rawBufferWarper.Apply(); ``` ## **Plan4. LoadRawTextureData(IntPtr data, int size) 的指针用法:** 然后关于 LoadRawTextureData(IntPtr data, int size)的方式也可以,和之前一样对齐就行。 ``` //在公共地方,比如初始化数组时候,取得他的指针 // 固定数组,防止垃圾回收器移动它 GCHandle handle = GCHandle.Alloc(mFrameData, GCHandleType.Pinned); // 获取数组的指针 IntPtr ptr = handle.AddrOfPinnedObject(); ... //循环使用过程中,直接用ptr进行load m_rawBufferWarper.LoadRawTextureData(ptr, mFrameData.Length * 4);//这里的长度是byte长度,不管你指针是int[] uint[] float[] ushort[] byte[] 这里是内存里的byte长度。我数据源mFrameData是int[] 那么这里乘以4 //进行一个提交 m_rawBufferWarper.Apply(); ... //卸载资源时 // 释放句柄 if (handle.IsAllocated) { handle.Free(); } ``` 总之,这个是低版本推荐比如2019及其以下的没有SetPixelData,推荐使用LoadRawTextureData ## **Plan5. 交给Shader** 这个就百花齐放了
Pre:
PSVIta_Unity目录
Next:
C4D 笔记 01 基本操做和快捷键
0
likes
86
Weibo
Wechat
Tencent Weibo
QQ Zone
RenRen
Submit
Sign in
to leave a comment.
No Leanote account?
Sign up now.
0
comments
More...
Table of content
No Leanote account? Sign up now.