大家好我是来自搜狐畅游引擎部TA组的小源小榞小圆,这次是分享关于三方向映射纹理的做法。

三方向映射在制作地形贴图,和一些需要在任意角度覆盖模型的材质会比较常用到。比如苔藓、污渍、锈迹、以及最常用到的地形材质。

使用世界坐标空间的XY轴的平面映射,可以看到比较陡峭的山崖部分有明显的贴图拉伸

使用世界坐标空间的XYZ轴的三方向映射,能比较好的解决陡峭地形变化导致的贴图过度拉伸问题

使用模型的纹理坐标来映射苔藓,会导致接缝甚至模型破裂

拉进距离看,因为使用了置换、但贴图又存在接缝所导致的模型裂隙

如果使用世界坐标空间的XY轴进行映射,垂直方向会有严重的拉伸

如果使用三方向映射可以比较好的解决这个问题

以前觉得三方向映射应该实现起来没什么难度,但实际实现的时候发现坑还不少。所以做一下记录和分享。

在正文开始前,在这里插播一条对同期进入公司的Crossous同学的感谢,靠谱的Crossous迅速地帮忙解决了一些我提出的unity问题!赞美Crossous!

Step 1 构建三方向遮罩:

这个是遮罩可视化的效果,根据法线将模型分成了三组

通过将顶点法线和 float3(0, 0, 1) 点积可以求出顶点法线在指向场景上方向量的投影(这里为UE为标准哈,unity自行转换一下)。求绝对值后减去一个系数,可以将投影长度较小的部分移动成为负值,这可以方便我们在后续将他们给裁掉。之后再乘一个系数是为了将遮罩为正的绝大部分缩放到大于1, 仅留下边缘部分0-1之间的数作为过渡。调整系数2控制遮罩边缘的硬度。系数1我尝试比较好的值是0.56左右,不过也可以根据边缘硬度调整。

然后你就可以得到这样一个遮罩。

我们把另外两个轴向的遮罩也做一下,加在一起除个三看看效果:

可以看到三个方向的遮罩有重叠的部分。而因为我们是使用顶点法线和轴向做点积求得的遮罩,所以其数值实际是从中心到边缘逐渐变小的。又因为三个轴向的采用了相同的系数,所以想要把相交部分平均分开,只需要用当前遮罩减去相邻的遮罩。如下:

上图是其中一块的遮罩,需要注意的是,在减去相邻遮罩的时候,需要将原本的遮罩的负值部裁切掉。对应材质里的 max(x , 0)。

我们求出两块遮罩(第三部分就是前两块的剩余),然后线性插值一点颜色看看:

嗯,乍看起来效果不错,但是可以看到绿色的部分占据了一些间隙,这可能是我们不希望出现的问题。修复这个小问题只需要加一个小小的系数:

这个系数也是随便给个合适的值就可以。或许你会发现,现在遮罩似乎出现了一些微妙的不平衡,但……这点大小的差别谁会在意呢?

好的现在我们有了三方向的遮罩,我们就可以用这三方向的遮罩来混合贴图了。贴图采样用世界空间坐标来作为UV:

一点点友善的提醒:Texture Sample节点的细节面板里,把Sampler Source修改为Shared Warp。我忘记这个限制是来自DX还是HLSL的了,一个Shader最多只能拥有16个Sampler,在做地形材质的时候,采样器数量非常容易超过限制。

好的,这里似乎就要结束了,但三方向映射还有一个比较令人头疼的点:法线。

如果我们直接像采样颜色或者粗糙度这些贴图一样去采样法线、然后用遮罩和线性插值混合,就会得到相当奇怪的光照。

这是因为,通常情况下,我们在游戏里给场景、角色、道具所使用的的法线都是切线空间法线。当我们直接使用模型的UV来采样法线贴图的时候,切线空间的法线会被“正确的”读取到相应的UV位置上去,这可以让引擎能正确的理解这张法线的颜色所表示的方向。如下图:

这是一张DX模式下的法线贴图,R通道越接近1,表示当前像素越向右侧偏转,越接近0,则越像左侧偏转。绿通道同理,接近1则向下,接近0则向上。注意,这里的上下左右的偏转,都是基于UV空间来说的。如果我们的法线贴图不再贴合UV空间(也就是目前我们所遇到的状况,我们使用了世界空间坐标、而非模型的UV来采样了贴图),就会让着色器产生困惑,这到底是是朝那里?

因为模型的UV绝大部分情况下都是不连续的,所以我们最后实际映射到模型上的法线,在UV坐标下看,很可能是这样的……

解决这个切线空间法线导致的问题目前我们尝试了两种方式:

第一种,直接使用矩阵,将三方向采样的切线空间法线转换到模型对应的切线空间:

为了解决这个问题,需要用一点点矩阵工具:我们既然知道,光照效果出现问题是因为切线空间不匹配,那我们把每个像素的法线向量,旋转到模型所对应的切线空间不就万事大吉。

下图可以看到,我们使用世界坐标来作为UV采样纹理时的UV情况。这里需要注意每个通道的顺序:排在前面的是U、后面的是V。V的正方向应该在U正方向的顺时针90度方向,如果V出现在了U的逆时针90度方向,则需要考虑调转轴向。

知道这个有啥用呢?知道这个,我们不就知道,我们在采样贴图的时候,所假定的切线方向了呀,还是世界空间的切线方向。同时,我们还能读取到模型顶点的世界坐标法线……

法线转换时候熟悉的味道出现了,还记得我们可以tangent、normal做叉乘,得到bitangent,然后可以用这仨向量构建一个从切线空间转到世界空间的矩阵吗?我们现在可以在搞一下:

上图是X轴方向投射的结果,但如果是X轴的负方向呢?从X轴的负方向看过去,UV做标记就变成了VU坐标,这时候我们只需要在把负方向投影的矩阵里的副切线翻转就可以了。还记得我们之前用世界空间法线和轴向做点积的时候吗?轴向的一侧结果是正值,另一侧则是负值,我们用它就可以了:

好的,那我们把三个轴向的矩阵都构建一下:

这里我们可以注意到,实际上从Z轴向来看的映射、和从Y轴的映射被合并在了一起计算,因为这俩轴向的映射的世界空间切线都是X轴方向,而模型顶点法线的取值固定,我们可以使用同一个矩阵来进行转换。减少一些运算量(考虑到三方向映射要将同一张图片采样3次,已经是比较大的开销了,能省点就省点吧)。

第二种,我们可以直接通过通道调整,将三方向映射的切线空间法线,转换到世界空间下,再分别和顶点法线进行混合,随后使用矩阵变换将世界空间法线转换到切线空间(如果不再进行其他法线计算的话,甚至可以直接使用世界空间法线进行光照计算,而略过到切线空间的矩阵转换)。

第二种做法的好处显而易见,首先比第一种少了数次矩阵变换,另外从切线空间到世界空间的矩阵很方便获取,不需要我们自己构建变换矩阵。以从Y轴方向为例,使用XZ轴作为UV来进行贴图采样的时候,切线空间的法线的R通道对应世界坐标的X方向,G通道为Z轴的反方向,而B通道垂直于XZ平面。

一个常用的法线混合计算方式是:

float3 normal = normalize(float3(baseNormal.xy + additionalNormal.xy , baseNormal.z * additionalNormal.z));

但是我们注意到,一般我们用到上边这个混合公式的时候,是B通道近似垂直于模型表面的时候,因此我们在混合模型顶点法线和Y轴方向投射的法线时,应当是:

float3 normalWorldSapce = normalize(float3(vertexnormal.x + yAxisProjectionNormal.x , vertexnormal.y * -yAxisProjectionNormal.y , vertexnormal.x + yAxisProjectionNormal.z));

在UE材质蓝图的实现里就是下边这个样子:

完成三个方向投射的法线贴图的转换、并将他们和顶点法线混合后,再使用我们做好的遮罩将他们进行混合,最后进行一次从世界空间到切线空间的转换即可。

然后我们就完成了三方向映射。

/*一些额外内容*/

鉴于国内Unity用户基数也相当的大,并且我司目前也主要使用Unity引擎,所以在写这篇文章的时候,部门老大也想在Unity里也实现一下。我这边在URP(Universal Render Pipeline)下实现一个三方向映射的Shader。(Unity版本:2021.3.2.f1c1)

为了比较方便的引用引擎内置PBR光照,我们直接拷贝一份Lit Shader过来进行修改,少写一点字。

先给shader改个名字,叫啥无所谓不要重名就成。然后下边翻翻看Lit Shader,略过茫茫多的ShaderProperties和宏定义,只看主干,Shader分为俩SubShader,其中第二个SubShader是在第一个subshader失效时启用的,所以我们主要关心第一个subshader。第一个subshader里有一堆Pass,其中最主要会直接影响画面渲染结果的就是ForwardLit和GBuffer,俩分别对应前向管线和延迟管线。管线选择可以在URP的配置文件中选择,这里篇幅(懒)原因就不展开细说,毕竟也不是我们这篇文章的主题,我们就使用前向管线来制作一版三方向映射的Shader。所以我们目前也只需要关注ForwardLit pass。

然后我们看ForwardLit pass的内容,在Lit shader这个文件里,ForwardLit只include两个文件,这俩文件包含了ForwardLit pass的大部分代码。

为了方便修改(改坏不愁)我们把LitForwardPass.hlsl文件单独拷贝一份并引用进我们的shader,LitInput.hlsl对我们的帮助不大,可以直接注释掉。

LitForwardPass里最主要有三个函数:InitializeStandardLitSurfaceData负责进行贴图采样,之后传递给InitializeInputData进行雾、投影等计算,再传递入UniversalFragmentPBR进行光照计算并输出给颜色。

我们只要自定 InitializeSurfaceData 这个部分就可以完成我们需要的效果。所以我们把这个函数注释掉,开始自己写这个部分。

我们还需要先知道SurfaceData这个结构体所包含的对象有哪些,这个结构体定义在SurfaceData.hlsl中。因为我们在Shader里移除了LitInput.hlsl的引用,而这个文件里引用了SufaceData.hlsl,所以我们还需要把这个结构放在CustomLitForwardPass.hlsl里或者引用一下。

接下来我们就可以开始整活了。

先简单写一下Properties需要的各个参数。

在.hlsl文件里申明对应的Properties变量,我这里写在了CustomLitForwardPass里Attributes之前。

因为我们需要使用世界坐标来进行采样,所以我们需要VertexShader部分计算WorldPosition并传递出来。正好原版的LitForwardPass.hlsl有这个部分,只是被包裹在一个宏定义里,我们注释掉这个宏就行。同时我们也需要用到世界空间的法线和切线用来做矩阵转换,这个部分也是Shader原本就有的内容,暴力注释掉宏就可以。

然后就可以开始写我们的“InitializeStandardLitSurfaceData”了。

首先是遮罩的计算、贴图的采样和混合,基础颜色和粗糙度AO使用普通的线性插值混合就可以了,切线空间的法线贴图经历了先转换到世界空间、和顶点法线混合后,也使用线性插值进行了混合,最后重新从世界空间转换到切线空间。

然后就是为我们的surfaceData赋能:使用我们计算好的albedo、packed、normal来初始化surfaceData的参数。

到这里就快要结束了,我们返回Unity查看Shader效果,会发现没有法线效果,这是因为在InitializeInputData函数里还有关于法线的宏定义,因为我们没有使用:LitShader的GUI,这个是否使用了法线和细节贴图的宏会保持关闭。给它注释掉法线就正常了。

最后我们在Unity里实现的效果如下图:

Over

欢迎加入我们!

感兴趣的同学可以在官网投递简历:

内推码:NTAI1kh

引擎校招:搜狐畅游 - 校园招聘

引擎实习:搜狐畅游 - 校园招聘

TA校招:搜狐畅游 - 校园招聘

TA实习:搜狐畅游 - 校园招聘

渲染TA实战:三方向映射 UE4相关推荐

  1. 渲染TA实战:冰面效果制作分享

    序 Hello大家好我是来自引擎部ta组的小圆,这次为大家介绍多层采样的冰面制作的分享.这个效果的特点在于:使用视差+多层采样实现了表面的凹凸起伏和具有深度的裂纹效果. 本次分享的包含: Substa ...

  2. 渲染TA实战:眼球的渲染

    哈喽,大家好,我是搜狐畅游引擎部的TA老胡,很高兴有机会和大家分享一下前一段时间工作和学习的成果. 前一阵,我做技术支持的项目提出,想要一个效果好一点的,眼睛的解决方案. Unity自带的渲染部分,包 ...

  3. 渲染TA实战:摄影测量游戏模型制作指南

    hi,大家好~我是来自搜狐畅游引擎部的美术向技术美术,小源小榞小圆,来到我们畅游引擎部门马上就要两年了.这次应部门老大邀请做一次分享.主要为摄影测量的实践细节. 为什么分享的主题是摄影测量游戏模型制作 ...

  4. Tri-Planar-Mapping[三面映射]

    为什么要用三面映射 首先说说为什么要用三面映射吧.在很多情况下会遇到uv不规范,甚至没有uv的模型(至少我遇到了没有uv的模型),在这种情况下,如果还需要使用纹理映射,就需要生成合适的uv. 另外的, ...

  5. 实战三:手把手教你实现物体识别

                                 实战三:手把手教你实现物体识别 一.基于Haad+Adaboost实现人脸识别 1.原理介绍(参考下面的博客文章) http://www.cn ...

  6. pytorch快速入门与实战——三、Unet实现

    专栏目录:pytorch(图像分割UNet)快速入门与实战--零.前言 pytorch快速入门与实战--一.知识准备(要素简介) pytorch快速入门与实战--二.深度学习经典网络发展 pytorc ...

  7. 实时体积云渲染(地平线):三.云体渲染

    实时体积云渲染(地平线):三.云体渲染 体渲染 最常见的体可视化方法就是Ray-marching法.该方法如下图: 在像云这样的部分透明的介质的情况下,采样点将沿着视线进一步增加到场景中,并增加固定的 ...

  8. 云计算Python自动化运维开发实战 三、python文件类型

    为什么80%的码农都做不了架构师?>>>    云计算Python自动化运维开发实战 三.python文件类型 导语: python常用的有3种文件类型 1. 源代码     py ...

  9. 【Qt】数据库实战(三)

    00. 目录 文章目录 00. 目录 01. 概述 02. 开发环境 03. 增删改查操作 04. 名字绑定和位置绑定 05. 程序示例 06. 批处理操作 07. 事务操作 08. 附录 01. 概 ...

最新文章

  1. 通过项目逐步深入了解Spring MVC(一)
  2. 2018年学员信息系统项目管理师备考经验
  3. matlab adc仿真,[转载]关于ADC仿真做FFT的设置和结果分析
  4. 微信WebView关闭后本地cookie无法清除问题
  5. sdn体系的三个平面_软件定义网络基础---SDN控制平面
  6. Hibernate Collection乐观锁定
  7. oracle 启用闪回数据库,如何启用Oracle10g闪回数据库特性
  8. 视图之一--创建简单的视图
  9. 机器学习代码实战——保存和加载模型(Save and Load Model)
  10. 芯片巨头三国杀:AI加剧芯片厂商间竞赛,英特尔、英伟达、AMD竞相发力
  11. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_06-vuejs研究-vuejs基础-v-on指令...
  12. 分享关于如何检测视频流码率
  13. [乐意黎]某音上超酷炫的 Word Clock 文字云时钟屏保配置
  14. 2D游戏平滑的迷雾战争效果
  15. 家用宽带如何叠加多条宽带,提高局域网速度
  16. 微信公众号:我们可以用它来干什么?
  17. 新库上线 | CnOpenData日本专利及引用被引用数据
  18. Db2 V11设计与调优 --- IBM中文官网
  19. 开源众包积分新功能上线啦
  20. IM——直播互动场景

热门文章

  1. VGL与中国海洋石油签署液化天然气购销协议;徐工汉云打造国内首个智能化剥片机组 | 能动...
  2. OraDump导出套件
  3. 在线预览pdf(不可下载)
  4. 猿创征文 |【gin-vue-admin】后端结构设计和基本工作原理
  5. 华为如何显示我的电脑连接到服务器地址,怎么查电脑的服务器连接地址
  6. 内网 centos7 离线安装rpm包的三种方法
  7. python官方文档学习_Python3.5.2官方文档学习备忘录
  8. java豆瓣查书api_如何通过豆瓣API获取图书和电影列表
  9. python程序改变图像的分辨率
  10. mysql进行创建序列化