UE5实现PS图层样式投影效果

一、图片投影

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混合模式》

效果预览 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值