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

news2024/11/30 2:51:46

投射阴影

最初打算将投影内容放在上一篇中,因为实现非常快速简单,没必要单独成篇。不过因为这里面涉及一些问题,我觉得还是单独作为一篇讲一下比较好。

原理

这里要用到的是 Shadow Pass Switch ,它可以为非不透明的材质替换阴影

某些版本UE只能搜中文"阴影通道切换"

在这里插入图片描述

简单的演示一下 Shadow Pass Switch 的功能
做一个这样的材质:
在这里插入图片描述
效果如下:
在这里插入图片描述

制作Shader

创建一个 Custom ,起名为 ShadowRayMarching ,输入节点如图,输出单通道
在这里插入图片描述

老样子,这些可以直接右键粘贴到输入:

((InputName="Tex"),(InputName="XYFrames"),(InputName="NumFrames"),(InputName="MaxSteps"),(InputName="StepSize"),(InputName="LightVector"),(InputName="CurPos"),(InputName="LocalObjectBoundsMax",Input=(OutputIndex=3,Mask=1,MaskR=1,MaskG=1,MaskB=1)))

代码如下:

float accumdens = 0;
for (int i = 0; i < MaxSteps; i++)
{
    float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;
    
    accumdens += cursample * StepSize ;
    CurPos -= LightVector * StepSize ; // 步进方向换成了 LightVector
}
//返回累计结果
return accumdens ;

是不是很熟悉,这就是我们梦开始的地方。还记得我们的起点吗,我们第一步就做了这样一个步进,只是当时是"从相机方向"进行步进。因为现在做的是阴影,也就需要改为从光源方向步进,因此代码中现在是:

CurPos -= LightVector * StepSize;//这里与之前不同

从相机方向步进:
在这里插入图片描述

从光源方向步进:
在这里插入图片描述

连接变量:
只有 ShadowMask_MaxSteps 是新建变量,其余都是已有变量,我们直接使用
将结果连入 BeersLaw (还记得吗,这是介质吸收),连到Mask输出打印看看(材质当然也换到了 已遮罩 ,因为现在是法线向内的模型,因此也要开启双面

在这里插入图片描述

在这里插入图片描述

看看效果,这就是用来形成阴影的Mask

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

制作Mesh

到这一步,我们会遇到一个问题
我们目前使用的模型是法线向内的,如果不开启 双面 ,你看的实际会是这样:
在这里插入图片描述

背面透明的材质无法阻挡光照并投射阴影。
如果继续使用"双面"呢?
可惜投影是不区分 TwoSidedSign 的,实际上投影对很多东西都不支持,稍后会提到

  1. 创建双面材质
    在这里插入图片描述
  2. 打光,可以看到正反面的不同并不会影响阴影
    在这里插入图片描述

况且,由于体积渲染本身已经很耗资源,开启双面会导致性能难以接受。或者,你可能考虑使用 TwoSidedSign 来从视觉上剔除体积雾的“外部”渲染,但这对性能没有改善。

举例来说,这就像在计算 (1+2+3) * 0
虽然结果是 0 ,但 1+2+3 是会被计算的
简单的因果律

总之,这个办法是无论如何都行不通的。那么我们现在有两个选择:

两种方案

方案1

使用两个Cube模型:一个法线向内的,用来渲染体积;一个法线向外的,渲染阴影遮罩。然后将他们重叠放在一起。
你可以选择这种方法,这会让内外两个mesh有更为独立的静态网格体组件的控制。缺点是整合两个模型,需要制作一个Actor蓝图,类似这样:
在这里插入图片描述

方案2

和1的思路一样,制作一个 双面的模型,并为模型内外表面分别设置材质ID

为保证纯粹性,教程采用"方案2"

建立内外法线cube

在UE直接建模也是可以的,像之前一样
但我发现UE建模功能的更新频繁,用它做教程没啥制导意义,可能过几个版本就没人看得懂了,所以这次直接用3dsMax做演示

  1. 建立一个 100cm 长宽高的 box ,模型的轴在中心 。复制出一个,增加 法线 修改器翻转法线,现在我们拥有一正一反两个模型
    在这里插入图片描述
    2.分别为它们增加材质修改器,分别指定材质ID 12
    (注意,图中两个都为1,是错的)
    在这里插入图片描述
    在这里插入图片描述

  2. 为他们增加平滑修改器,这是为了优化顶点数量
    在这里插入图片描述

  3. 将它们移回中心,并选中两个模型,塌陷
    在这里插入图片描述

  4. 创建多维子材质并赋予模型,这是为了导出时能正确导出材质ID。(图里给了一个切片,是为了让读者可以看到内外的不同材质。实际不要切哦!)
    在这里插入图片描述

  5. 导出FBX并导入UE,注意不要开启这个模型的 Nanite ,可以看到两个材质通道(元素)
    在这里插入图片描述

组合在一起

模型放入场景

  1. 将模型放入场景,注意要关掉 影响距离场光照
    还记得吗,接收阴影 是使用距离场实现的,关掉它避免影响自身
    在这里插入图片描述
  2. 创建子实例
    1.为主材质 M_VolRayMarching 创建子材质 MI_VolRayMarching
    2.再为子材质 MI_VolRayMarching 创建子材质 MI_VolRayMarching_Shadow

注意父子关系为:
M_VolRayMarchingMI_VolRayMarchingMI_VolRayMarching_Shadow

  1. MI_VolRayMarching 放入法线向内的材质通道,MI_VolRayMarching_Shadow 放入法线向外的材质通道
    在这里插入图片描述

制作材质

现在把主材质连接好,这个材质需要同时实现半透明和遮罩材质,并在子材质中切换:

主材质

直接看图:
在这里插入图片描述

  1. 增加 Static Switch Parameter 节点(图中1),起名为 IsShadow ,用来做子实例切换。
  2. 注意图中的"2"和"3"是不一样的,"2"是不透明度 Opacity,"3"是不透明蒙版 Opacity Mask
  3. 主材质是半透明,因此"不透明蒙版"是灰色,但是同样需要连上。稍后会在子材质切换到Mask材质。
  4. Switch节点是"Is Shadow",因此下面的实现阴影的部分要连到True,如图中1,别连反了。
  5. 再最后检查一下材质设置:
    在这里插入图片描述
    在这里插入图片描述
子材质

MI_VolRayMarching 目前不需要修改,直接打开 MI_VolRayMarching_Shadow

  1. 勾选IsShadow
    在这里插入图片描述
  2. 修改材质重载,混合模式改为遮罩在这里插入图片描述
  3. 检查结果:在这里插入图片描述
    在这里插入图片描述
    检查一下,可以看到,外层的Mask材质为我们投下阴影

Tip:
当你快速改变光照或者改变模型位置时,你可能会发现阴影更新不及时,这是阴影缓存造成的,将模型的阴影缓存无效化改为 始终
在这里插入图片描述

隐藏Mask材质

两套模型方案有所不同,

如果你选择的是另外一个方案(方案1),也就是"内外是两个独立模型"的方案:
选择法线向外的模型,为其勾选 隐藏阴影 (它的意思是"隐藏时阴影"),关闭 可视
就可以在非可视情况投射阴影。
在这里插入图片描述
在这里插入图片描述
我没实际做方案1,因此下图中,对一个圆柱模型进行了设置,模型被隐藏了,但阴影还在:
在这里插入图片描述

这里使用老朋友Shadow Pass Switch
也就是在视图里,Default 输入的 Mask0。阴影 Shadow 则使用我们制作的光线方向步进出来的蒙版(下图1)
留下debug
考虑到Debug,为了在需要的时候还能看到这个黑色的mask,这里做了一个切换,ShowShadowMask (上图2)

现在效果如下:

在这里插入图片描述
请添加图片描述
自阴影,接收投影,投射阴影,一切都OOKK的

Done

一些问题

投影的实现本身并不是一件简单的事情,因此我们这种快速实现方式也不可避免地存在一些缺点。
以下是一些试图修正这些问题的无用尝试和昂贵的方法
(也许在某次UE的版本更新后,这些方法会有变得有效。所以先记录下来)

当体积模型与物体穿插时,阴影会相接
在这里插入图片描述
MI_VolRayMarching_Shadow Debug一下
在这里插入图片描述
能看到这就是问题的原因
在这里插入图片描述
阴影是通过Mask实现,也就代表它无法使用 SceneDepth 等数据来修复(体积雾里我们使用了它)。

那如果使用距离场呢?
ShadowRayMarching 增加CameraPosWS输入
在这里插入图片描述
代码如下:

// 创建变量,累加步进过程中的总密度
float accumdens = 0;

// 使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{
    // PseudoVolumeTexture 函数用于伪体积纹理采样,函数需要的参数在括号内传递
    float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;

    // 使用距离场排除体积
    
    float3 RayPointPos = 2 * (CurPos - 0.5) * LocalObjectBoundsMax - 0.5 * 2;
    RayPointPos = LWCToFloat(TransformLocalPositionToWorld(Parameters,RayPointPos)) - CameraPosWS;
    // 在调用 GetDistanceToNearestSurfaceGlobal 时减去 CameraPosWS
    float SDFDistance = GetDistanceToNearestSurfaceGlobal(RayPointPos);
    if (SDFDistance < 0) break;
    

    accumdens += cursample * StepSize;

    // 为下次循环更新射线位置,沿着相机方向步进
    CurPos -= LightVector * StepSize;
}

// 返回累计结果
return accumdens;

这里添加了获取距离场的代码。在沿光线步进的过程中,一旦距离场小于0,就可以判断射线已到达表面,此时直接结束并退出射线,返回累积的体积密度结果。
效果如下:
在这里插入图片描述
可以看到蒙版确实表达了正确的深度。但意外的,阴影实际上未受影响。
Why?我们做一个快速的实验,制作一个这样简单的材质
在这里插入图片描述
在这里插入图片描述
能看到使用了距离场作为Mask的面片,未投下任何阴影,那么试着直接看看值
在这里插入图片描述
在这里插入图片描述
这就是根本原因,投影和距离场是冲突的

如果不使用距离场,还有什么方法可以获取场景深度呢?那就只能采用代价高昂的2D捕获来实现真实的阴影。

在这里插入图片描述
这是我的测试场景,我通过Actor实现了一种沿光照方向捕获深度的相机,这样就可以拍摄到目标并实现正确的投影,然后使用贴花进行投射。
不过,这种方法对我来说实在过于昂贵,我认为并不值得尝试。这里只是给你提供一个思路,如果你有一个无情的甲方爸爸,你可以考虑使用这种方法。


下一篇是对目前阶段简短的收拾和整理的总结篇。

再然后就开始制作动态编辑体积雾的功能的新篇章咯!
(收拾它↓)
在这里插入图片描述

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

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

相关文章

ubuntu22.04下GStreamer源码编译单步调试

前言 本文会通过介绍在linux平台下的GStreamer的源码编译和单步调试example实例。官网介绍直接通过命令行来安装gstreamer可以参考链接&#xff1a;Installing on Linux。 这种方法安装后&#xff0c;基于gstreamer的程序&#xff0c;单步调试的时候并不会进入到gstreamer源码…

架构师:Nginx 实现负载均衡的技术指南

1、简述 NGINX 是一种高性能的 HTTP 服务器和反向代理服务器,广泛应用于 web 服务器场景中。负载均衡是 NGINX 的重要功能之一,能够将请求分发到多个服务器上,提高应用的可用性和性能。 NGINX 负载均衡的主要策略: 轮询 (Round Robin): 将请求依次分发到后端服务器,每个…

中国移动机器人将投入养老场景;华为与APUS共筑AI医疗多场景应用

AgeTech News 一周行业大事件 华为与APUS合作&#xff0c;共筑AI医疗多场景应用 中国移动展出人形机器人&#xff0c;预计投入养老等场景 作为科技与奥富能签约&#xff0c;共拓智能适老化改造领域 天与养老与香港科技园&#xff0c;共探智慧养老新模式 中山大学合作中国…

Web应用程序的设计与前端开发

我们的客户专门从事自动化系统的开发和支持&#xff0c;用于分析、报告、规划和其他业务任务&#xff0c;以及集成外部产品。 任务 我们的客户开始开发一个用于企业业务分析的web应用程序。他们自己处理后端&#xff0c;而我们的团队负责界面和前端。界面不仅在视觉上具有吸引…

论文阅读(十六):Deep Residual Learning for Image Recognition

文章目录 1.介绍2.基本原理3.两种残差块4.网络结构 论文&#xff1a;Deep Residual Learning for Image Recognition   论文链接&#xff1a;Deep Residual Learning for Image Recognition   代码链接&#xff1a;Github 1.介绍 在ResNet网络提出之前&#xff0c;传统的卷…

医疗领域的RAG技术:如何通过知识图谱提升准确性

在医学领域&#xff0c;准确的信息检索和处理至关重要。随着大型语言模型&#xff08;LLMs&#xff09;的兴起&#xff0c;检索增强生成&#xff08;RAG&#xff09;技术在医学信息处理中的应用越来越受到关注。本文将探讨RAG技术在医学领域的应用&#xff0c;特别是如何利用知…

动态规划 - 完全背包问题

文章目录 题目描述题解思路题解代码 题目描述 题解思路 完全背包问题和01背包问题不同的地方就是完全背包问题中每个物品能选无数次&#xff0c;而01背包问题中每个物品最多只能选择一次 如果你还没有学过01背包&#xff0c;请先看这篇博客学习01背包&#xff1a;https://blo…

基于Javaweb的医院挂号预约管理系统

系统展示 用户前台界面 管理员后台界面 医生后台界面 系统背景 在现代社会&#xff0c;随着医疗需求的不断增长&#xff0c;病患挂号成为医院面临的一大挑战。传统的挂号方式不仅耗时耗力&#xff0c;还容易引发混乱和不满。病患需要排队等候&#xff0c;挂号过程繁琐&#xff…

杨氏矩阵(有一个数字矩阵,矩阵的每行从左到右的递增的,矩阵从上到下是递增的请编写一个程序,在这样的矩阵中查找某个数字是否存在)

//杨氏矩阵 //有一个数字矩阵&#xff0c;矩阵的每行从左到右的递增的&#xff0c;矩阵从上到下是递增的 //请编写一个程序&#xff0c;在这样的矩阵中查找某个数字是否存在 // 1 2 3 // 4 5 6 // 7 8 9 #include<stdio.h> int main() {int a[3][3] { 0 };int i 0, j …

【设计一个恒流转恒压用于电池充电管理】2022-01-25

设计一个恒流转恒压用于电池充电管理&#xff0c;恒流定时恒压&#xff0c;看到一个限流稳压电路图。以下参数进行设计限流恒流<5安培稳压恒压12.6伏特&#xff0c;功率管选型NMOS场效应管IRF7403&#xff08;AO4407A&#xff09;&#xff0c;输入电压与输出压差必须大于4.4…

【计算机网络】HTTP报文详解,HTTPS基于HTTP做了哪些改进?(面试经典题)

HTTP协议基本报文格式 在计算机网络中&#xff0c;HTTP&#xff08;超文本传输协议&#xff09;是应用层的一种协议&#xff0c;用于客户端&#xff08;通常是浏览器&#xff09;和服务器之间的通信。HTTP报文分为请求报文和响应报文&#xff0c;以下是它们的基本格式。 1. H…

服务器数据恢复—服务器硬盘指示灯亮黄灯,raid崩溃的数据恢复案例

服务器数据恢复环境&#xff1a; 一台浪潮服务器中有一组由6块SAS硬盘组建的RAID。服务器上划分了1个卷&#xff0c;存放Oracle数据库文件。 服务器故障&检测&#xff1a; 服务器上有两个硬盘指示灯亮黄灯&#xff0c;RAID崩溃&#xff0c;服务器不可用。 将故障服务器中所…

【机器学习】图像识别——计算机视觉在工业自动化中的应用

1. 引言 随着人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;的快速发展&#xff0c;计算机视觉已成为工业自动化中的核心技术之一。图像识别&#xff0c;作为计算机视觉领域的重要分支&#xff0c;能够通过分析和理解图像或视频数据来识别、分类或检…

【LeetCode:349. 两个数组的交集 + 哈希表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

如何使用 Browserless 抓取动态网站?

什么是动态网页&#xff1f; 动态网页是指其内容并非完全直接嵌入在静态HTML中&#xff0c;而是通过服务器端或客户端渲染生成的网页。 它可以根据用户的操作实时显示数据&#xff0c;例如在用户点击按钮或向下滚动页面时加载更多内容&#xff08;如无限滚动&#xff09;。这…

查缺补漏----三次握手与四次挥手

注意事项&#xff1a; ① 如果是和FTP服务器建立连接&#xff0c;那么要建立两个TCP连接。一个是控制连接一个是数据连接。 ② SYN报文段不能携带数据。三次握手的最后一个报文段可以捎带数据&#xff0c;但是如果不携带数据&#xff0c;那么就不消耗序号。 ③ 在断开连接过程中…

线性代数学习

1.标量由只有一个元素的张量表示 import torchx torch.tensor([3,0]) y torch.tensor([2,0])x y, x * y, x / y, x**y 2.可以将向量视为标量值组成的列表 x torch.arange(4) x 3.通过张量的索引访问任一元素 x[3] 4.访问张量长度 len(x) 5.只有一个轴的张量&#xff0c…

Apache Seata Raft模式配置中心

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata Raft模式配置中心 title: Seata Raft模式配置中心 author: 蒋奕晨-清华大学&…

解决在Windows中安装tensorflow2.10无法检测到GPU的问题

解决在Windows中安装tensorflow2.10无法检测到GPU的问题 官方给出的Windows本地安装方式 更新显卡驱动到最新。安装anaconda或miniconda作为python环境的管理工具。创建新的环境tf&#xff1a;conda create --name tf python3.9&#xff0c;然后进入改环境&#xff1a;conda …

汇编验证并跟踪求平均数程序

一.实验目的 在数据段中定义一个5字节数据的数组array&#xff0c;把它们看做有符号数并求它们的平均数&#xff0c;结果保存在avg内存单元。 二.实验代码&#xff08;dosbox&#xff09; ;*************************************************************** assume ds:data,…