【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第二篇-着色器制作】

在上一篇文章中,我们已经理顺了实现流程。
接下来,我们将在UE5中,从头开始一步一步地构建一次流程。
通过这种方法,我们可以借助一个熟悉的开发环境,使那些对着色器不太熟悉的朋友们更好地理解着色器的工作原理。

这篇文章中,我们将使用这样一个纹理,点此下载

在这里插入图片描述
我喜欢先实现着色器,因此先用这个贴图作为占位
这是一个体积纹理的二维展开,最终我们会用RT纹理来代替这种静态的贴图,在runtime用RT进行实时的绘制

创建材质

在这里插入图片描述
首先创建材质,并将混合模式调整为 半透明 ,着色模式调整为 无光照

制作基础体积着色器

一、光线步进

采样贴图并通过步进对密度进行累计


在这里插入图片描述
首先,我们先创建Custom节点,并添加输入
在这里插入图片描述

Tip:
在这里插入图片描述
可以复制下方代码,然后在UE中,通过 输入 右键鼠标,进行粘贴

((InputName="Tex"),(InputName="XYFrames"),(InputName="NumFrames"),(InputName="MaxSteps"),(InputName="StepSize"),(InputName="LocalCamVec"),(InputName="CurPos"))
输入说明
Tex将要进行采样的2D切片纹理对象
XYFramesfloat2 切片的XY数量
NumFrames切片总数
MaxSteps最大步进数
StepSize步大小
LocalCamVec本地空间相机方向
CurPos采样位置

接下来我们将编写一个 光线步进(Ray Marching) 着色器,当然在这里更准确的称呼应该是Camera Ray。
其基本原理是,让屏幕上的每个像素沿着相机的朝向发射射线。这条射线从起点到终点的过程被称为"步进(Step)"。在每一步中,我们采样纹理,如果纹理中有值,则将其累加,这个累加值称为每一步的密度。达到最大步数后,通过查看这条射线累积了多少密度,就可以确定这个像素的光线穿透程度。
在这里插入图片描述

在代码中粘贴如下代码,对每一步的功能进行了注释

//创建变量,从0开始累加沿相机方向步进过程中的总密度
float accumdens = 0;//使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{// 在当前步进位置进行纹理采样,采样的是 R 通道// PseudoVolumeTexture 函数用于伪体积纹理采样,函数需要的参数在括号内传递float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;// 将当前采样到的密度值累加到总密度中// 乘以步长是为了将采样密度与步进距离相匹配accumdens += cursample * StepSize;// 为下次循环更新射线位置,沿着相机方向步进CurPos += -LocalCamVec * StepSize;
}//返回累计结果
return accumdens;

在这里插入图片描述
创建对应变量输入,并将其连接到输出,就可以查看结果了

有几个新手要注意的点:

  • Tex 输入的是“纹理对象”而不是“纹理采样”
  • 使用TransfromVector函数将CameraVector由场景转换为本地
  • 如果你是较早的UE5版本,Custom的代码要填在"这里"
    在这里插入图片描述
  • 代码中的PseudoVolumeTexture是内建的采样函数,运作原理在第一篇
  • 函数BoundingBoxBased_0-1_UVW 就像他的名字一样,输出bound体积中的每个位置,你可以将RGB连接到输出查看

在这里插入图片描述
在这里插入图片描述
他会基于Bound提供位置


二、修复伪影

当从侧方看去,会发现有伪影

正面侧面
在这里插入图片描述在这里插入图片描述

这里发生的事:
在这里插入图片描述

我们可以力大砖飞!

我们可以力大砖飞 的通过提高MaxSteps解决,但我们有更好的办法

在这里插入图片描述

为了按照上图中的方式进行采样,我们将要使用这个函数:
在这里插入图片描述
他属于Volumetrics插件,如果你开启了这个插件就可以直接使用
在这里插入图片描述

注意:

  • 如果并不想开启这个插件,接下来将制作这个函数
  • 如果你启用了这个插件,也需要将此函数复制一个,因为后续需要对其修改

创建材质函数VolumeBoxIntersect

在这里插入图片描述

为VolumeBoxIntersect函数建立输入和输出
输入①输入说明输出②输出说明
Plane alignment对齐起始位置与平面间隔以减少采样伪影Box Distance-
Steps应该使用多少步进来进行平面对齐。应与步进匹配Intersection Position-
创建Custom函数

重命名为 Ray March Cube Setup
在这里插入图片描述

要注意:

  1. 输出类型为float4
  2. 在这里将Custom节点重命名

输入粘贴为:

((InputName="PlaneAlignment",Input=(Expression="/Script/Engine.MaterialExpressionFunctionInput'/Engine/Transient.MaterialFunction_0:MaterialExpressionFunctionInput_1'")),(InputName="MaxSteps",Input=(Expression="/Script/Engine.MaterialExpressionFunctionInput'/Engine/Transient.MaterialFunction_0:MaterialExpressionFunctionInput_0'")),(InputName="scenedepth",Input=(Expression="/Script/Engine.MaterialExpressionSceneDepth'/Engine/Transient.MaterialFunction_0:MaterialExpressionSceneDepth_1'",Mask=1,MaskR=1)))

代码为:

float localscenedepth = scenedepth;float3 camerafwd = mul(float3(0.00000000,0.00000000,1.00000000),(float3x3)ResolvedView.ViewToTranslatedWorld);float3 depthpos = ((Parameters.CameraVector * localscenedepth) / abs( dot( camerafwd, Parameters.CameraVector ) ) );depthpos = mul(depthpos,  (float3x3)WSDemote(GetWorldToLocal(Parameters))).xyz;depthpos /= 256;localscenedepth = length(depthpos);//0-1
//localscenedepth /= (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2 * scale);
//localscenedepth /= abs( dot( camerafwd, Parameters.CameraVector ) );//bring vectors into local space to support object transforms
float3 localcampos = mul(float4( DFDemote(ResolvedView.WorldCameraOrigin),1.00000000), (WSDemote(GetWorldToLocal(Parameters)))).xyz;
float3 localcamvec = -normalize( mul(Parameters.CameraVector, (float3x3)WSDemote(GetWorldToLocal(Parameters))) );//make camera position 0-1
localcampos = (localcampos / (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2)) + 0.5;float3 invraydir = 1 / localcamvec;float3 firstintersections = (0 - localcampos) * invraydir;
float3 secondintersections = (1 - localcampos) * invraydir;
float3 closest = min(firstintersections, secondintersections);
float3 furthest = max(firstintersections, secondintersections);float t0 = max(closest.x, max(closest.y, closest.z));
float t1 = min(furthest.x, min(furthest.y, furthest.z));float planeoffset = 1-frac( ( t0 - length(localcampos-0.5) ) * MaxSteps );t0 += (planeoffset / MaxSteps) * PlaneAlignment;
t1 = min(t1, localscenedepth);
t0 = max(0, t0);float boxthickness = max(0, t1 - t0);float3 entrypos = localcampos + (max(0,t0) * localcamvec);return float4( entrypos, boxthickness );

计算过程上一篇有介绍,这里不赘述,效果如下

在这里插入图片描述

Done

现在我们已经完成了对伪影的修复。可以看到,原本四周存在伪影的部分现在呈现出一种“扭曲”效果,这是因为当前的 MaxSteps 设定为 16。

为了获得更好的效果,可以将 MaxSteps 适当提升到 32 或 64,这样仍能保持不错的性能。

如果不进行伪影修复,而是通过增加步数的方法(力大砖飞)来提升细节质量,通常需要将 MaxSteps 调至 256 甚至更高,才能达到相同的细节水平。
而循环里目前仅仅只有三个步骤,后续扩展效果几乎不可能,因此这一步伪影处理是必须的

三、相机进入内部

现在,相机不可以进入box内部。因为着色器只在模型表面进行渲染

我们可以力大砖飞!

我们可以力大砖飞 的通过开启材质双面解决,但我们依旧有更好的办法

创建法线向内的Box模型

使用建模模式,快速的创建一个法线向内的Box
(如果你有轴在中心的法线向内的box模型则可以跳过)

进入建模模式

在这里插入图片描述

如果你没找到建模模式,你需要开启引擎的插件:在这里插入图片描述

创建Box

在这里插入图片描述

  1. 选择建模模式
  2. 选择创建功能
  3. 选择创建Box
  4. 枢纽点选择居中
  5. 在场景中单击,放置模型
  6. 点击接受创建模型
调整法线

在这里插入图片描述

  1. 保持选中模型
  2. 选择属性修改
  3. 选择法线修改
  4. 勾选翻转法线
  5. 接受修改

现在将材质放入
在这里插入图片描述
Done
可以看到相机进入"内部"也不会超出模型承载的渲染范围

四、介质光吸收

在这里插入图片描述
将输出连入不透明度,再加上一个颜色。此时能更好的看到它的密度看上去非常“扁平”。
为此要使用布格-朗伯-比尔定律,来更好的计算介质对光的吸收
它的公式在上一篇中有介绍,这里不赘述,这里我们直接使用自带的函数BeersLaw
在这里插入图片描述
它将返回有多少光被材质吸收
在这里插入图片描述

现在看上去好多了。
虽然还没有阴影和光照,但在这之前,我们先去处理另外的问题

五、体积深度

在这里插入图片描述
我们目前还缺乏体积的深度,体积只是被渲染到了模型的内表面 ,通过这个对比可以看到发生了什么:

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

1.禁用深度测试

在这里插入图片描述
现在我们为其重新制作深度
为此进入材质,禁用深度测试,接下来深度由我们说的算

2.制作深度

计算 MaxSteps

BoxDistance是框距离,相当于是射线穿透框的厚度,将其乘MaxSteps对其缩放,使用Floor取整,且使用clamp确保它不会超过限制
在这里插入图片描述

修改 VolumeBoxIntersect

在这里插入图片描述
可以看到当前的深度对的不多
现在要修复这个问题:

在这里插入图片描述
重新进入到 VolumeBoxIntersect 函数的 Custom
在这里插入图片描述
这里有几行被注释掉的内容,大体上是用于将局部场景深度转换到 0-1 范围内

为Custom增加输入 scale

scale = length(TransformLocalVectorToWorld(Parameters,float3( 1.0 , 0.0 , 0.0 )).xyz);

其蓝图也就是:
在这里插入图片描述

Tip:
Q:为什么不直接写进cutom?
A:尽最大可能的把计算过程写为蓝图,而不要在custom内计算
看编译后的HLSL就会知道,UE的材质编译器会对蓝图的内容进行接近疯狂的优化(而custom不会,写什么就是什么)
(后续会用蓝图重制这个custom的大部分内容,但目前先这样,当下还有很多工作没完成)

然后我们注释掉红框,并解除绿框的注释:
在这里插入图片描述
修改后的函数如下:
在这里插入图片描述
修改后的 Ray March Cube Setup 代码为:

float localscenedepth = scenedepth;float3 camerafwd = mul(float3(0.00000000,0.00000000,1.00000000),(float3x3)ResolvedView.ViewToTranslatedWorld);/*
float3 depthpos = ((Parameters.CameraVector * localscenedepth) / abs( dot( camerafwd, Parameters.CameraVector ) ) );depthpos = mul(depthpos,  (float3x3)WSDemote(GetWorldToLocal(Parameters))).xyz;depthpos /= 256;localscenedepth = length(depthpos);
*///0-1
localscenedepth /= (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2 * scale);
localscenedepth /= abs( dot( camerafwd, Parameters.CameraVector ) );//bring vectors into local space to support object transforms
float3 localcampos = mul(float4( DFDemote(ResolvedView.WorldCameraOrigin),1.00000000), (WSDemote(GetWorldToLocal(Parameters)))).xyz;
float3 localcamvec = -normalize( mul(Parameters.CameraVector, (float3x3)WSDemote(GetWorldToLocal(Parameters))) );//make camera position 0-1
localcampos = (localcampos / (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2)) + 0.5;float3 invraydir = 1 / localcamvec;float3 firstintersections = (0 - localcampos) * invraydir;
float3 secondintersections = (1 - localcampos) * invraydir;
float3 closest = min(firstintersections, secondintersections);
float3 furthest = max(firstintersections, secondintersections);float t0 = max(closest.x, max(closest.y, closest.z));
float t1 = min(furthest.x, min(furthest.y, furthest.z));float planeoffset = 1-frac( ( t0 - length(localcampos-0.5) ) * MaxSteps );t0 += (planeoffset / MaxSteps) * PlaneAlignment;
t1 = min(t1, localscenedepth);
t0 = max(0, t0);float boxthickness = max(0, t1 - t0);float3 entrypos = localcampos + (max(0,t0) * localcamvec);return float4( entrypos, boxthickness );

在这里插入图片描述
深度已经正常,一切都好起来了…?
在这里插入图片描述

六、精度修复

当你试图缩放cube,你会发现它破碎了
在这里插入图片描述
在这里插入图片描述
眼看着美好的事物在你的操作下支离破碎,这可太令人崩溃了,究其原因在于
在这里插入图片描述
TransformVector节点的精度不足,那么我们使用Custom手搓一个使用LWC精度的替代节点吧:

  • 创建Custom,命名为TransFromVector_WorldToLocal
  • 添加输入CameraVector
    在这里插入图片描述
  • 代码如下
return normalize(mul(CameraVector,(float3x3)LWCToFloat(GetPrimitiveData(Parameters).WorldToLocal)));

在这里插入图片描述

Done

七、阶梯纹理修复

在这里插入图片描述
没有时间庆祝,接下来赶到战场的是阶梯问题。深度和缩放一旦被修复,图中画圈位置的阶梯状图形就会非常显眼。
这很明显这是由step产生的,因为我们的步(step)是预计算的,因此步与步之间就形成了这种阶梯纹理

我们可以力大砖飞!

我们可以力大砖飞 的通过增加MaxSteps数量,用更小的细分来解决,但总是有更好的办法

实际上我们只需要在进行额外一次采样就可以了
在这里插入图片描述

  • 使用frac取小数作为一小步FinalStepSize输入
  • 在Custom中的for后再进行一次采样

修改后代码如下:

//创建变量,从0开始累加沿相机方向步进过程中的总密度
float accumdens = 0;//使用 MaxSteps 作为最大步数进行循环,每次循环执行以下操作
for (int i = 0; i < MaxSteps; i++)
{// 在当前步进位置进行纹理采样,采样的是 R 通道// PseudoVolumeTexture 函数用于伪体积纹理采样,函数需要的参数在括号内传递float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;// 将当前采样到的密度值累加到总密度中// 乘以步长是为了将采样密度与步进距离相匹配accumdens += cursample * StepSize;// 为下次循环更新射线位置,沿着相机方向步进CurPos += -LocalCamVec * StepSize;
}//修复阶梯 额外在循环后再进行一次小步(FinalStepSize)的采样,累计到密度
CurPos -= -LocalCamVec * StepSize;
CurPos += -LocalCamVec * StepSize * FinalStepSize;
float cursample = PseudoVolumeTexture(Tex, TexSampler, saturate(CurPos), XYFrames, NumFrames).r;
accumdens += cursample * StepSize * FinalStepSize;//返回累计结果
return accumdens;

在这里插入图片描述
Done
到此一个最基本的体积纹理渲染就做好了


在下一篇进行【制作进阶体积着色器】,继续为其实现光照等效果


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

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

相关文章

思科安全网络解决方案

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

Redis数据持久化总结笔记

Redis 是内存数据库&#xff0c;如果不将内存中的数据库状态保存到磁盘&#xff0c;那么一旦服务器进程退出&#xff0c;服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能&#xff01; Redis 提供了 2 个不同形式的持久化方式 RDB&#xff08;Redis DataBase&#…

【python】requests 库 源码解读、参数解读

文章目录 一、基础知识二、Requests库详解2.1 requests 库源码简要解读2.2 参数解读2.3 处理响应2.4 错误处理 一、基础知识 以前写过2篇文章&#xff1a; 计算机网络基础&#xff1a; 【socket】从计算机网络基础到socket编程——Windows && Linux C语言 Python实现…

排序----希尔排序

void ShellSort(int* a, int n) {int gap n;while (gap > 1){// 1保证最后一个gap一定是1// gap > 1时是预排序// gap 1时是插入排序gap gap / 3 1;for (size_t i 0; i < n - gap; i){int end i;int tmp a[end gap];while (end > 0){if (tmp < a[end]){…

英伟达NVIDIA数字IC后端笔试真题(ASIC Physical Design Engineer)

今天小编给大家分享下英伟达NVIDIA近两年数字IC后端笔试真题&#xff08;ASIC Physical Design&#xff09; 请使用OR门和INV反相器来搭建下面所示F逻辑表达式的电路图。 数字IC后端设计如何从零基础快速入门&#xff1f;(内附数字IC后端学习视频&#xff09; 2024届IC秋招兆…

WEB领域是不是黄了还是没黄

进入2024年后&#xff0c;WEB领域大批老表失业&#xff0c;一片哀嚎&#xff0c;个个饿的鬼叫狼嚎&#xff0c;为啥呢&#xff0c;下面是我个人的见解和看法。 中国程序员在应用层的集中 市场需求&#xff1a;中国的互联网行业在过去几年中经历了爆炸性增长&#xff0c;尤其是…

RAG技术全面解析:Langchain4j如何实现智能问答的跨越式进化?

LLM 的知识仅限于其训练数据。如希望使 LLM 了解特定领域的知识或专有数据&#xff0c;可&#xff1a; 使用本节介绍的 RAG使用你的数据对 LLM 进行微调结合使用 RAG 和微调 1 啥是 RAG&#xff1f; RAG 是一种在将提示词发送给 LLM 之前&#xff0c;从你的数据中找到并注入…

Linux-DHCP服务器搭建

环境 服务端&#xff1a;192.168.85.136 客户端&#xff1a;192.168.85.138 1. DHCP工作原理 DHCP动态分配IP地址。 2. DHCP服务器安装 2.1前提准备 # systemctl disable --now firewalld // 关闭firewalld自启动 # setenforce 0 # vim /etc/selinux/config SELINU…

828华为云征文|华为云Flexus云服务器X实例 基于CentOS系统镜像快速部署Laravel开源论坛

最近公司可热闹了&#xff01;大家都在为搭建博客论坛系统忙得不可开交&#xff0c;尤其是在选服务器这件事儿上&#xff0c;那叫一个纠结。 同事 A 说&#xff1a;“咱得选个厉害的服务器&#xff0c;不然这论坛以后卡得跟蜗牛爬似的可咋办&#xff1f;” 同事 B 回应道&#…

【AcWing】【C++】模板之区间和与区间合并

最近在对程序设计算法进行复习&#xff0c;终于复习完了AcWing基础算法课的第一章&#xff0c;在此对第一章最后两个模板区间和与区间合并进行记录与分享。 区间和 题目描述与输入输出样例 题目来自于AcWing 802. 区间和。 思路 从题目描述来说&#xff0c;第一眼看来这是…

Fyne ( go跨平台GUI )中文文档-入门(一)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI )…

镭射限高防外破预警装置-线路防外破可视化监控,安全尽在掌握中

镭射限高防外破预警装置-线路防外破可视化监控&#xff0c;安全尽在掌握中 在城市化浪潮的汹涌推进中&#xff0c;电力如同现代社会的生命之脉&#xff0c;其安全稳定运行直接关系到每一个人的生活质量和社会的整体发展。然而&#xff0c;随着建设的加速&#xff0c;电力设施通…

部署wordpress项目

一、先部署mariadb 二、在远程登录工具上进行登录测试&#xff0c;端口号为30117&#xff0c;用户为 root&#xff0c;密码为123 三、使用测试工具&#xff1a; [rootk8s-master aaa]# kubectl exec -it pods/cluster-test0-58689d5d5d-7c49r -- bash 四、部署wordpress [root…

论文阅读 | 基于流模型和可逆噪声层的鲁棒水印框架(AAAI 2023)

Flow-based Robust Watermarking with Invertible Noise Layer for Black-box DistortionsAAAI, 2023&#xff0c;新加坡国立大学&中国科学技术大学本论文提出一种基于流的鲁棒数字水印框架&#xff0c;该框架采用了可逆噪声层来抵御黑盒失真。 一、问题 基于深度神经网络…

【AI算法岗面试八股面经【超全整理】——NLP】

AI算法岗面试八股面经【超全整理】 概率论【AI算法岗面试八股面经【超全整理】——概率论】信息论【AI算法岗面试八股面经【超全整理】——信息论】机器学习【AI算法岗面试八股面经【超全整理】——机器学习】深度学习【AI算法岗面试八股面经【超全整理】——深度学习】NLP【A…

教师管理系统小程序+ssm论文源码调试讲解

第二章 开发工具及关键技术介绍 2.1 JAVA技术 Java主要采用CORBA技术和安全模型&#xff0c;可以在互联网应用的数据保护。它还提供了对EJB&#xff08;Enterrise JavaBeans&#xff09;的全面支持&#xff0c;java servlet AI&#xff0c;JS&#xff08;java server ages&…

Unity 热更新(HybridCLR+Addressable)-创建Addressable资源

三、创建Addressable资源 创建三个文件夹&#xff0c;放Addressable资源&#xff0c;里面对应放程序集&#xff0c;预制体以及场景 拖拽到Addressable Groups对应组中 其中文件名太长&#xff0c;带着路径&#xff0c;可以简化名字 创建一个脚本&#xff0c;对于这个脚本进行一…

vue3自定义hooks

引言 Vue3引入了组合式API&#xff0c;使得代码逻辑更自由、灵活。其中自定义Hooks能让我们将客服用的逻辑抽离成一个独立的函数&#xff0c;以实现在多个组件中复用的目的。可以简单理解成封装成一个模块&#xff0c;以方便其他地方调用。 实现 自定义hooks useDog impor…

杰发科技——Eclipse环境安装

文件已传到网盘&#xff1a; 1. 安装文件准备 2. 安装Make 默认路径&#xff1a;C:\Program Files (x86)\GnuWin32\bin\ 不复制的话会报错 Error: Program "make" not found in PATH 3. 安装工具链 默认路径&#xff1a;C:\Program Files (x86)\Arm GNU Toolchain…

闯关leetcode——69. Sqrt(x)

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/sqrtx/description/ 内容 Given a non-negative integer x, return the square root of x rounded down to the nearest integer. The returned integer should be non-negative as well. You mu…