【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第四篇-着色器投影-接收阴影部分】

上一章中实现了体积渲染的光照与自阴影,那我们这篇来实现投影

回顾

勘误

在开始本篇内容之前,我已经对上一章中的内容的错误进行了修改。为了确保不会错过这些更正,同时也避免大家重新阅读一遍,我将在这里为大家演示一下修改的具体内容。

  • 错误连接:在之前的文章里,SkyLightEnvMapSample 错误地连接到了“光源方向”。
  • 正确连接:实际上,需要沿垂直方向 (0,0,-1) 进行采样,这样才能与 SkyAtmosphere 的结果保持一致。
    在这里插入图片描述
    (图为SkyLightEnvMapSample 正确的输入)

扩展:
快速的制作一个左右分屏,对比一下两者:
在这里插入图片描述
在材质编辑器没有SkyAtmosphere,因此左侧没有环境光:
在这里插入图片描述
在场景中,可以看到左右两侧的天光是基本一致的:
在这里插入图片描述


准备工作:整理和完善

再开始本章工作前,先对我们的凌乱的Shader做一次整理和完善吧!

整理

整理Freams

首先我们正式将XYFrames拆成两个参数,因为怕有人忘记这是一个float2

在这里插入图片描述
我们有一段时间不会再见到他们了,把他们 折叠到节点 ,收纳起来
在这里插入图片描述
起个名改个引脚,后续的相同操作就不多说明了
在这里插入图片描述

整理Base

本章中还需要修改这里,所以先不打包
但我们把Density移动到LightVecor上方
在这里插入图片描述

之前之后
在这里插入图片描述在这里插入图片描述

Tip:
在这里拖动
在这里插入图片描述

Tip:
小懒蛋们,在文章最后展示全部代码的分,完整的输入节点的"快速粘贴"

完善光照输入

在上一章中介绍了多种对环境光照的取值方式,我们现在为他们制作切换函数

1.整理输入

调整位置,将LightVector LightColor SkyColor 三个相关输入摆放在一起:
(图中演示的是不基于SkyAtmosphere,都做相同操作)

之前之后
在这里插入图片描述在这里插入图片描述
2.制作函数

在这里插入图片描述
将它们折叠到函数,起名VolumeLight
(如果你使用的是早期UE5版本,则需要手动创建材质函数)
在这里插入图片描述
为其增加Input ,输入类型为StaticBool,三个输入分别命名为
UseSkyAtmosphere CustomLightVector CustomColor
在这里插入图片描述

我们可以使用一个小技巧,在材质中创建一个“结构体”,然后通过Switch节点进行切换,从而减少Switch节点的数量。
现在,我们已经制作了一个函数,使其可以在几种方式之间进行切换。
需要特别说明的是,实际传递的内容与MaterialAttributes节点使用的名字无关。例如,在全局位置偏移中实际放置的是LightVector
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面这几张整体截图中,Input的输入类型是 Bool ,但应该像第一张图一样是 StaticBool

归一化

为了出于对转换结果的安全考虑,这里增加一个归一化
在这里插入图片描述

世界空间LightVectorWS

我们保留一个未转换为本地空间的光照方向作为输出,稍后会用到
在这里插入图片描述

Tip:为了避免造成误解,需要再次说明,实际传递的内容与 MaterialAttributes 节点上使用的名字无关。例如,在 全局位置偏移 中,实际放置的是 LightVector 。只是将它作为"结构体"使用。

完成后是这个样子
在这里插入图片描述
在这里插入图片描述

3.整理SelfShadow

好现在我么可以整理剩下的东西了
在这里插入图片描述
如上图,直接折叠,改好名即可


Done
清爽啦
现在已经把环境整理妥当,那就开始本章内容吧!


制作Shader

简单的概述

接下来的阴影效果会将着色器的复杂度提升一大截。

需要注意的是,着色器讲究的是“看上去对的”就是“对的”。除非你确实有重大需求,否则没有必要无脑地加入更多功能,以免增加不必要的复杂度。目前,体积雾的着色器已经在计算光照的过程中实现了“自阴影”,这在很多情况已经足够了。

接下来,我们要为其增加另外两种阴影效果,分别是“体积雾对其他物体投射的阴影(投射阴影)”和“其他物体在体积雾上的阴影(接收阴影)”

接收阴影

在材质球中实现真正的“接收阴影”是一项非常复杂的任务,这对于Shader来说是非常昂贵和不切实际的。然而,我们可以利用“距离场阴影”技术来实现相似的效果。UE引擎已经广泛应用了这种技术,它计算成本低且能够生成柔和的软阴影,在性能和视觉效果之间取得了良好的平衡。

因此,我们的“接收阴影”本质上是实现一个“距离场阴影”。不过,在这篇文章中,我不会详细介绍“距离场阴影”的具体实现,因为相关信息已经很容易找到。未来,我计划写一些基于距离场的效果,届时会对“距离场阴影”进行更详细的介绍。

接下来的工作是采样全局距离场,以实现所需的阴影效果。

增加变量

在这里插入图片描述

为 RayMarching 新增4个输入

输入说明
LightVectorWS世界空间光方向
CameraPosWS世界空间相机位置
LocalObjectBoundsMax本地空间的Bound框大小
LightTangent光切线,也就是对距离场步进时的距离,这将决定阴影边缘的模糊程度
DFSStepsDistanceFieldShdowSteps 的缩写,距离场阴影的采样步数

快速粘贴

((InputName="Tex"),(InputName="XYFrames"),(InputName="NumFrames"),(InputName="MaxSteps"),(InputName="StepSize"),(InputName="LocalCamVec"),(InputName="CurPos"),(InputName="FinalStepSize"),(InputName="Density"),(InputName="LightVector"),(InputName="LightColor",Input=(OutputIndex=1)),(InputName="SkyColor",Input=(OutputIndex=2)),(InputName="ShadowSteps"),(InputName="ShadowStepSize"),(InputName="ShadowDensity"),(InputName="ShadowThreshold"),(InputName="AmbientDensity"),(InputName="LightVectorWS"),(InputName="CameraPosWS"),(InputName="LocalObjectBoundsMax",Input=(OutputIndex=3,Mask=1,MaskR=1,MaskG=1,MaskB=1)),(InputName="LightTangent"),(InputName="DFSSteps")

增加代码

RayMarching代码如下:

//创建变量,从0开始累加沿相机方向步进过程中的总密度
float accumdens = 0;//Shadow部分
//创建变量,透射率和光线的能量
float transmittance =1;
float3 lightenergy = 0;
//基本和相机方向步进一样,但这些都是常量,不需要写进for里
Density *= StepSize;
LightVector *= ShadowStepSize;
ShadowDensity *= ShadowStepSize;
//一个对数来计算阈值,用来判断光线是否还值得计算
float shadowthresh = -log(ShadowThreshold)/ShadowDensity;//使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;// 在当前步进位置进行纹理采样//Shadow部分if(cursample > 0.001)//如果采样位置没有密度,则跳过{float3 Lpos = CurPos;//Lpos将作为光线步进的起始位置float shadowdist = 0;//和之前的accumdens一样,积累阴影//自阴影for(int s = 0; s < ShadowSteps; s++){Lpos += LightVector;//移动步进位置float Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样//判断是否在框内,不是则直接break退出forfloat3 shadowboxtest = floor( 0.5+ (abs(0.5-Lpos)));//float exitshadowbox = shadowboxtest.x + shadowboxtest.y + shadowboxtest.z;float exitshadowbox = dot(shadowboxtest,1);//简短的通道相加if(shadowdist > shadowthresh || exitshadowbox >= 1) break;shadowdist += Lsample;//累计}//接收阴影float3 dfpos = 2 * (CurPos -0.5) * LocalObjectBoundsMax;//-0.5 * 2,得到一个居中的Bounddfpos = LWCToFloat(TransformLocalPositionToWorld(Parameters,dfpos)) - CameraPosWS;//将dfpos转换为世界空间,需要LWC精度所以在代码里转换,减去相机位置float dftracedist = 1; //创建四个变量float dfshadow = 1;//这是我们最终要的float curdist = 0;float DistanceAlongTrace = 0;for (int d = 0; d < DFSSteps; d++)//又一次的光线步进{DistanceAlongTrace += curdist;//增加距离curdist = GetDistanceToNearestSurfaceGlobal(dfpos);//采样全局距离场,他和蓝图里`DistanceToNearestSurface`是相同函数float SphereSize = DistanceAlongTrace * LightTangent;//采样距离场软阴影的球形距离dfshadow = min( saturate(curdist/SphereSize),dfshadow);//用小于它的结果来更新变量dfpos.xyz += LightVectorWS * dftracedist * curdist;//继续移动位置dftracedist *= 1.0001;//增加一个很小的因子}//更新样本和光能,算法是BeersLaw函数cursample = 1 -exp(-cursample * Density);lightenergy += exp(-shadowdist * ShadowDensity) * cursample * transmittance * LightColor * dfshadow;//在结果上乘dfshadowtransmittance *= 1-cursample;//环境光照部分shadowdist = 0;//重置一下阴影距离,继续利用它计算光照Lpos = CurPos + float3(0,0,0.025);//新位置float Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样shadowdist += Lsample;Lpos = CurPos + float3(0,0,0.05);Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样shadowdist += Lsample;Lpos = CurPos + float3(0,0,0.15);Lsample = PseudoVolumeTexture(Tex, TexSampler, saturate(Lpos), XYFrames, NumFrames).r;//采样shadowdist += Lsample;lightenergy += exp(-shadowdist * AmbientDensity) *cursample * SkyColor * transmittance;//累计到光}CurPos += -LocalCamVec;
}CurPos += LocalCamVec * (1 - FinalStepSize);
float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;return float4(lightenergy, transmittance);

这是增加的部分:
在这里插入图片描述

正如之前提到的,这里不详细解释距离场阴影的原理。简单来说,我们在上一章实现了自阴影的基础上,添加了接收阴影的计算,并在后续的 lightenergy 累计过程中引入了 dfshadow

Tip:
使用custom写hlsl的一大缺点就是没啥向后兼容性,EPIC改不改函数名字完全取决于心情。这里使用了一个函数 GetDistanceToNearestSurfaceGlobal() 他就是蓝图中的 DistanceToNearestSurface , 如果将来哪个版本里函数报错了,希望你知道你需要找什么。
在这里插入图片描述

有关性能

尽管距离场阴影的性能相对较好,但它也并非真正的低成本。DistanceFieldShadowSteps 参数设置过低时,通常会出现一些奇怪的问题。为了避免这些问题,我在使用时一般将这个参数设置为大于32。不过需要注意的是,这意味着在主循环的每一步中都会执行32次计算。

目前效果

在这里插入图片描述
一个接收阴影就做好了

Done


最近工作比较忙,这导致这个Shader写的比较慢。原本计划在一篇文章中同时做接收和投影阴影的实现,不过现在看来得把它们分开写。
为了避免拖得太久让大家着急,我们将在下一章再制作另一部分投影阴影的内容。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/55078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法分析——《二分查找》

&#x1f6e9;《二分查找》 &#x1f3a8;题目描述&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 …

Java基础(Arrays工具类)(asList()方法)(详细)

目录 一、Arrays工具类 &#xff08;1&#xff09;引言 &#xff08;2&#xff09;基本介绍 &#xff08;3&#xff09;主要功能&#xff08;提供的方法&#xff09; &#xff08;I&#xff09;排序&#xff08;Arrays.sort()&#xff09; &#xff08;II&#xff09;搜索(查找…

怎么给视频加片头片尾和字幕

在这个视觉内容爆炸的时代&#xff0c;一段精心制作的视频不仅能吸引眼球&#xff0c;更能传达深刻的情感与信息。而一个引人入胜的片头、一个温馨感人的片尾&#xff0c;以及恰到好处的字幕&#xff0c;无疑是提升视频质感的关键。那么新人要怎么给视频加片头片尾和字幕效果呢…

Springboot项目-实战2-实现

文章目录 接口接收数据并进行数据清洗mysql读取到redis接口返回参数对象java函数使用备注返回参数分析stream操作Thread线程队列集合存储统计加密日志以及aspect对接口的时间影响&#xff1f;war包和jar包的区别&#xff1f;filter、interceptor、aspect区别&#xff1f;探针Gs…

如何在 Kubernetes 上部署和配置开源数据集成平台 Airbyte?

在 Kubernetes 上部署和配置 Airbyte 是一个复杂但非常有价值的过程&#xff0c;特别是对于需要强大数据集成和数据处理能力的企业或团队。Airbyte 是一个开源的数据集成平台&#xff0c;允许用户从各种来源提取数据并加载到目标存储中。其强大的插件系统支持多种数据源与目标&…

C语言 | Leetcode C语言题解之第440题字典序的第K小数字

题目&#xff1a; 题解&#xff1a; #define MIN(x, y) ((x) < (y) ? (x) : (y))int getSteps(int curr, long n) {int steps 0;long first curr;long last curr;while (first < n) {steps MIN(last, n) - first 1;first first * 10;last last * 10 9;}return …

QT使用qss控制样式实现动态换肤

文章目录 设计QSS样式表动态加载QSS文件主函数调用QT提供了一种非常灵活的方式来使用QSS(Qt Style Sheet,类似于 CSS 的样式表),实现界面的动态换肤功能。QSS可以改变Qt应用程序中几乎所有可视组件的外观,包括颜色、字体、边框等。下面介绍一下如何通过QSS实现动态换肤。 设…

诗画紫砂壶

大家详细解答一首网络上流传的顺口溜&#xff0c;其中包含了很多的紫砂壶型。 // 紫砂壶型 // 秦权汉瓦唐羽仙&#xff0c;西施文旦美人肩。 逸公德钟对却月&#xff0c;仿鼓虚扁望方山。东坡提梁卧井栏&#xff0c;供春提璧看柿圆。荷花海棠吹松段。掇只君乐奏合欢&#xff…

vue3中< keep-alive >页面实现缓存及遇到的问题

vue3中< keep-alive >页面实现缓存及遇到的问题 实现原理&#xff1a;keep-alive 是 Vue 的内置组件&#xff0c;当它包裹动态组件时&#xff0c;会缓存不活动的组件实例&#xff0c;而不是销毁它们。实现不同路由是否缓存只需要设置对应路由参数keepAlive为true&#xf…

64.【C语言】再议结构体(下)

本文衔接第63篇63.【C语言】再议结构体(上) 目录 目录 6.复习 7.修改默认对齐数 8.结构体传参 01.传递非指针参数 02.传递指针参数(传递地址) 03.对比 9.结构体实现位段 01.位段的定义 02.格式 03.例题 答案速查 分析 10.位段跨平台问题 11.位段的应用 12.其他…

scrapy 爬取微博(五)【最新超详细解析】: 爬取微博文章

1 读取配置参数 爬取微博文章首先需要读取settings.py中的设置的配置变量&#xff0c;然后编写爬虫&#xff0c;读取的配置变量主要有爬取的关键词、时间范围、爬取区域等。 class WeiboSearchSpider(scrapy.Spider):name weibo_searchallowed_domains [weibo.com]settings…

完成UI界面的绘制

绘制UI 接上文&#xff0c;在Order90Canvas下创建Image子物体&#xff0c;图片资源ui_fish_lv1&#xff0c;设置锚点&#xff08;CountdownPanelImg同理&#xff09;&#xff0c;命名为LvPanelImg,创建Text子物体&#xff0c;边框宽高各50&#xff0c; &#xff0c;重名为LvT…

影刀---如何进行自动化操作

本文不是广告&#xff0c;没有人给我宣传费&#xff0c;只是单纯的觉得这个软件很好用 感谢大家的多多支持哦 本文 1.基本概念与操作&#xff08;非标准下拉框和上传下载&#xff09;非标准对话框的操作上传对话框、下载的对话框、提示的对话框 2.综合案例3.找不到元素怎么办&a…

css 中 ~ 符号、text-indent、ellipsis、ellipsis-2、text-overflow: ellipsis、::before的使用

1、~的使用直接看代码 <script setup> </script><template><div class"container"><p><a href"javascript:;">纪检委</a><a href"javascript:;">中介为</a><a href"javascript:…

架构师:消息队列的技术指南

1、简述 消息队列(Message Queue, MQ)是一种异步通信机制,允许系统的各个组件通过消息在彼此之间进行通信。消息队列通过解耦系统组件、缓冲高峰期请求和提高系统的可扩展性,成为分布式系统中不可或缺的一部分。 2、工作原理 消息队列的基本工作原理是生产者将消息发布到…

python爬虫案例——抓取链家租房信息(8)

文章目录 1、任务目标2、分析网页3、编写代码1、任务目标 目标站点:链家租房版块(https://bj.lianjia.com/zufang/) 要求:抓取该链接下前5页所有的租房信息,包括:标题、详情信息、详情链接、价格 如: 2、分析网页 用浏览器打开链接,按F12或右键检查,进入开发者模式;因…

Axure-本地发布,局域网内用户访问

目录 前言内容一. 选择Axure发布路径&#xff0c;进行相应配置二.添加IIS服务配置&#xff08;不在这里赘述&#xff01;&#xff09;三&#xff1a;添加IIS配置&#xff0c;创建网站四.添加安全策略&#xff1a;实践&#xff1a; 前言 最近加入了公司的积分系统&#xff0c;由…

Qt网络编程——QUdpSocket

文章目录 Qt网络编程QUdpSocketUdp回显服务器Udp客户端 Qt网络编程 网络编程本质上是写应用层代码&#xff0c;需要传输层提供支持。 而传输层最核心的协议就是UDP和TCP&#xff0c;这两个协议有较大差别&#xff0c;所以Qt提供了两套API。 要是有Qt网络编程的API&#xff0…

【CSS Tricks】css动画详解

目录 引言一、动画关键帧序列二、动画各属性拆解1. animation-name2. animation-duration3. animation-delay3.1 设置delay为正值3.2 设置delay为负值 4. animation-direction5. animation-iteration-count6. animation-fill-mode7. animation-play-state8. animation-timing-f…

Kubernetes云原生存储解决方案之 Rook Ceph实践探究

Kubernetes云原生存储解决方案之 Rook Ceph实践探究 除了手动部署独立的 Ceph 集群并配置与Kubernetes进行对接外&#xff0c;Rook Ceph 支持直接在 Kubernetes 集群上部署 Ceph 集群。 通过Rook Ceph云原生存储编排平台&#xff0c;使得 Kubernetes 集群中启用高可用的 Ceph…