卷积描边算法

部署运行你感兴趣的模型镜像

前段时间美术想要一些角色描边的效果,一开始做了外描边,但是美术想要身体内也有描边效果

类似这种描边,如果用外描边是没办法实现的。

这时候就需要能描述内描边的算法。

从实现来看,包括内描边的算法可以做的方式包括:

1.身体分开描边:

也就是衣服一次外描边,身体一次外描边,头发一次外描边。。。。

好处:容易操作,而且效果可以控制。

坏处:drawcall会多很多,相当于每个配件都多一个dc,相当于多了一倍的dc。如果角色多,那就很恐怖了。。。

2.算法描边:

(1)卷积描边:

这种算法是用了边缘检测的方式来实现的,边缘检测的目的是识别数字图像中亮度变化明显的点。

一般的卷积有Sobel, Laplacian, Canny filter等。

Sobel Kernel通常采用两个3x3的矩阵来表示。由于Sobel滤波是检测一个方向上(一阶)的色彩或者亮度的梯度变化,所以需要在水平和垂直方向进行两次卷积。其两个卷积核(Sobel算子 - Sobel Operator)分别如下:

我们分别获得水平和垂直的结果后将其相加

,这样通过判断G是否大于一个阈值,我们就能判断这个像素是否属于边缘。

另一方面,Laplacian算子是一个二阶的微分算子,所以只需一个卷积核就能够检测到边缘,其缺点是对于噪声(Noise)比较敏感,如果图像中有比较多的噪声可能处理出来的结果会有很多噪点。

传统的实现方式就不说了,网上有很多相关的算法。

因为他们都是3x3的矩阵,所以我们运算时需要9次采样。如果我们运行在手机上会法线卡顿明显(gpu运算量加大)

那么如果我们想要一个折中的算法,而且又能体现出内描边的效果,我就放弃了3x3的矩阵,用2x2的矩阵来实现。

卷积计算这里有个特点:9个数值相加后为0,这样才能保证我们的权重总值时1。

那么基于这个特点,我们用2x2的矩阵时就可以参考这个特点了。

算法原理是:

float2x2 sobel= float2x2(
                1, 1,
                1, 1
                );

这里申请了2x2的矩阵,全是1,这个矩阵主要用于采样邻近的四个斜对角的点的像素。然后我们需要再采样一个正常的当前像素乘(-4)的值来平衡整个权重

int forLen = 2;
			for (int m = 0; m < forLen; m++)
				for (int n = 0; n < forLen; n++)
				{
					sobelH += tex2D(_MainTex, i.uv + offsets[m * forLen + n] * adjacentPixel) *laplacianOperator[m][n];
				}

			sobelH += tex2D(_MainTex, i.uv) * -1 * forLen * forLen;

这就是整个shader中最耗时的地方了做了五次采样。

其他都是正常的算法了。

另外想说的是,sobel算子他需要算开根号

我们其实并不需要,只需要算平方后我们把值整体缩小就好了。(或者输入时让美术输入更大的值起步)

整个算法如下:


struct v2f_outline
{
			float2 uv: TEXCOORD0;
			float4 pos : SV_POSITION;
			float3 normal : TEXCOORD1;
};


v2f_outline vert_sobal(appdata v)
		{
			v2f_outline o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
			return o;
		}
float4 fragColor_sobel(v2f_outline i) : COLOR
		{
			half offset = 1;

			float2 offsets[4] = {
				float2(-offset, offset),
				float2(offset, offset),
				float2(-offset, -offset),
				float2(offset, -offset)
			};

			float2x2 sobel = float2x2(
				1, 1,
				1, 1
				);
			float4 sobelH = float4(0, 0, 0, 0);
			float2 adjacentPixel = _MainTex_TexelSize.xy * _AdjacentPixel;
			int forLen = 2;
			for (int m = 0; m < forLen; m++)
				for (int n = 0; n < forLen; n++)
				{
					sobelH += tex2D(_MainTex, i.uv + offsets[m * forLen + n] * adjacentPixel) *sobel[m][n];
				}

			sobelH += tex2D(_MainTex, i.uv) * -1 * forLen * forLen;

			float sobel = sobelH * sobelH;

			float4 sceneColor = tex2D(_MainTex, i.uv);

			float edgeMask = saturate(sobel);
			float3 EdgeMaskColor = float3(edgeMask, edgeMask, edgeMask);

			float3 finalColor = saturate((EdgeMaskColor * _OutlineColor.rgb) + (sceneColor.rgb - EdgeMaskColor));
			return float4(_OutlineColor.rgb, sceneColor.a - 1 + EdgeMaskColor.r);

		}

Pass{
			Name "SIMPLE_TRANSPARENT"
			Tags{"LightMode" = "LightweightForward"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			ENDCG
		}

最后看看效果

 

另外,关于锯齿可以通过msaa或做模糊来处理

 

如果想要描边选择区域可以控制,可以用_CameraDepthTexture获取深度信息,然后根据不同的深度显示不同的数据

 

 

您可能感兴趣的与本文相关的镜像

Wan2.2-I2V-A14B

Wan2.2-I2V-A14B

图生视频
Wan2.2

Wan2.2是由通义万相开源高效文本到视频生成模型,是有​50亿参数的轻量级视频生成模型,专为快速内容创作优化。支持480P视频生成,具备优秀的时序连贯性和运动推理能力

<think>我们正在处理一个关于在Unreal Engine中实现轮廓描边效果的问题。根据引用[1]的内容,我们可以知道实现轮廓描边效果的基本思路: 1. 使用后期处理材质(Post Process Material)来实现。 2. 利用CustomDepth(自定义深度)来获取物体轮廓信息。 3. 通过拉普拉斯边缘检测算法(或类似卷积核)检测边缘。 4. 将检测到的边缘用自定义颜色描边,非边缘区域显示场景原色。 具体步骤(基于引用[1]的描述): - 创建一个材质(Material),并设置为后期处理材质(Material Domain: Post Process)。 - 在材质中,我们需要使用SceneTexture节点获取CustomDepth(自定义深度)和场景颜色(SceneColor)。 - 实现一个卷积核(例如拉普拉斯算子)来检测边缘。具体做法是:对当前像素周围的像素(上下左右)进行采样,然后用中心像素值乘以4再减去上下左右四个像素值的和,得到一个差值,这个差值可以表示边缘的强度。 - 将边缘强度限制在0-1之间(使用Clamp节点)。 - 使用线性插值(Lerp)节点,将边缘部分替换为自定义颜色(乘以一个强度标量),非边缘部分使用场景原色。 现在,我们按照这个思路来构建材质蓝图。 步骤详解: 1. 创建材质: - 新建一个材质,将材质域(Material Domain)设置为“后期处理(Post Process)”。 2. 获取CustomDepth: - 使用SceneTexture节点,选择“CustomDepth”作为纹理类型。 3. 边缘检测: - 我们需要对当前像素周围的像素进行采样。在材质编辑器中,可以使用“Custom”节点(即自定义表达式)或者利用现有的节点来构建一个卷积核。 - 拉普拉斯算子的一种常见形式是: ``` 0 -1 0 -1 4 -1 0 -1 0 ``` 即中心点乘以4,减去上下左右四个点的值(每个点乘以1)。 - 在材质中,我们可以通过偏移当前纹理坐标来采样相邻像素。偏移量可以通过像素大小(Pixel Size)来计算。使用SceneTextureLookup节点时,我们可以通过提供UV偏移来实现。 4. 计算边缘强度: - 中心像素值:SceneTexture:CustomDepth 在当前位置的采样值。 - 上下左右四个像素的采样值:通过当前UV加上偏移量(例如,左:(-1,0) * 偏移量,右:(1,0) * 偏移量,上:(0,1) * 偏移量,下:(0,-1) * 偏移量)来采样。 - 计算:中心值 * 4 - (左+右+上+下) - 将结果取绝对值(因为边缘可能是从亮到暗或从暗到亮,但我们都认为是边缘),然后使用Clamp节点限制在0到1之间。 5. 颜色混合: - 使用Lerp节点,将边缘强度作为Alpha。输入A为场景原色(SceneColor),输入B为自定义的描边颜色(可以乘以一个标量Power来控制强度)。 - 注意:当边缘强度为0时,输出场景原色;当边缘强度为1时,输出描边颜色。 6. 控制参数: - 可以创建标量参数(Scalar Parameter)来控制描边的宽度(即偏移量,影响边缘检测的灵敏度)和描边颜色的强度(Power)。 具体材质节点图(文字描述): 1. 创建参数: - Width(标量):控制边缘检测的偏移量(即相邻像素的采样距离,值越大,描边越粗)。 - OutlineColor(颜色):描边颜色。 - Power(标量):描边颜色强度。 2. 获取像素大小: - 使用“PixelSize”节点(在UE4中,可以通过“ScreenSize”节点计算,但更简单的方法是使用“SceneTexture:ScreenAlignedPosition”或直接使用“ScreenPosition”节点来获取像素大小)。不过,我们可以直接用一个常数除以其分辨率,但更常用的方法是使用“ViewSize”和“InvViewSize”节点。实际上,我们可以通过一个简单的计算:偏移量 = Width / 屏幕宽度(或高度)。但为了方便,我们可以使用“GetViewportSize”节点获取屏幕分辨率,然后计算每个像素的大小(1/分辨率)。然后乘以Width参数,得到实际的偏移向量。 3. 采样相邻像素: - 当前UV:使用TexCoord节点(表示当前屏幕UV)。 - 计算偏移向量:Offset = (Width / ViewSize.X, Width / ViewSize.Y) ?实际上,我们可以用ViewSize的倒数乘以Width来得到每个方向上的偏移量。更准确地说,偏移向量应该是 (Width * 像素宽度, Width * 像素高度)。像素宽度 = 1.0 / ViewSize.X,像素高度 = 1.0 / ViewSize.Y。 - 所以,我们可以这样构建偏移向量:Offset = (Width * (1.0/ViewSize.X), Width * (1.0/ViewSize.Y)) 4. 采样: - 中心点:SceneTexture:CustomDepth 在UV处采样。 - 左:UV + (-1,0)*Offset - 右:UV + (1,0)*Offset - 上:UV + (0,1)*Offset (注意:在屏幕空间中,V坐标向下为正,所以向上是V减小,但通常我们使用(0,1)偏移表示向下,所以这里要注意方向。在UE4中,屏幕空间UV的原点(0,0)在左上角,所以向下是V增加。因此,上方向应该是(0,-1)*Offset,下方向是(0,1)*Offset。但引用[1]中并没有详细说明方向,我们按照常规:上下左右分别对应:上(0,-1)、下(0,1)、左(-1,0)、右(1,0)) - 注意:由于CustomDepth的值是非线性(通常存储的是深度值),我们需要确保在同一个物体上的深度值相近,不同物体的深度值有跳变。因此,边缘检测会发生在深度跳变的地方。 5. 计算边缘强度: - 中心值 * 4 - (左+右+上+下) - 取绝对值(Abs)后,可以乘以一个系数来调整灵敏度,然后使用Clamp(0,1)将其限制在0-1之间。 6. 混合颜色: - 最终颜色 = Lerp(SceneColor, OutlineColor * Power, EdgeIntensity) 注意:由于边缘检测可能会产生多条边缘线,或者边缘线较粗,我们可以通过调整Width参数来控制描边的粗细,通过调整边缘检测的公式(比如使用更大的卷积核)来改善效果。 在UE4中实现的具体步骤: 1. 创建后期处理材质: - 新建材质,选择材质域为“Post Process”。 - 打开材质编辑器。 2. 添加参数: - 在材质图表中,右键点击,搜索“ScalarParameter”,创建两个标量参数,分别命名为“Width”(默认值1.0)和“Power”(默认值1.0)。 - 创建一个颜色参数,命名为“OutlineColor”(默认白色)。 3. 获取屏幕尺寸: - 添加一个“GetViewSize”节点(在UE4中,这个节点在“Utility”下,或者直接搜索)。这个节点输出当前视图的尺寸(宽度和高度)。 - 计算像素大小:添加一个“Divide”节点,将(1,1)向量除以ViewSize,得到每个像素的大小(PixelSize)。注意:这里我们使用一个“Append”节点将两个除法合并?实际上,我们可以分别计算: PixelWidth = 1.0 / ViewSize.X PixelHeight = 1.0 / ViewSize.Y - 然后,用Width参数乘以这个像素大小,得到偏移向量(OffsetVector): OffsetVector = (Width * PixelWidth, Width * PixelHeight) 我们可以使用一个“Multiply”节点,将Width与一个二维向量(PixelWidth, PixelHeight)相乘。 4. 采样CustomDepth: - 添加一个“SceneTexture”节点,选择“CustomDepth”作为纹理类型。 - 复制这个节点,我们将需要采样5次(中心、左、右、上、下)。 - 对于中心采样:直接连接到SceneTexture节点,UV使用默认的屏幕UV(即“TexCoord”节点,但通常我们使用“ScreenPosition”节点来获取,不过SceneTexture节点默认使用屏幕UV)。 - 对于其他四个方向的采样,我们使用偏移后的UV: UV_Left = UV + (-1,0) * OffsetVector UV_Right = UV + (1,0) * OffsetVector UV_Up = UV + (0,-1) * OffsetVector // 注意:向上是V减小,所以是负 UV_Down = UV + (0,1) * OffsetVector 5. 计算拉普拉斯值: - 中心值:Center = SceneTexture:CustomDepth采样(UV) - 左:Left = SceneTexture:CustomDepth采样(UV_Left) - 右:Right = SceneTexture:CustomDepth采样(UV_Right) - 上:Up = SceneTexture:CustomDepth采样(UV_Up) - 下:Down = SceneTexture:CustomDepth采样(UV_Down) - 计算:Laplacian = 4*Center - (Left + Right + Up + Down) 6. 处理边缘强度: - 取绝对值:Abs(Laplacian) - 为了控制灵敏度,我们可以乘以一个系数(比如0.1)然后限制在0-1之间。但引用[1]中直接使用了Clamp(0,1)。但是深度值的差异可能很大,所以我们可以先乘以一个小的系数(比如0.01)然后再Clamp。或者使用一个参数来控制灵敏度。 - 这里我们简化处理:直接使用一个“Clamp”节点(最小0,最大1),将绝对值结果限制在0-1之间。但这样可能不够灵敏,所以我们可以先乘以一个标量(比如1000)再Clamp,因为深度值通常很小(在0到几万之间)。但更好的做法是使用一个参数来控制灵敏度,或者使用指数函数等。这里我们先按照引用[1]的方法:直接Clamp(0,1)。 7. 获取场景颜色: - 添加一个“SceneTexture”节点,选择“SceneColor”。 8. 颜色混合: - 计算描边颜色:OutlineColor * Power - 使用“LinearInterpolate”(Lerp)节点: A: SceneColor B: OutlineColor * Power Alpha: 上一步得到的边缘强度(Clamped值) 9. 输出: - 将Lerp的结果连接到材质的Emissive Color(自发光颜色)或者直接连接到Final Color(如果材质设置允许)。对于后期处理材质,我们通常直接输出到场景颜色,所以连接到“Emissive”通道即可(因为后期处理材质会叠加到场景上)。 10. 应用材质: - 将材质应用到后期处理体积(Post Process Volume)的“Blendable”材质列表中,或者通过相机设置添加。 注意:需要确保项目设置中启用了CustomDepth: - 打开项目设置(Project Settings)-> Rendering -> 勾选“Custom Depth-Stencil Pass”(设置为Enabled or Enable with Stencil)。 另外,需要描边的物体必须渲染到CustomDepth: - 在需要描边的物体的Mesh上,勾选“Render CustomDepth Pass”(在细节面板的Rendering部分)。 这样,我们就实现了轮廓描边效果。 现在,根据要求生成相关问题:</think>### Unreal Engine 轮廓描边实现方法 在Unreal Engine中实现轮廓描边效果主要通过**后期处理材质(Post Process Material)** 结合**自定义深度(Custom Depth)** 和**边缘检测算法**实现。以下是详细步骤: --- #### 1. **启用自定义深度缓冲** - 在项目设置中启用自定义深度: ``` 项目设置 → Rendering → Custom Depth-Stencil Pass → 设置为"Enable with Stencil" ``` - 为需要描边的物体开启渲染自定义深度: - 在物体细节面板勾选 `Render CustomDepth Pass`(通常在Mesh组件的Rendering部分) --- #### 2. **创建后期处理材质** ```markdown 1. 新建材质,设置材质域为 **"Post Process"** 2. 添加以下核心节点: - `SceneTexture: CustomDepth` 获取深度值 - `MF_Kernel` 节点(自定义卷积核) - `Lerp` 节点混合颜色 ``` --- #### 3. **材质蓝图实现(关键步骤)** ```mermaid graph LR A[SceneTexture: CustomDepth] --> B(MF_Kernel) C[Width参数] --> B B --> D(拉普拉斯卷积计算) D --> E(Clamp 0-1) E --> F(Lerp Alpha) G[OutlineColor * Power] --> F H[SceneColor] --> F F --> I[输出颜色] ``` 1. **边缘检测**(拉普拉斯算子): - 公式:$$ \text{Edge} = 4 \times \text{Center} - (\text{Left} + \text{Right} + \text{Top} + \text{Bottom}) $$ - 实现:用 `MF_Kernel` 配置卷积核: ``` [ 0, -1, 0 ] [-1, 4, -1 ] [ 0, -1, 0 ] ``` 2. **参数控制**: - `Width`(标量参数):控制采样偏移量,调节描边粗细 - `Power`(标量参数):控制描边颜色强度 - `OutlineColor`(颜色参数):描边颜色 3. **颜色混合**: - 用 `Clamp` 将边缘值限制在 `[0,1]` 范围 - 将结果作为 `Lerp` 的Alpha: - Alpha=0 → 显示场景原色(`SceneColor`) - Alpha=1 → 显示描边颜色(`OutlineColor * Power`) --- #### 4. **应用材质到场景** - 方法1:通过 **Post Process Volume** - 将材质添加到 `Blendables` 列表 - 调整优先级确保生效 - 方法2:通过 **Camera Settings** - 相机Actor的细节面板添加后期材质 --- #### 优化技巧 1. **深度滤波**:在 `SceneTexture` 后添加 `GaussianBlur` 减少锯齿 2. **多对象区分**:结合 `CustomStencil` 通道,为不同物体分配不同描边颜色 3. **性能控制**:添加 `IF` 节点在特定距离关闭描边 > ⚠️ 注意:复杂场景需调整 `Width` 避免描边重叠,移动端建议降低采样精度[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值