2023-12-08 18:00:41 +08:00
|
|
|
|
---
|
|
|
|
|
title: 描边
|
|
|
|
|
date: 2023-12-08 16:49:45
|
|
|
|
|
excerpt:
|
|
|
|
|
tags:
|
|
|
|
|
rating: ⭐
|
|
|
|
|
---
|
|
|
|
|
# 实现功能
|
|
|
|
|
- 后处理描边
|
|
|
|
|
- MeshDraw描边
|
2023-06-29 11:55:02 +08:00
|
|
|
|
|
2023-12-18 11:48:10 +08:00
|
|
|
|
# 其他游戏做法
|
|
|
|
|
## 蓝色协议
|
|
|
|
|
采用后处理、Backface以及预绘制描边。
|
|
|
|
|
[[蓝色协议的方案#轮廓]]
|
|
|
|
|
|
2024-06-24 13:01:28 +08:00
|
|
|
|
# 概念
|
|
|
|
|
## 描边概念以及各个算子数学的意义
|
|
|
|
|
参考:https://zhuanlan.zhihu.com/p/478943345
|
|
|
|
|
|
|
|
|
|
### 图像描边的本质
|
|
|
|
|
根据微积分的定义在二维离散函数中推导出来的。核心目的是得到像素点与其相临像素的灰度值变化情况,并通过这种变化来增强图像。原始定义的梯度只是灰度值变化的度量工具。
|
|
|
|
|
|
|
|
|
|
### Sobel
|
|
|
|
|
- Sobel卷积x方向的因子:
|
|
|
|
|
|
|
|
|
|
| -1 | 0 | +1 |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| -2 | 0 | +2 |
|
|
|
|
|
| -1 | 0 | +1 |
|
|
|
|
|
|
|
|
|
|
y方向的因子为:
|
|
|
|
|
|
|
|
|
|
|+1|+2|+1|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|0|0|0|
|
|
|
|
|
|-1|-2|-1|
|
|
|
|
|
Sobel算子更具像素点上下左右临近灰度加权差,在边缘出达到极值这i一现象检测边缘。对噪声具有平滑作用,提供较为准确的边缘方向信息,边缘定位精度不够高,**当对精度要求不是很高时**,是一种较为常用的边缘检测方法。
|
|
|
|
|
**如果要求整体x和y方向的图像梯度,只需要将Gx与Gy相加即可。**
|
|
|
|
|
|
|
|
|
|
### Robert
|
|
|
|
|
Robert算子被应用到图像增强总的锐化,其作为一阶微分算子Robert计算简单,对细节的反应敏感。其边缘检测的作用是提供边缘候选点,可以提供相对较细的边缘。Gx 与Gy的表示:
|
|
|
|
|
|
|
|
|
|
| -1 | 0 | 0 | -1 |
|
|
|
|
|
| --- | --- | --- | --- |
|
|
|
|
|
| 0 | 1 | 1 | 0 |
|
|
|
|
|
### Laplace
|
|
|
|
|
拉普拉斯算子是最简单的**各同性微分算子**,具有**旋转不变性**。二维图像的拉普拉斯变换是各向同性的二阶导数。拉普拉斯算子可以增强图像**边缘变化剧烈的位置**,**平缓减弱变换缓慢的变化区域**。因此可以**使用拉普拉斯算子对原图像灰度图像进行处理,然后再与原图像叠加**。
|
|
|
|
|
|
|
|
|
|
# 实现记录
|
2024-06-24 16:01:26 +08:00
|
|
|
|
[[OutlinePass]]
|
2024-06-24 13:01:28 +08:00
|
|
|
|
|
2023-12-08 18:00:41 +08:00
|
|
|
|
# 杂项
|
2023-06-29 11:55:02 +08:00
|
|
|
|
## 李兄实现Outline思路
|
|
|
|
|
### Depth与Normal描边
|
|
|
|
|
ToonOutlineMain()
|
|
|
|
|
```c++
|
|
|
|
|
float3x3 laplacianOperator = float3x3(-1, -1, -1,
|
|
|
|
|
-1, 8, -1,
|
|
|
|
|
float3x3 Gx = float3x3( -1, +0, +1,
|
|
|
|
|
-1, -1, -1);
|
|
|
|
|
-2, +0, +2,
|
|
|
|
|
-1, +0, +1);
|
|
|
|
|
float3x3 Gy = float3x3( +1, +2, +1,
|
|
|
|
|
+0, +0, +0,
|
|
|
|
|
-1, -2, -1);
|
|
|
|
|
```
|
|
|
|
|
使用GetPixelValue()取得Normal与Depth,之后使用拉普拉斯算子与Sobel算子进行边缘检测:
|
|
|
|
|
```c++
|
|
|
|
|
float4 fs0 = s0 * laplacianOperator[0][0];
|
|
|
|
|
float4 fs1 = s1 * laplacianOperator[0][1];
|
|
|
|
|
float4 fs2 = s2 * laplacianOperator[0][2];
|
|
|
|
|
float4 fs3 = s3 * laplacianOperator[1][0];
|
|
|
|
|
float4 fs4 = s4 * laplacianOperator[1][1];
|
|
|
|
|
float4 fs5 = s5 * laplacianOperator[1][2];
|
|
|
|
|
float4 fs6 = s6 * laplacianOperator[2][0];
|
|
|
|
|
float4 fs7 = s7 * laplacianOperator[2][1];
|
|
|
|
|
float4 fs8 = s8 * laplacianOperator[2][2];
|
|
|
|
|
|
|
|
|
|
float4 sampledValue = fs0 + fs1 + fs2 + fs3 + fs4 + fs5 + fs6 + fs7 + fs8;
|
|
|
|
|
OutlineMask0 = saturate(1.0 - length(sampledValue)); //Line is black
|
|
|
|
|
```
|
|
|
|
|
```c++
|
|
|
|
|
float4 ds0x = s0 * Gx[0][0];
|
|
|
|
|
float4 ds1x = s1 * Gx[0][1];
|
|
|
|
|
float4 ds2x = s2 * Gx[0][2];
|
|
|
|
|
float4 ds3x = s3 * Gx[1][0];
|
|
|
|
|
float4 ds4x = s4 * Gx[1][1];
|
|
|
|
|
float4 ds5x = s5 * Gx[1][2];
|
|
|
|
|
float4 ds6x = s6 * Gx[2][0];
|
|
|
|
|
float4 ds7x = s7 * Gx[2][1];
|
|
|
|
|
float4 ds8x = s8 * Gx[2][2];
|
|
|
|
|
float4 SGX = ds0x + ds1x + ds2x + ds3x + ds4x + ds5x + ds6x + ds7x + ds8x;
|
|
|
|
|
|
|
|
|
|
float4 ds0y = s0 * Gy[0][0];
|
|
|
|
|
float4 ds1y = s1 * Gy[0][1];
|
|
|
|
|
float4 ds2y = s2 * Gy[0][2];
|
|
|
|
|
float4 ds3y = s3 * Gy[1][0];
|
|
|
|
|
float4 ds4y = s4 * Gy[1][1];
|
|
|
|
|
float4 ds5y = s5 * Gy[1][2];
|
|
|
|
|
float4 ds6y = s6 * Gy[2][0];
|
|
|
|
|
float4 ds7y = s7 * Gy[2][1];
|
|
|
|
|
float4 ds8y = s8 * Gy[2][2];
|
|
|
|
|
float4 SGY = ds0y + ds1y + ds2y + ds3y + ds4y + ds5y + ds6y + ds7y + ds8y;
|
|
|
|
|
|
|
|
|
|
OutlineMask1 = saturate(2.0 - step(0.9, length(sqrt(SGX * SGX + SGY * SGY))));
|
|
|
|
|
```
|
|
|
|
|
这个算法巧妙的地方在于对计算卷积核之后使用length(float4(Normal,Depth))来取得结果;Sobel也是:length(sqrt(SGX * SGX + SGY * SGY)。
|
|
|
|
|
|
|
|
|
|
最后使用```OutColor.rgba = OutlineMask0;//lerp(OutlineMask0, OutlineMask1, 0.5);```进行混合。使用OutlineIDMap.a作为宽度控制项,并乘以通过Depth重映射后的变量作为宽度Fix因子。
|
|
|
|
|
|
|
|
|
|
### ID描边
|
|
|
|
|
对ToonIDTexture进行Sobel描边。(只使用了Sobel)
|
|
|
|
|
|
|
|
|
|
### 混合结果
|
|
|
|
|
|
|
|
|
|
## 蓝色协议的做法
|
|
|
|
|
### 模型外扩
|
|
|
|
|
|
|
|
|
|
### 后处理
|
|
|
|
|
1. 使用Sobel算子进行深度检测,**只勾数值差别较大的区域**:脸部顶点色定义区域 与 模型外部轮廓。
|
|
|
|
|
2. 使用Sobel进行ID贴图检测。**只勾数值差别较大的区域**。
|
|
|
|
|
3. 进行法线点积(dot)检测。**在 深度差异小 以及 同一个ID区域内进行检测**:手指区域。
|
2023-12-08 18:00:41 +08:00
|
|
|
|
|
|
|
|
|
1. 使用Sobel过滤器进行深度检测描边。
|
|
|
|
|
2. 使用Sobel过滤器进行Id图检测描边。
|
|
|
|
|
3. 使用Sobel过滤器进行Normal检测描边。用于处理一些难以分ID,深度差又很小的地方,通过获取周围点法线求点乘的方式判断出轮廓。![[08-Assets/Images/ImageBag/UrealEngineNPR/蓝色协议_Normal检测描边.png)
|
|
|
|
|
4. 预先画好的轮廓(GBuffer)。
|
|
|
|
|
|
|
|
|
|
所以使用需要 OutlineId、OutlineWidth(感觉可以传递一个全局Outline信息贴图再通过ID查表来获取,但只能在角色较少时使用)、OutlinePaint 、OutlineZShift(个人感觉不需要)
|