一、图片投影
1、创建材质函数
MF_PS_Style_Shadow
定义 function input。
公开到库(可选)
Shadow代码:
/**
PS图层样式投影效果
@param {UVs} texture coordinate
@param {Texture} texture object
@param {TextureSize} 纹理大小(x, y)
@param {ShadowRGBA} 投影颜色与不透明度
@param {ShadowRotate} 投影角度
@param {ShadowLength} 投影距离
@param {ShadowSize} 投影大小
@param {BorderThreshold} 边界UVs阈值(左, 上, 右, 下)
*/
float4 Shadow(float2 UVs, Texture2D Texture, float2 TextureSize, float4 ShadowRGBA, float ShadowRotate, half ShadowLength, half ShadowSize, float4 BorderThreshold=0.001) {
const float PI = acos(-1);
// 单位像素
float2 TexturePixel = 1 / TextureSize;
// 角度
float Angle = 360 * ShadowRotate;
// 弧度
float Degrees = Angle / 180 * PI;
// 阴影反向方位(单位向量)
float2 Direction = TexturePixel * float2(cos(Degrees), sin(Degrees));
class Function {
Texture2D Texture;
SamplerState TextureSampler;
float4 ShadowRGBA;
float2 Position;
float BorderThresholdLeft;
float BorderThresholdTop;
float BorderThresholdRight;
float BorderThresholdBottom;
float PI;
float2 TexturePixel;
// 阴影颜色
float3 ShadowColor(float3 Color) {
// 如果需要与颜色混合,在此修改返回值,如下式 正片叠底。
// return this.ShadowRGBA.rgb * Color;
return this.ShadowRGBA.rgb;
}
// 混合
float3 Blend(float3 base, float3 blend, float alpha) {
// 如果使用了混合模式,把 blend 按混合公式计算一次,如下式 正片叠底。
// blend = base * blend;
return lerp(base, blend, alpha);
}
// 纹理采样
float4 TextureSample(float2 UVs) {
// 如果需要 alpha 通道反向,在此修改。
return Texture2DSampleLevel(this.Texture, this.TextureSampler, UVs, 0).xyzw;
}
float4 GetShadowRGBA(float2 UVs) {
// 当前像素点 RGBA
float4 TextureRGBA = this.TextureSample(UVs);
// 阴影反向方位 UVs
float2 PositionUVs = UVs + this.Position;
// 阴影反向方位 UVs 超出了 0 - 1 的范围则不计算
if (PositionUVs.x < this.BorderThresholdLeft || PositionUVs.x > this.BorderThresholdRight || PositionUVs.y < this.BorderThresholdTop || PositionUVs.y > this.BorderThresholdBottom) {
return TextureRGBA;
}
// 阴影反向方位像素点RGBA
float4 PositionRGBA = this.TextureSample(PositionUVs);
// 阴影透明度
float ShadowOpacity = PositionRGBA.a * this.ShadowRGBA.a;
if (ShadowOpacity <= 0) {
return TextureRGBA;
}
// 当前像素点混合后的结果色
// this.ShadowRGBA.rgb 为 base 固有色
// TextureRGBA.rgb 为 blend 固有色
// TextureRGBA.a 为 alpha
float3 ShadowBlendColor = this.Blend(this.ShadowColor(PositionRGBA.rgb) * ShadowOpacity, TextureRGBA.rgb, TextureRGBA.a);
// 当前像素点混合后的透明度
float ShadowBlendOpacity = ShadowOpacity + TextureRGBA.a - ShadowOpacity * TextureRGBA.a;
// 当前像素点混合后的RGBA
return float4(ShadowBlendColor / ShadowBlendOpacity, ShadowBlendOpacity);
}
float Calculate1DGaussian(float x) {
return exp(-0.5 * pow(this.PI * x, 2));
}
float4 GetShadowSizeRGBA(float2 UVs, half ShadowSize) {
// 当前像素点 RGBA
float4 TextureRGBA = this.TextureSample(UVs);
// 投影大小范围内像素颜色累加
float4 RGBASum = float4(0, 0, 0, 0);
// 投影大小范围内像素的权重
float WeightSum = 0;
for (half x = -ShadowSize; x <= ShadowSize; x++) {
for (half y = -ShadowSize; y <= ShadowSize; y++) {
float Weight = this.Calculate1DGaussian(x / ShadowSize) * this.Calculate1DGaussian(y / ShadowSize);
WeightSum += Weight;
// 阴影偏移 UVs
float2 OffsetUVs = UVs + float2(x, y) * this.TexturePixel + this.Position;
if (OffsetUVs.x < this.BorderThresholdLeft || OffsetUVs.x > this.BorderThresholdRight || OffsetUVs.y < this.BorderThresholdTop || OffsetUVs.y > this.BorderThresholdBottom) {
continue;
}
// 阴影偏移像素点 RGBA
float4 OffsetRGBA = this.TextureSample(OffsetUVs);
// 阴影透明度
float Opacity = this.ShadowRGBA.a * OffsetRGBA.a;
if (Opacity <= 0) {
continue;
}
// 阴影结果色
float4 RGBA = float4(this.ShadowColor(OffsetRGBA.rgb), Opacity);
RGBASum += RGBA * Weight;
}
}
// 模糊后的 RGBA
float4 BlurRGBA = RGBASum / WeightSum;
// 当前像素点混合后的结果色
float3 Color = this.Blend(BlurRGBA.rgb * BlurRGBA.a, TextureRGBA.rgb, TextureRGBA.a);
// 当前像素点混合后的透明度
float Opacity = BlurRGBA.a + TextureRGBA.a - BlurRGBA.a * TextureRGBA.a;
// 当前像素点混合后的RGBA
return float4(Color / Opacity, Opacity);
}
}; // 注意要加分号
// Function func;
// func.Texture = Texture;
// func.TextureSampler = TextureSampler;
// func.ShadowRGBA = ShadowRGBA;
// func.Position = ShadowLength * Direction;
// func.BorderThresholdLeft = BorderThreshold.x;
// func.BorderThresholdTop = BorderThreshold.y;
// func.BorderThresholdRight = 1 - BorderThreshold.z;
// func.BorderThresholdBottom = 1 - BorderThreshold.w;
// func.PI = PI;
// func.TexturePixel = TexturePixel;
Function func = { Texture, TextureSampler, ShadowRGBA, ShadowLength * Direction, BorderThreshold.x, BorderThreshold.y, 1 - BorderThreshold.z, 1 - BorderThreshold.w, PI, TexturePixel };
if (ShadowSize < 1) {
return func.GetShadowRGBA(UVs);
}
return func.GetShadowSizeRGBA(UVs, round(ShadowSize));
}
2、创建材质
M_PS_Style_Shadow
修改 材质域 和 混合模式。
调用之前创建的材质函数。
材质函数调用方式。
如果之前没有公开到库,则使用 material function call 调用
3、效果预览
二、文字投影
1、添加 Retainer Box
设置尺寸,并指定效果材质。
2、添加 TextBlock
3、设置材质实例参数
由于字体所在画布大小为 300 * 100,这里要设置同步。
4、效果预览(需要运行游戏才能看到效果)
ShadowSize 为 0。
ShadowSize 为 10。
ShadowSize 为 20。
需要改颜色就修改 ShadowRGBA。
三、人物角色投影
1、创建渲染目标
命名 RT_Equipment。
用于 UI 贴图,修改参数,这里大小使用1024。
2、创建 Actor
命名 BP_Equipment
添加 场景捕获组件2D。
根据需要设置参数。
添加 骨骼网络体组件。
指定模型与动画。
调整好位置。
3、放入场景。
将 BP_Equipment 放入 Level,也可以在运行时动态生成。
4、修改材质
由于 A 通道需要反向,所以之前的 Shadow 代码做如下改动:
// 纹理采样
float4 TextureSample(float2 UVs) {
// 如果需要 alpha 通道反向,在此修改。
float4 PixelRGBA = Texture2DSampleLevel(this.Texture, this.TextureSampler, UVs, 0).xyzw;
return float4(PixelRGBA.rgb, 1 - PixelRGBA.a);
}
5、效果预览
四、使用混合模式
假定使用 正片叠底,代码做如下改动。
// 阴影颜色
float3 ShadowColor(float3 Color) {
// 如果需要与颜色混合,在此修改返回值,如下式 正片叠底。
return this.ShadowRGBA.rgb * Color;
}
也可以换成其他的混合模式,具体算法可以参考文章 《HLSL实现PS混合模式》。