渲染TA实战:三方向映射 UE4
大家好我是来自搜狐畅游引擎部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相关推荐
- 渲染TA实战:冰面效果制作分享
序 Hello大家好我是来自引擎部ta组的小圆,这次为大家介绍多层采样的冰面制作的分享.这个效果的特点在于:使用视差+多层采样实现了表面的凹凸起伏和具有深度的裂纹效果. 本次分享的包含: Substa ...
- 渲染TA实战:眼球的渲染
哈喽,大家好,我是搜狐畅游引擎部的TA老胡,很高兴有机会和大家分享一下前一段时间工作和学习的成果. 前一阵,我做技术支持的项目提出,想要一个效果好一点的,眼睛的解决方案. Unity自带的渲染部分,包 ...
- 渲染TA实战:摄影测量游戏模型制作指南
hi,大家好~我是来自搜狐畅游引擎部的美术向技术美术,小源小榞小圆,来到我们畅游引擎部门马上就要两年了.这次应部门老大邀请做一次分享.主要为摄影测量的实践细节. 为什么分享的主题是摄影测量游戏模型制作 ...
- Tri-Planar-Mapping[三面映射]
为什么要用三面映射 首先说说为什么要用三面映射吧.在很多情况下会遇到uv不规范,甚至没有uv的模型(至少我遇到了没有uv的模型),在这种情况下,如果还需要使用纹理映射,就需要生成合适的uv. 另外的, ...
- 实战三:手把手教你实现物体识别
实战三:手把手教你实现物体识别 一.基于Haad+Adaboost实现人脸识别 1.原理介绍(参考下面的博客文章) http://www.cn ...
- pytorch快速入门与实战——三、Unet实现
专栏目录:pytorch(图像分割UNet)快速入门与实战--零.前言 pytorch快速入门与实战--一.知识准备(要素简介) pytorch快速入门与实战--二.深度学习经典网络发展 pytorc ...
- 实时体积云渲染(地平线):三.云体渲染
实时体积云渲染(地平线):三.云体渲染 体渲染 最常见的体可视化方法就是Ray-marching法.该方法如下图: 在像云这样的部分透明的介质的情况下,采样点将沿着视线进一步增加到场景中,并增加固定的 ...
- 云计算Python自动化运维开发实战 三、python文件类型
为什么80%的码农都做不了架构师?>>> 云计算Python自动化运维开发实战 三.python文件类型 导语: python常用的有3种文件类型 1. 源代码 py ...
- 【Qt】数据库实战(三)
00. 目录 文章目录 00. 目录 01. 概述 02. 开发环境 03. 增删改查操作 04. 名字绑定和位置绑定 05. 程序示例 06. 批处理操作 07. 事务操作 08. 附录 01. 概 ...
最新文章
- 通过项目逐步深入了解Spring MVC(一)
- 2018年学员信息系统项目管理师备考经验
- matlab adc仿真,[转载]关于ADC仿真做FFT的设置和结果分析
- 微信WebView关闭后本地cookie无法清除问题
- sdn体系的三个平面_软件定义网络基础---SDN控制平面
- Hibernate Collection乐观锁定
- oracle 启用闪回数据库,如何启用Oracle10g闪回数据库特性
- 视图之一--创建简单的视图
- 机器学习代码实战——保存和加载模型(Save and Load Model)
- 芯片巨头三国杀:AI加剧芯片厂商间竞赛,英特尔、英伟达、AMD竞相发力
- 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_06-vuejs研究-vuejs基础-v-on指令...
- 分享关于如何检测视频流码率
- [乐意黎]某音上超酷炫的 Word Clock 文字云时钟屏保配置
- 2D游戏平滑的迷雾战争效果
- 家用宽带如何叠加多条宽带,提高局域网速度
- 微信公众号:我们可以用它来干什么?
- 新库上线 | CnOpenData日本专利及引用被引用数据
- Db2 V11设计与调优 --- IBM中文官网
- 开源众包积分新功能上线啦
- IM——直播互动场景
热门文章
- VGL与中国海洋石油签署液化天然气购销协议;徐工汉云打造国内首个智能化剥片机组 | 能动...
- OraDump导出套件
- 在线预览pdf(不可下载)
- 猿创征文 |【gin-vue-admin】后端结构设计和基本工作原理
- 华为如何显示我的电脑连接到服务器地址,怎么查电脑的服务器连接地址
- 内网 centos7 离线安装rpm包的三种方法
- python官方文档学习_Python3.5.2官方文档学习备忘录
- java豆瓣查书api_如何通过豆瓣API获取图书和电影列表
- python程序改变图像的分辨率
- mysql进行创建序列化