UE4---RayMarch史莱姆

image-20210103023008823

思路

1.利用RayMarch先绘制个球

2.处理材质与其他模型交界过硬问题

3.构建法线

4.平滑合并

绘制球

同样是首先传入CameraDir和WorldPos,MAX_Step,构建距离场,渲染距离场,屏幕射线与物体进行求交

float4 finalCol = 0.0f;
float3 pos = WorldPos;

for (int i = 0; i<MAX_Step; i++)
{
    //球体距离场
    float distance = length(pos) - 50.0f;

    //判断距离是否足够接近
    if (distance<0.01f)
    {
        finalCol = 1.0f;
        break;
    }

    //累加距离
    pos += CameraDir * distance;
}

return finalCol;

image-20210102155523945

现在还有一个问题就是,我们RayMarch绘制得到的球体不会跟随物体的移动而移动

GIF 2021-1-2 15-57-15

解决方法:我们添加一个ObjectPostion的偏移即可,ObjectPostion其实就是场景物体的中心轴位置

image-20210102155925606

image-20210102155939867

只要把ObjectPostion节点传入usf进行处理即可

float distance = length(pos - ObjectPos) - 50.0f;

image-20210102160234169

GIF 2021-1-2 16-01-22

处理材质与其他模型交界过硬问题

可以看到交界非常的硬,我们解决这个问题一般使用SceneDepth解决

image-20210102162741467

在for循环中添加以下代码

    //处理与其他模型交界过硬问题
    if (SceneDepth < length(pos - CameraPos))
    {
        break;
    }

image-20210102162817759

物体在屏幕中间时显示正常,当物体移动到屏幕边缘时,球体会出现严重的变形

GIF 2021-1-2 20-14-37

出现问题的原因:当我们摄像机往左移动时候,场景深度并没有改变都是一样的,但是我们的length(pos - CameraPos)由于我们摄像机的移动是变大了,所以造成畸变变小。

image-20210102201739300

相对于摄像机平面同一条直线上的场景深度是一样,这可以我们可以看渲染管线的变换可以知道,当然我这里选择用材质来证明,可以看到同一直线上的颜色一致的。

image-20210102202259011

image-20210102202322138

解决方法:用一个trick对SceneDepth进行弥补,其实就是用CameraVector和CameraDirectionVector进行弥补

CameraVector像素指向Camera的方向

img

CameraDirectionVector摄像机面对的方向

image-20210102204629558

通过点积即可完成对边缘SceneDepth的弥补操作

image-20210102204843678

最后结果,可以看出畸变情况好了很多

GIF 2021-1-2 20-49-16

构建法线

思路和上一篇文章一样,代码如下,注意定义多个函数需要把函数放入结构体内

struct FunctionStruct
{
    //构建球体距离场
    float sphere(float3 pos)
    {
        return length(pos) - 50.0f;

    }

    //计算法线方向
    float3 CalNormal(float3 pos)
    {
        float D_value = 0.01f;
        return normalize(float3(
        sphere(float3(pos.x + D_value, pos.y, pos.z)) - sphere(float3(pos.x - D_value, pos.y, pos.z)),
        sphere(float3(pos.x, pos.y + D_value, pos.z)) - sphere(float3(pos.x, pos.y - D_value, pos.z)),
        sphere(float3(pos.x, pos.y, pos.z + D_value)) - sphere(float3(pos.x, pos.y, pos.z - D_value))
        ));

    }
};

FunctionStruct FS;
float hit = 0.0f;
float3 pos = WorldPos;
float3 normal = 0.0f;

for (int i = 0; i<MAX_Step; i++)
{

    //处理与其他模型交界过硬问题
    if (SceneDepth < length(pos - CameraPos))
    {
        break;
    }

    //球体距离场
    float distance = FS.sphere(pos - ObjectPos);

    //判断距离是否足够接近
    if (distance<0.01f)
    {
        hit = 1.0f;
        normal = FS.CalNormal(pos - ObjectPos);
        break;
    }


    //累加距离
    pos += CameraDir * distance;
}

return float4(normal,hit);

image-20210102211839166

获取法线之后要传入UE4材质就可以享受UE4的光照效果,需要着色模型为默认光照,光照模式为体积方向,取消勾选切线空间法线

image-20210102212252727

平滑合并

平滑函数

float smin( float a, float b, float k){
    float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0);
    return lerp( b, a, h) - k*h*(1.0-h);
}

这里就有一个问题,平滑函数需要a,b两个距离场参数,但是我们只有到球体的距离场,那怎么获取b参数呢?

原视频作者给出了两种方案。

方案1(不太明白)

利用SceneDepth - length(pos - CameraPos)得到b参数,然后进行平滑合并

    float a = FS.sphere(pos - ObjectPos);
    float b = SceneDepth - length(pos - CameraPos);
    float distance = FS.smin(a,b,Smooth);

如果我们将合并到的distance直接累加到pos,虽然球体与其他模型边缘会平滑,但是整体的效果会是错误的,用于存放球体的模型都会被渲染出来

image-20210103014640415

因为得到的distance只有接近边缘时才会正确,所以我们不以distance进行累加,而是让pos每次循环沿屏幕射线前进1(感觉非常耗性能)

for (int i = 0; i<MAX_Step; i++)
{

    //处理与其他模型交界过硬问题
    if (SceneDepth < length(pos - CameraPos))
    {
        break;
    }

    //球体距离场
    float a = FS.sphere(pos - ObjectPos);
    float b = SceneDepth - length(pos - CameraPos);
    float distance = FS.smin(a,b,Smooth);

    //判断距离是否足够接近
    if (distance<0.01f)
    {
        hit = 1.0f;
        normal = FS.CalNormal(pos - ObjectPos);
        break;
    }


    //累加距离
    pos += CameraDir * 1;
}

这样就得到一个非常漂亮的史莱姆了

image-20210103015700192

创建材质实例更容易进行调节

image-20210103015759196

方案二

UE4是支持生成网格距离场的,项目设置—-distance

image-20210103015956546

可以看到为正方体和存放球体的模型以及平面都生成了距离场,但是分辨率不高

image-20210103020419152

这里我们没必要为存放球体模型生成距离场,取消勾选距离场光照

image-20210103020518820

在世界场景设置中设置距离场精细度

image-20210103020723548

通过DistanceToNeareastSurface节点获取最近平面距离

image-20210103020906402

如果我们需要在自己的usf中使用该节点,可以查看HLSL代码查看函数名称,如果带有global则全局可用

for (int i = 0; i<MAX_Step; i++)
{

    //处理与其他模型交界过硬问题
    if (SceneDepth < length(pos - CameraPos))
    {
        break;
    }

    //球体距离场
    float a = FS.sphere(pos - ObjectPos);
    //float b = SceneDepth - length(pos - CameraPos);
    float b = GetDistanceToNearestSurfaceGlobal(pos);
    float distance = FS.smin(a,b,Smooth);

    //判断距离是否足够接近
    if (distance<Stop)
    {
        hit = 1.0f;
        normal = FS.CalNormal(pos - ObjectPos);
        break;
    }


    //累加距离
    pos += CameraDir * 1;
}

得到漂亮的小球

image-20210103022635893

问题

感觉RayMarching太耗性能了,而且近看有因为精细度造成的锯齿问题,还有有很好的数学能力才会绘制出好看的场景

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2021 Opda
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信