UE HLSL案例

UE HLSL案例

使用正弦函数创建3D球体

代码

// 定义射线原点 = 视图方向(连接到相机的矢量) - 世界位置(虚幻中的绝对位置)
float3 rayOrigin = viewDir - worldPos;
float3 rayStep = viewDir * 1;
// 构建一个球的点太少会出问题
for (int i = 0; i < 256; i++)
{
    // (射线原点 - 物体中点)的长度 - 球的半径
    float dist = length(rayOrigin - sphereCenter) - sphereRadius;

    if (dist < 0.01)
    {
        // 输出的透明度为1,颜色为红色
        OpacityMask = 1;
        return float3(1.0, 0.0, 0.0);
    }

    rayOrigin += rayStep;
}
// 输出的透明度为0,颜色为黑色
OpacityMask = 0;
return float3(0.0, 0.0, 0.0);

场景中的材质,混合模式设置为已遮罩着色模型设置为无光照
材质中给出传参及最终效果如下

在这里插入图片描述

添加阴影

这里测试需要添加了一个参数sphereColor,球的颜色。

在添加漫反射着色时需要添加一个参数,光源位置lightPos

// 定义射线原点 = 视图方向(连接到相机的矢量) - 世界位置(虚幻中的绝对位置)
float3 rayOrigin = 1 - (viewDir - worldPos);
float3 rayStep = viewDir * -1;
// 将传入的光源位置做归一化
float3 lightDirection = normalize(lightPos);

// 构建一个球的点太少会出问题
for (int i = 0; i < 256; i++)
{
    // (射线原点 - 物体中点)的长度 - 球的半径
    float dist = length(rayOrigin - sphereCenter) - sphereRadius;

    if (dist < 0.01)
    {
        // 当视线与球体相交时,该表面的法线 = 归一化(射线原点-球体中心)
        float3 normal = normalize(rayOrigin - sphereCenter);
        // 计算表面反射的光线(漫反射)
        float diffuse = max(dot(normal, lightDirection), 0);
        // 输出的透明度为1,颜色为红色
        OpacityMask = 1;
        return sphereColor * diffuse;
    }

    rayOrigin += rayStep;
}
// 输出的透明度为0,颜色为黑色
OpacityMask = 0;
return float3(0.0, 0.0, 0.0);

效果如下:

在这里插入图片描述

添加镜面高光
// 定义射线起点 = 其中viewDir是连接到相机的向量,worldPos是物体在虚幻引擎中的绝对位置(一个从物体指向相机的射线)
float3 rayOrigin = 1 - (viewDir - worldPos);
// 定义了沿着射线方向的步进值,这里乘以-1表示沿着视线方向的反方向。
float3 rayStep = viewDir * -1;
// 将传入的光源位置做归一化,得到光线的方向向量
float3 lightDirection = normalize(lightPos);

// 构建一个球的点太少会出问题
for (int i = 0; i < 256; i++)
{
    // (射线原点 - 物体中点)的长度 - 球的半径 ----->射线原点到物体中心的距离减去球的半径,用来判断射线是否与球体相交。
    float dist = length(rayOrigin - sphereCenter) - sphereRadius;

    // 如果计算得到的距离小于阈值0.01,表示光线与球体相交。
    if (dist < 0.01)
    {
        // 计算相交点的法线,这里使用了相交点到球体中心的向量作为法线,并对其进行归一化  该表面的法线 = 归一化(射线原点-球体中心)
        float3 normal = normalize(rayOrigin - sphereCenter);
        // 计算漫反射,即光线与法线的夹角,然后取其与光线方向的点积并与0取最大值
        float diffuse = max(dot(normal, lightDirection), 0);\
        
        float3 reflection = reflect(lightDirection, normal);
        float3 viewDirection = normalize(-rayOrigin - worldPos);
        float specular = pow(max(dot(reflection, viewDirection), 0), 16);

        // 输出的透明度为1,颜色为红色
        OpacityMask = 1;
        return ((sphereColor * diffuse) + (specular * float3(1.0, 1.0, 1.0)));
    }

    rayOrigin += rayStep;
}
// 输出的透明度为0,颜色为黑色
OpacityMask = 0;
return float3(0.0, 0.0, 0.0);

核心在于

// 计算反射光线的方向向量
float3 reflection = reflect(lightDirection, normal);
// 计算观察方向,即相机位置指向相交点的方向向量
float3 viewDirection = normalize(-rayOrigin - worldPos);
// 计算镜面反射,即反射光线与观察方向的夹角,并取其与0的最大值后进行16次幂运算, 16次幂运算表示镜面反射的强度
float specular = pow(max(dot(reflection, viewDirection), 0), 16);
自定义动效

https://www.youtube.com/watch?v=-8Z99NZ5Hs&list=PLoHLpVCC9RmMMmW5eP1aAyJrTjxd46rx&index=9

构建一个表面波动的球体
// 定义射线起点 = 其中viewDir是连接到相机的向量,worldPos是物体在虚幻引擎中的绝对位置(一个从物体指向相机的射线)
float3 rayOrigin = 1 - (viewDir - worldPos);
// 定义了沿着射线方向的步进值,这里乘以-1表示沿着视线方向的反方向。
float3 rayStep = viewDir * -1;
// 将传入的光源位置做归一化,得到光线的方向向量
float3 lightDirection = normalize(lightPos);

// 构建一个球的点太少会出问题
for (int i = 0; i < 256; i++)
{
    // 这行代码计算了球体表面的位移。这个位移使用了射线起点的各个坐标分量,结合时间变化,通过正弦函数来模拟球体表面的波动。
    // sphereCenter是球体的中心位置,displace是位移值。
    float displace = sphereCenter + (sin(rayOrigin.x * sin(time) / 3) +
                                     sin(rayOrigin.y * sin(time) / 3) +
                                     sin(rayOrigin.z * sin(time) / 3));
    // (射线原点 - 物体中点)的长度 - 球的半径 ----->射线原点到物体中心的距离减去球的半径,用来判断射线是否与球体相交。
    float dist = length(rayOrigin - displace) - sphereRadius;

    // 如果计算得到的距离小于阈值0.01,表示光线与球体相交。
    if (dist < 0.01)
    {
        // 计算相交点的法线,这里使用了相交点到球体中心的向量作为法线,并对其进行归一化  该表面的法线 = 归一化(射线原点-球体中心)
        float3 normal = normalize(rayOrigin - displace);
        // 计算漫反射,即光线与法线的夹角,然后取其与光线方向的点积并与0取最大值
        float diffuse = max(dot(normal, lightDirection), 0.1);
        // 计算反射光线的方向向量
        float3 reflection = reflect(lightDirection, normal);
        // 计算观察方向,即相机位置指向相交点的方向向量
        float3 viewDirection = normalize(-rayOrigin - worldPos);
        // 计算镜面反射,即反射光线与观察方向的夹角,并取其与0的最大值后进行16次幂运算, 16次幂运算表示镜面反射的强度
        float specular = pow(max(dot(reflection, viewDirection), 0), 16);

        // 输出的透明度为1,颜色为红色
        OpacityMask = 1;
        return ((sphereColor * diffuse) + (specular * float3(1.0, 1.0, 1.0)));
    }

    rayOrigin += rayStep;
}
// 输出的透明度为0,颜色为黑色
OpacityMask = 0;
return float3(0.0, 0.0, 0.0);

核心在于

// 这行代码计算了球体表面的位移。这个位移使用了射线起点的各个坐标分量,结合时间变化,通过正弦函数来模拟球体表面的波动。
// sphereCenter是球体的中心位置,displace是位移值。
float displace = sphereCenter + (sin(rayOrigin.x * sin(time) / 3) +
                                 sin(rayOrigin.y * sin(time) / 3) +
                                 sin(rayOrigin.z * sin(time) / 3));

将代码中所有的sphereCenter用displace代替后,相当于原本sphereCenter是(0,0,0),现在构建每个点时都加了个偏移值。

效果如下

在这里插入图片描述

构建一个甜甜圈形状

代码

// 定义射线起点 = 其中viewDir是连接到相机的向量,worldPos是物体在虚幻引擎中的绝对位置(一个从物体指向相机的射线)
float3 rayOrigin = 1 - (viewDir - worldPos);
// 定义了沿着射线方向的步进值,这里乘以-1表示沿着视线方向的反方向。
float3 rayStep = viewDir * -1;
// 将传入的光源位置做归一化,得到光线的方向向量
float3 lightDirection = normalize(lightPos);

struct sdfShapes
{
    // 构建一个甜甜圈的形状(p表示位置的三维向量、size甜甜圈的大小、cutout切口大小)
    float donut(float3 p, float size, float cutout)
    {
        // 计算了点p到原点在xz平面上的距离,然后减去甜甜圈的大小 size,这样得到的q表示点p到甜甜圈环的距离
        float2 q = float2(length(p.xz) - size, p.y);
        // 使用length(q)计算了点p到甜甜圈环的距离,并减去了切口大小cutout,得到的结果即为甜甜圈形状的有符号距离场值
        return length(q) - cutout;
    }
};
sdfShapes sdf;
// 构建一个球的点太少会出问题
for (int i = 0; i < 256; i++)
{
    float dist = sdf.donut(rayOrigin, 50, 25);

    // 如果计算得到的距离小于阈值0.01,表示光线与球体相交。
    if (dist < 0.01)
    {
        float eps = 0.001;
        // 计算相交点的法线
        float3 normal = normalize(float3(
            sdf.donut(float3(rayOrigin.x + eps, rayOrigin.y, rayOrigin.z), 50, 25) - sdf.donut(float3(rayOrigin.x - eps, rayOrigin.y, rayOrigin.z), 50, 25),
            sdf.donut(float3(rayOrigin.x, rayOrigin.y + eps, rayOrigin.z), 50, 25) - sdf.donut(float3(rayOrigin.x, rayOrigin.y - eps, rayOrigin.z), 50, 25),
            sdf.donut(float3(rayOrigin.x, rayOrigin.y, rayOrigin.z + eps), 50, 25) - sdf.donut(float3(rayOrigin.x, rayOrigin.y, rayOrigin.z - eps), 50, 25)));
        // 计算漫反射,即光线与法线的夹角,然后取其与光线方向的点积并与0取最大值
        float diffuse = max(dot(normal, lightDirection), 0.05);
        // 计算反射光线的方向向量
        float3 reflection = reflect(lightDirection, normal);
        // 计算观察方向,即相机位置指向相交点的方向向量
        float3 viewDirection = normalize(-rayOrigin - worldPos);
        // 计算镜面反射,即反射光线与观察方向的夹角,并取其与0的最大值后进行16次幂运算, 16次幂运算表示镜面反射的强度
        float specular = pow(max(dot(reflection, viewDirection), 0), 16);

        // 输出的透明度为1,颜色为红色
        OpacityMask = 1;
        return ((sphereColor * diffuse) + (specular * float3(1.0, 1.0, 1.0)));
    }

    rayOrigin += rayStep;
}
// 输出的透明度为0,颜色为黑色
OpacityMask = 0;
return float3(0.0, 0.0, 0.0);
  1. 甜甜圈形状生成

    代码中定义了一个名为 sdfShapes 的结构体,并在其中实现了一个名为 donut 的函数。这个函数接受一个三维向量 p(表示位置)、甜甜圈的大小 size 和切口大小 cutout 作为参数。在函数中,通过计算点 p 到原点在xz平面上的距离,然后减去甜甜圈的大小 size,得到的 q 表示点 p 到甜甜圈环的距离。最后,通过计算 length(q) - cutout,得到甜甜圈形状的有符号距离场值。

  2. 法线计算

    在光线与甜甜圈相交时,代码通过在相交点附近采样 SDF 的值,并计算其差分来近似计算法线。具体来说,它使用了三个采样点,分别在 x、y、z 方向上增加了一个小的偏移量 eps,然后计算了这些采样点处的 SDF 值。接着,通过计算这些差分值构造了一个近似的法线向量,并对其进行归一化。

    这种方法是一种近似,通常用于光线追踪中的法线计算,尤其是对于复杂的几何形状,它可以提供一个合理的法线估计。

  3. 光照计算

    计算了法线后,代码继续计算了漫反射和镜面反射光的强度,这些值会影响最终的颜色输出。漫反射光的强度取决于光线方向和法线之间的夹角,而镜面反射光的强度则取决于光线的反射方向和观察方向之间的夹角。这些计算使用了光线方向、法线、观察方向等向量,并通过点积和幂函数进行计算。

SDF(Signed Distance Function)是一种用于描述几何形状的函数,它返回一个点到几何形状的最近表面的有符号距离。这个有符号距离表示了点相对于几何形状的位置关系:如果点在几何形状内部,SDF 返回的值为负;如果点在几何形状外部,SDF 返回的值为正;如果点在几何形状的表面上,SDF 返回的值为 0。

HLSL修改纹理
接收并显示传入贴图

代码

float4 color = Texture2DSample(texObject, texObjectSampler, uv);
return color;

需要传入texObject,uv,一个是纹理,一个是UV。

效果如下

在这里插入图片描述

修改UV大小

代码

struct texDistort
{
    // 修改纹理比例的函数
    float2 texScale(float2 uv, float scalar)
    {
        float2 texScale = (uv - 0.5) * scalar + 0.5;
        return texScale;
    }
};
texDistort txd;
float4 color = Texture2DSample(texObject, texObjectSampler, txd.texScale(uv, 2.0));
return color;

效果

在这里插入图片描述

旋转uv
// 旋转uv,传入的angle是弧度制
float2 texRotate(float2 uv, float angle)
{
    float2x2 rotationMatrix = float2x2(cos(angle), sin(angle),
                                       -sin(angle), cos(angle));
    return mul(rotationMatrix, uv - 0.5) + 0.5;
}

使用

float4 color = Texture2DSample(texObject, texObjectSampler, txd.texRotate(uv, radians(15.0)));

效果

在这里插入图片描述

扭曲UV

代码

// 扭曲uv
float2 texDistortion(float2 uv, float time)
{
    // 计算了从中心点 (0.5, 0.5) 到当前像素点 uv 的角度,使用 atan2 函数。
    float angle = atan2(uv.y - 0.5, uv.x - 0.5);
    // 计算了当前像素点 uv 到中心点 (0.5, 0.5) 的距离,使用 length 函数
    float radius = length(uv - 0.5);
    // 设置扭曲量。它将距离 radius 与时间 time 结合在一起,使用正弦函数 sin 进行计算。radius 与 time 的线性组合用于调整扭曲的频率和速度。* 4 用于放大扭曲效果
    float distortion = sin(3 * radius + 2 * time) * 4;
    // 计算了主要的扭曲值。它使用当前像素点的角度 angle 和之前计算的扭曲量 distortion 来生成扭曲
    float primDist = sin(6.0 * angle) * distortion;

    // 使用 texRotate 函数来应用扭曲。texRotate 函数将 uv 坐标点和扭曲值作为参数,对纹理进行旋转或扭曲操作,并返回结果
    return texRotate(uv, primDist);
}

效果如下

在这里插入图片描述

HLSL做出深度效果

HLSL使用深度排列纹理做出深度效果

float3 rayStep = viewDir * -1;
float4 inputTex = Texture2DSample(texObject, texObjectSampler, uv);

for (int i = 0; i < 25; i++)
{
    if (inputTex.r > 0.1 && inputTex.g > 0.1 && inputTex.b > 0.1)
    {
        return float3(i, 0, 0);
    }
    uv += rayStep * 0.15;

    inputTex = Texture2DSample(texObject, texObjectSampler, uv.xy);
}
return inputTex;

效果如下

在这里插入图片描述

HLSL做出下雨涟漪效果

https://www.youtube.com/watch?v=I4b1Jj_VIf8&list=PLoHLpVCC9RmMMmW5eP1aAyJrTjxd46rx_&index=14

// 用于累积最终的颜色输出
float4 result = float4(0, 0, 0, 0);
// 代表一个二维点的中心位置
float2 pointCtr = float2(0.5, 0.5);

// 定义波纹的最小半径和最大半径
float radiusMin = 0.05;
float radiusMax = 0.1;
// 定义波纹厚度
float ringThickness = 0.005;
// 定义内部和外部的淡化值
float fadeInner = 0.005;
float fadeOuter = 0.001;
// 定义时间周期,一个波纹从开始到结束要多久
float duration = 1.0;

// 定义了一个用于生成随机数的种子
float2 seed = float2(123.456, 789.012);
// 定义偏移范围
float2 offsetRange = float2(-1, 1);

// 雨滴波纹的数量(循环次数)
float drops = 100;
for (int i = 0; i < drops; i++)
{
    // 更新随机数种子
    seed = frac(seed * 123.456);

    // 生成一个随机偏移量
    float2 randOffset = lerp(offsetRange.x, offsetRange.y, seed);
    // 计算一个周期
    float cycle = duration + frac(randOffset);
    // 计算脉冲值
    float pulse = frac(time / cycle);
    // 计算半径,基于脉冲值来变化
    float radius = radiusMin + pulse * (radiusMax - radiusMin);
    // 计算偏移
    float2 offset = (uv - 0.5) - randOffset;
    // 计算点到中心的距离
    float pointDist = length(offset);

    // 计算半径限制
    float radiusLimit = radiusMin + (seed.y) * (radiusMax - radiusMin);

    // 计算alpha值,根据内部淡化值和距离中心点的距离来决定
    float alpha = saturate(smoothstep(radius - fadeInner,
                                      radius + fadeInner, pointDist));

    // 根据外部淡化值和距离中心点的距离来进一步调整alpha值
    alpha *= saturate(1 - smoothstep(radiusLimit - fadeOuter, radiusLimit + fadeOuter, pointDist));

    // 检查点是否在环的范围内
    if (pointDist >= radius - ringThickness &&
        pointDist <= radius + ringThickness)
    {
        // 将alpha值添加到结果中
        result += alpha;
    }
}

// 确保return在0到1的范围内
return saturate(result);

效果

在这里插入图片描述

HLSL制作描边效果

Sobel边缘检测过滤器:本质上就是遍历图像中的所有像素,对3x3像素网格进行采样,并过滤调水平边缘和竖直边缘
首先需要创建一个材质,材质域设置为后期处理
之后自定义一个custom节点

// Sobel边缘检测过滤器
float2 sobelMatrix[9] = {
    float2(-1, -1), float2( 0, -2), float2( 1, -1),
    float2(-2,  0), float2( 0,  0), float2( 2,  0),
    float2(-1,  1), float2( 0,  2), float2( 1,  1),
};
float intensity = 1.0;
float2 gradients = 0.0;

for(int i = 0; i < 9; ++i) 
{
    int row = i / 3; // 当前行
    int col = i % 3; // 当前列表
    // 获取相邻的像素偏移
    float offsetX = (col - 1) / res.x;
    float offsetY = (row - 1) / res.y;
    // 从渲染的图像中采样像素
    float2 offsetUV = float2(uv.x + offsetX, uv.y + offsetY);
    // 用 sobel 矩阵/滤波器乘以相邻像素的强度
    float sampledIntensity = SceneTextureLookup(offsetUV, 14, false);
    // 用强度控制乘以我们的梯度
    gradients += sobelMatrix[i] * sampledIntensity;
}

gradients *= intensity;

return length(gradients);

在这里插入图片描述
使用方式
在场景中添加一个后期处理体积PostProcessVolume,设置无限范围(未限定)为true,之后设置在渲染功能上添加一个后处理材质
在这里插入图片描述

效果如下:

在这里插入图片描述

要想不是边边的地方正常显示只需要对return做一下判断,如下

if(length(gradients) > 0.90) {
    return length(gradients);
} else {
    return SceneTexture;
}

效果如下
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Addam Holmes

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值