【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.rhkb.cn/news/432883.html

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

相关文章

叉车司机信息权限采集系统,保障与优化叉车运输网络的安全

叉车司机信息权限采集系统可以通过监控司机的行车行为和车辆状况&#xff0c;实时掌握车辆位置和行驶路线&#xff0c;从而提高运输安全性&#xff0c;优化运输网络&#xff0c;降低事故风险。同时&#xff0c;该系统还可以通过对叉车司机信息和行车数据的分析&#xff0c;优化…

Flutter屏幕适配

我们可以根据下面有适配属性的Widget来进行屏幕适配 1.MediaQuery 通过它可以直接获得屏幕的大小&#xff08;宽度 / 高度&#xff09;和方向&#xff08;纵向 / 横向&#xff09; Size screenSize MediaQuery.of(context).size; double width screenSize.width; double h…

springboot异常(三):异常处理原理

&#x1f345;一、BasicErrorController ☘️1.1 描述 BasicErrorController是Springboot中默认的异常处理方法&#xff0c;无需额外的操作&#xff0c;当程序发生了异常之后&#xff0c;Springboot自动捕获异常&#xff0c;重新请求到BasicErrorController中&#xff0c;在B…

网络安全 DVWA通关指南 DVWA Stored Cross Site Scripting (存储型 XSS)

DVWA Stored Cross Site Scripting (存储型 XSS) 文章目录 DVWA Stored Cross Site Scripting (存储型 XSS)XSS跨站原理存储型 LowMediumHighImpossible 参考文献 WEB 安全靶场通关指南 相关阅读 Brute Force (爆破) Command Injection&#xff08;命令注入&#xff09; Cro…

Spring:项目中的统一异常处理和自定义异常

介绍异常的处理方式。在项目中&#xff0c;都会进行自定义异常&#xff0c;并且都是需要配合统一结果返回进行使用。 1.背景引入 &#xff08;1&#xff09;背景介绍 为什么要处理异常&#xff1f;如果不处理项目中的异常信息&#xff0c;前端访问我们后端就是显示访问失败的…

eslint-plugin-react的使用中,所出现的react版本警告

记一次使用eslint-plugin-react的警告 Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration . 背景 我们在工程化项目中&#xff0c;常常会通过eslint来约束我们代码的一些统一格…

基于RPA+BERT的文档辅助“悦读”系统 | OPENAIGC开发者大赛高校组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…

关于寻址方式的讨论

### 对话内容 **学生B&#xff08;ESFP&#xff09;**&#xff1a;老师&#xff0c;寻址方式听起来很复杂&#xff0c;能详细讲解一下吗&#xff1f;而且最好能举些具体例子&#xff01;&#x1f60a; **老师&#xff08;ENTP&#xff09;**&#xff1a;当然可以&#xff01;…

JVM(HotSpot):方法区(Method Area)

文章目录 一、内存结构图二、方法区定义三、内存溢出问题四、常量池与运行时常量池 一、内存结构图 1.6 方法区详细结构图 1.8方法区详细结构图 1.8后&#xff0c;方法区是JVM内存的一个逻辑结构&#xff0c;真实内存用的本地物理内存。 且字符串常量池从常量池中移入堆中。 …

蓝队技能-应急响应篇Web内存马查杀Spring框架型中间件型JVM分析Class提取

知识点&#xff1a; 1、应急响应-Web框架内存马-分析&清除 2、应急响应-Web中间件内存马-分析&清除 注&#xff1a;框架型内存马与中间件内存马只要网站重启后就清除了。 目前Java内存马具体分类&#xff1a; 1、传统Web应用型内存马 Servlet型内存马&#xff1a;…

vivado中除法器ip核的使用

看了很多博客&#xff0c;都没写清楚&#xff0c;害 我要实现 reg [9:0] a; 被除数 reg [16:0] b; 除数 wire [39:0] res; 结果 wire [15:0] real_shan; 要实现a/b 则如下这么配置 选择经过几个周期出结果 wire [39:0] res; // dly5 div_gen_0 div_gen_0_inst (.aclk(clk), …

精密制造的革新:光谱共焦传感器与工业视觉相机的融合

在现代精密制造领域&#xff0c;对微小尺寸、高精度产品的检测需求日益迫切。光谱共焦传感器凭借其非接触、高精度测量特性脱颖而出&#xff0c;而工业视觉相机则以其高分辨率、实时成像能力著称。两者的融合&#xff0c;不仅解决了传统检测方式在微米级别测量上的局限&#xf…

通过 LabVIEW 正则表达式读取数值(整数或小数)

在LabVIEW开发中&#xff0c;字符串处理是一个非常常见的需求&#xff0c;尤其是在处理包含复杂格式的数字时。本文通过一个具体的例子来说明如何利用 Match Regular Expression Function 和 Match Pattern Function 读取并解析字符串中的数字&#xff0c;并重点探讨这两个函数…

MyBatis<foreach>标签的用法与实践

foreach标签简介 实践 demo1 简单的一个批量更新&#xff0c;这里传入了一个List类型的集合作为参数&#xff0c;拼接到 in 的后面 &#xff0c;来实现一个简单的批量更新 <update id"updateVislxble" parameterType"java.util.List">update model…

计算机视觉学习路线

计算机视觉&#xff08;Computer Vision&#xff09;是计算机科学的一个重要分支&#xff0c;旨在使计算机能够理解和解释视觉数据。以下是一个详细的计算机视觉学习路线&#xff0c;帮你系统地掌握这个领域所需的知识和技能。 1. 基础数学和编程 在深入学习计算机视觉之前&…

希捷电脑硬盘好恢复数据吗?探讨可能性、方法以及注意事项

在数字化时代&#xff0c;数据已成为我们生活和工作中不可或缺的一部分。希捷电脑硬盘作为数据存储的重要设备&#xff0c;承载着大量的个人文件、工作资料以及珍贵回忆。然而&#xff0c;面对硬盘故障或误操作导致的数据丢失&#xff0c;许多用户不禁要问&#xff1a;希捷电脑…

毕业设计选题:基于ssm+vue+uniapp的鲜花销售小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

FLUX.1图像生成模型:AI工程师的实践与探索

文章目录 1 FLUX.1系列模型2 AI工程师的视角3 ComfyUI部署4 FLUX.1部署5 工作流6 面向未来 黑森林实验室&#xff08;Black Forest Labs&#xff09;研发的FLUX.1图像生成模型&#xff0c;以其120亿参数的庞大规模&#xff0c;正在重新定义图像生成技术的新标准。FLUX.1系列模型…

【TabBar嵌套Navigation案例-新特性页面-代码位置 Objective-C语言】

一、接下来,我们来说这个新特性页面 1.首先,看一下我们的示例程序,这里改一下,加一个叹号, command + R, 好,首先啊,这里边有一个新特性页面,当我这个程序是第一次安装、第一次运行、还有呢、就是当这个应用程序更新的时候,我应该去加载这个新特性页面, 然后呢,这…

信息,就是位+上下文什么是文本文件和二进制文件

信息&#xff0c;就是位上下文 计算机系统是由硬件和软件系统组成的&#xff0c;它们共同工作来运行应用程序 hello.c #include <stdio.h>int main(){printf("Hello World~");return 0; }hello程序的生命周期是从一个源程序&#xff08;或者说源文件&#xf…