当前位置: 首页 > backend >正文

网格纹理采样算法

我有一个二维纹理数组,需要将它渲染到屏幕上,屏幕上的每个像素来自于数组中不同下标的纹理的对应位置的像素。举个例子,纹理坐标 (0.2,0.3)(0.2, 0.3)(0.2,0.3) 处的像素,它对应的纹理数组的下标是 333,那么就去第333幅纹理里采样 (0.2,0.3)(0.2,0.3)(0.2,0.3)这个纹理坐标的像素。

为了保存屏幕上每个像素(准确来说是子像素)来自纹理数组的哪张纹理,我们将每个像素对应的纹理数组下标也保存到一张纹理里面,暂且叫它索引纹理吧,这样我们只需要对索引纹理进行采样,就能得到每个子像素对应的纹理数组下标了。

虽然 Unity Shader 支持纹理数组(怎么用最后再说),但是我的纹理数组是从相机渲染得到的,我尝试了很多办法,都无法从相机直接渲染到纹理数组,总是只能渲染到纹理数组的第一张纹理上,不知道是真的不行还是我没找到方法。所以我现在只能将纹理数组都渲染到一张大纹理上,相当于把一位数组变成了二维数组。

现在有了网格纹理,也有了索引纹理,只需要在 shader 的片元着色器中完成采样就可以了。思路也很简单,先确定当前像素来自第几幅图,然后将纹理坐标重定位到该子图内,最后用重定位的纹理坐标对网格纹理采样就可以了。首先是纹理坐标重定位,这个过程其实很简单,假设网格纹理有 RRRCCC 列。

  • 首先计算纹理坐标在子图内的坐标。假设网格纹理的大小是 W×HW\times HW×H,则每个子图的大小只有 WC×HR\frac{W}{C}\times\frac{H}{R}CW×RH,相当于 宽高分别缩小了 CCCRRR 倍,因此将纹理坐标 uvuvuv 也分别缩小 CCCRRR 倍就是纹理坐标在子图中的位置。
  • 然后要计算子图在网格纹理中的偏移量,再加上第一步得到的经过缩放的纹理坐标,就是网格纹理的采样坐标。由于纹理坐标是归一化到 0→10\rightarrow 101 的,所以这一步的关键是计算子图在宫格纹理里是第几行,第几列。假设要采样第 iiijjj 列的子图,那么纹理坐标偏移就是 (jC,iR)(\frac{j}{C},\frac{i}{R})(Cj,Ri)

所以现在的问题是计算子图在第几行第几列。我们将纹理数组的下标存到了索引纹理中,但是 shader 中的图像像素也是归一化的,因此在生成索引纹理时,我们就已经对下标进行了归一化,也就是索引纹理里面存放的其实是 i数组长度\frac{i}{数组长度}数组长度i。所以我们用索引纹理的采样结果乘以数组长度(R×CR\times CR×C)就能得到真正的下标了,接下来就是一维数组下标转二维数组下标的问题了,这是个很简单的问题。

float2 redirectUV(float3 uv)
{int count = R * C;int index = floor(uv.z * count)int x = index % C;int y = index / C;return float2((x+uv.x)/C, (y+uv.y)/R)
}float3 rgb = tex2D(_IndexTex, uv)
float2 uv_r = redirectUV(float3(uv, rgb.r));
float2 uv_g = redirectUV(float3(uv, rgb.g));
float2 uv_b = redirectUV(float3(uv, rgb.b));
// sample grid texture

上面我们为了计算子图的横纵坐标,需要先计算子图总数,然后得到子图下标,再转化成行列坐标。那么有没有办法不计算子图总数,也不计算下标,直接得到行列坐标呢?

有的,兄弟!有的!

我们可以换个思路想这个问题,假设有一根长度为 111 的线段,我们先将他分成 RRR 大段,然后再将每一大段分成 CCC 小段,那么其实我们要求解的问题变成了这个归一化的索引下标落在哪一大段,哪一小段。

我们将归一化的索引乘以 RRR,它的整数部分就是行数;而它的小数部分乘以 CCC 就是列数,在 shader 里我们可以使用 modf 函数来拆分浮点数。

float2 redirectUV(float3 uv)
{float2 ov;ov.x = floor(modf(uv.z * R, ov.y) * C);return (ov + uv.xy) / float2(C, R);	
}float3 rgb = tex2D(_IndexTex, uv)
float2 uv_r = redirectUV(float3(uv, rgb.r));
float2 uv_g = redirectUV(float3(uv, rgb.g));
float2 uv_b = redirectUV(float3(uv, rgb.b));
// sample grid texture

这样只用一行代码就能把行列坐标同时计算出来了,这个公式可以从前面的方法中推导出来,感兴趣的朋友可以试一试。

unity shader 中使用纹理数组

最后说一说怎么在 unity shader 中使用纹理数组。

首先声明纹理的时候要使用 2DArray 类型。

Properties
{_MainTex ("Tex", 2DArray) = "" {}
}

然后是在 Pass 里面声明采样器,普通2D纹理通常是:

sampler2D _MainTex;
float4 _MainTex_ST;

而纹理数组使用的是:

UNITY_DECLARE_TEX2DARRAY(_MyArr);

最后是纹理采样,需要使用下面的函数:

UNITY_SAMPLE_TEX2DARRAY(_MyArr, i.uv);

注意这里的 uvfloat3z 分量就是纹理数组的下标,这里不是归一化下标,是真实下标。

在 C# 中创建 RenderTexture 纹理数组需要在纹理真正创建前设置下面的属性:

renderTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex2DArray;
renderTexture.volumeDepth = arrayLength;
http://www.xdnf.cn/news/19152.html

相关文章:

  • SEO关键词布局总踩坑?用腾讯云AI工具从核心词到长尾词一键生成(附青少年英语培训实操案例)
  • 文件,目录,字符串使用
  • 金仓数据库迁移评估系统(KDMS)V4正式上线,助力企业高效完成数据库国产化替代
  • Ubuntu 中通过 SSH 克隆 Windows 上的 Git 仓库
  • STFT和梅尔频谱图
  • Notepad++常用设置
  • Session
  • HunyuanVideo-Foley - AI视频配音 根据视频和文本描述生成逼真的电影级音频 支持50系显卡 一键整合包下载
  • uniapp解析富文本,视频无法显示问题
  • 网络初识及网络编程
  • WPF中的ref和out
  • Shell 秘典(卷三)——循环运转玄章 与 case 分脉断诀精要
  • 访问Nginx 前端页面,接口报502 Bad Gateway
  • 软考 系统架构设计师系列知识点之杂项集萃(137)
  • 如何在 Jenkins Docker 容器中切换到 root 用户并解决权限问题
  • 深入理解 RabbitMQ:从底层原理到实战落地的全维度指南
  • C++之stack类的代码及其逻辑详解
  • 基于DCT-FFT的图像去噪滤波算法
  • GD32入门到实战22--红外NEC通信协议
  • 超越传统SEO:用生成引擎优化(GEO)驱动下一轮增长
  • Tomcat 企业级运维实战系列(三):Tomcat 配置解析与集群化部署
  • UI前端大数据可视化实战策略:如何设计符合用户认知的数据可视化界面?
  • JUC并发编程10 - 内存(02) - volatile
  • vscode terminal远程连接linux服务器GUI图形界面
  • 鸿蒙NEXT布局全解析:从线性到瀑布流,构建自适应UI界面
  • 深入理解计算机端口:为什么通信需要端口?
  • 【读论文】质心重分配显微镜实现活样本超分辨成像
  • Qt中的QSS介绍
  • Time-MOE添加MLP分类头进行分类任务
  • 用户自定义字段(Custom Fields)设计方案,兼顾多语言、分组、校验、权限、查询性能、审计与多租户