建设网站公司专业服务,做电影网站赚钱吗,智能网站价格,什么网站开发客户在上一篇文章中#xff0c;我们已经理顺了实现流程。 接下来#xff0c;我们将在UE5中#xff0c;从头开始一步一步地构建一次流程。 通过这种方法#xff0c;我们可以借助一个熟悉的开发环境#xff0c;使那些对着色器不太熟悉的朋友们更好地理解着色器的工作原理。
这篇…在上一篇文章中我们已经理顺了实现流程。 接下来我们将在UE5中从头开始一步一步地构建一次流程。 通过这种方法我们可以借助一个熟悉的开发环境使那些对着色器不太熟悉的朋友们更好地理解着色器的工作原理。
这篇文章中我们将使用这样一个纹理点此下载 我喜欢先实现着色器因此先用这个贴图作为占位 这是一个体积纹理的二维展开最终我们会用RT纹理来代替这种静态的贴图在runtime用RT进行实时的绘制 创建材质 首先创建材质并将混合模式调整为 半透明 着色模式调整为 无光照
制作基础体积着色器
一、光线步进
采样贴图并通过步进对密度进行累计 首先我们先创建Custom节点并添加输入 Tip 可以复制下方代码然后在UE中通过 输入 右键鼠标进行粘贴 ((InputNameTex),(InputNameXYFrames),(InputNameNumFrames),(InputNameMaxSteps),(InputNameStepSize),(InputNameLocalCamVec),(InputNameCurPos))输入说明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 要注意: 输出类型为float4在这里将Custom节点重命名 输入粘贴为:
((InputNamePlaneAlignment,Input(Expression/Script/Engine.MaterialExpressionFunctionInput/Engine/Transient.MaterialFunction_0:MaterialExpressionFunctionInput_1)),(InputNameMaxSteps,Input(Expression/Script/Engine.MaterialExpressionFunctionInput/Engine/Transient.MaterialFunction_0:MaterialExpressionFunctionInput_0)),(InputNamescenedepth,Input(Expression/Script/Engine.MaterialExpressionSceneDepth/Engine/Transient.MaterialFunction_0:MaterialExpressionSceneDepth_1,Mask1,MaskR1)))代码为:
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 );计算过程上一篇有介绍这里不赘述效果如下 现在我们已经完成了对伪影的修复。可以看到原本四周存在伪影的部分现在呈现出一种“扭曲”效果这是因为当前的 MaxSteps 设定为 16。
为了获得更好的效果可以将 MaxSteps 适当提升到 32 或 64这样仍能保持不错的性能。 如果不进行伪影修复而是通过增加步数的方法力大砖飞来提升细节质量通常需要将 MaxSteps 调至 256 甚至更高才能达到相同的细节水平。 而循环里目前仅仅只有三个步骤后续扩展效果几乎不可能因此这一步伪影处理是必须的 三、相机进入内部
现在相机不可以进入box内部。因为着色器只在模型表面进行渲染
我们可以力大砖飞
我们可以力大砖飞 的通过开启材质双面解决但我们依旧有更好的办法
创建法线向内的Box模型
使用建模模式快速的创建一个法线向内的Box 如果你有轴在中心的法线向内的box模型则可以跳过)
进入建模模式 如果你没找到建模模式你需要开启引擎的插件: 创建Box 选择建模模式选择创建功能选择创建Box枢纽点选择居中在场景中单击放置模型点击接受创建模型
调整法线 保持选中模型选择属性修改选择法线修改勾选翻转法线接受修改
现在将材质放入 可以看到相机进入内部也不会超出模型承载的渲染范围
四、介质光吸收 将输出连入不透明度再加上一个颜色。此时能更好的看到它的密度看上去非常“扁平”。 为此要使用布格-朗伯-比尔定律来更好的计算介质对光的吸收 它的公式在上一篇中有介绍这里不赘述这里我们直接使用自带的函数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)));七、阶梯纹理修复 没有时间庆祝接下来赶到战场的是阶梯问题。深度和缩放一旦被修复图中画圈位置的阶梯状图形就会非常显眼。 这很明显这是由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;到此一个最基本的体积纹理渲染就做好了 在下一篇进行【制作进阶体积着色器】继续为其实现光照等效果