Unity | 解决描边断开问题
当使用法线外扩描边法时, 会产生断开现象v.vertex.xyz += v.normal.xyz * _LineWidth * 0.1;
断开
<hr/>解法1:
跟美术策划商量, 不要用硬边(误
<hr/>
解法2:
在dcc软件里, 将软边模型的法线信息, 烘焙到硬边模型(引擎里需要用的模型)的顶点色里, 在shader进行外扩描外扩时, 朝顶点色映射后的方向扩.
需要注意的点:
[*]建议将软边模型的切线空间的法线信息烘焙到顶点色(是个面试考点), 防止蒙皮动画带来变形. 要是偷懒用物体空间法线也不是不行...
[*]烘焙后拿到的顶点色, 需要从映射到[-1, 1]再用(别忘了, 别忘了, 别忘了!)
[*]烘焙可能出现难以解决的错误(本人对dcc的熟悉度, 暂时还不能解决这个烘焙问题...)
[*]需要有uv map
[*]以上注意点不代表所有注意点, 只因本人目前只遇到这些坑
引擎里使用的shader见后文.
我用的是blender来烘焙顶点色的, 但存在肉眼可见的误差, 所以这里提供dcc脚本的方法, 若在引擎的效果错误, 则检查一下dcc里顶点色layer是否正确.
模型空间(效果正确):
import bpy
#切线空间顶点色烘焙
mesh = bpy.context.object.data #mesh
bpy.ops.mesh.vertex_color_add()
#bpy.ops.mesh.uv_texture_add()
mesh.calc_tangents()
mesh.calc_normals()
dic = {}
nList = []
for i in mesh.loops:
dic = mesh.vertex_normals.vector
t = i.tangent
b = i.bitangent
n = i.normal
tn =
tn = t * dic + t * dic + t * dic
tn = b * dic + b * dic + b * dic
tn = n * dic + n * dic + n * dic
nList.append(tn)
n = 0
vl = mesh.vertex_colors#vertex color layer
for i in vl.data:
i.color = nList * 0.5 + 0.5#x invers
i.color = nList * 0.5 + 0.5
i.color = nList * 0.5 + 0.5
i.color = 1
n += 1
pass
切线空间(有些许误差):
import bpy
import mathutils
mesh = bpy.context.object.data #mesh
bpy.ops.mesh.vertex_color_add()
#bpy.ops.mesh.uv_texture_add()
mesh.calc_tangents()
mesh.calc_normals()
nList = []
for i in mesh.loops:
n_smooth = mathutils.Vector((0, 0, 0))
for j in mesh.loops:
if i.vertex_index == j.vertex_index:
n_smooth += j.normal
n_smooth.normalize()
t = i.tangent
n = i.normal
b = i.bitangent_sign * n.cross(t)
tn = mathutils.Vector((0, 0, 0))
tn = t * n_smooth + t * n_smooth + t * n_smooth
tn = b * n_smooth + b * n_smooth + b * n_smooth
tn = n * n_smooth + n * n_smooth + n * n_smooth
nList.append(tn)
n = 0
vl = mesh.vertex_colors#vertex color layer
for i in vl.data:
i.color = nList * 0.5 + 0.5
i.color = nList * 0.5 + 0.5
i.color = nList * 0.5 + 0.5
i.color = 1
n += 1
pass
虽然上面这张图肉眼看来没问题, 但细细观察(下图), 还是有断开地方, 初步判定是, 因为模型空间向切线空间转换过程出现误差(目前还没找到解决方法, 之后会了再补上...)
<hr/>解法3:
用代码, 计算出模型平滑后的法线, 用平滑法线替换模型法线, 或填充到顶点色, 都可以. 即, 在引擎里, 做 解法2中dcc代码部分
这种方法大概率不会出错 (为什么说是大概率, 因为本人做了一个垃圾模型, 出现了走样...但此外大部分模型是不会走样的), 如果出错, 可以优先检查一下uv map
平滑法线参考:
本文代码的思路:
[*]遍历模型的每一个vertices
[*]遍历模型的每一个normals
[*]将每一个normal与模型的所有normal对比, 若两个normal对应的vertice位置相同, 则认为两个normal是同一个顶点为平滑的法线, 需要被平滑
[*]将一个顶点所有未平滑的法线相加, 归一化, 得到平滑后的法线
[*]将平滑后的法线信息, 写到模型顶点色
[*]模型材质shader中, 将模型顶点, 沿着顶点色映射成向量的方向, 外扩
若储存切线空间法线, 则需要模型展uv
private Vector3[] nList;
private Vector3[] vList;
private Color[] cList;
private Color[] ori_cList;
//......
nList = meshFilter.sharedMesh.normals;
vList = meshFilter.sharedMesh.vertices;
ori_cList = new Color;
cList = new Color;
for (int i = 0; i < ori_cList.Length; i++)
{
ori_cList = new Color(0.5f, 0.5f, 0.5f);
}
//......
private void SmoothData()
{
for (int i = 0; i < nList.Length; i++)//遍历每一根法线
{
Vector3 nor = Vector3.zero;
for (int j = 0; j < nList.Length; j++)//将当前取出的法线, 与模型所有法线对比
{
if(vList == vList)//若两根法线对应点坐标相同
{
nor += nList;//法线累积
}
}
//[-1, 1] ->
//obj -> tangent
Vector3 nCol = Obj2Tangent(nor.normalized, i) * 0.5f + Vector3.one * 0.5f;//所有累积的法线归一化, 转换到切线空间
cList = new Color(nCol.x, nCol.y, nCol.z);//填充到顶点色
}
meshFilter.sharedMesh.SetColors(cList);//修改模型顶点色
}
在c#遍历模型sharedMesh的所有vertices, 与normals时, 发现两种数量是一样的, 不用担心顶点比法线少, 对比顶点时产生越界问题.
例如下图, 红框的一个顶点, 实际代表3个vertices(该点与3边连接, 每个边拆出1个), 3个normals
效果:
有些时候不希望内部出现顶点挤出现象, 可以用模板测试去掉
本人菜鸡...上述代码的效率还有待优化, 高模用这个脚本会卡上很久...高模慎用! dcc修改顶点色的方法比引擎修改的方法效率高点.
但各文章解决外扩断开的核心基本一致, 用一个平滑后的法线替换原法线
完整代码:
c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Outline : MonoBehaviour
{
private MeshFilter meshFilter;
private Vector3[] nList;
private Vector3[] vList;
private Color[] cList;
private Color[] ori_cList;
private void OnEnable()
{
InitData();
SmoothData();
}
private void OnDisable()
{
if (meshFilter == null) return;
ResetData();
}
private void InitData()
{
meshFilter = GetComponent<MeshFilter>();
if (meshFilter == null)
{
enabled = false;
return;
}
nList = meshFilter.sharedMesh.normals;
vList = meshFilter.sharedMesh.vertices;
ori_cList = new Color;
cList = new Color;
for (int i = 0; i < ori_cList.Length; i++)
{
ori_cList = new Color(0.5f, 0.5f, 0.5f);
}
}
private void SmoothData()
{
for (int i = 0; i < nList.Length; i++)
{
Vector3 nor = Vector3.zero;
for (int j = 0; j < nList.Length; j++)
{
if(vList == vList)
{
nor += nList;
}
}
//[-1, 1] ->
//obj -> tangent
Vector3 nCol = Obj2Tangent(nor.normalized, i) * 0.5f + Vector3.one * 0.5f;
cList = new Color(nCol.x, nCol.y, nCol.z);
}
meshFilter.sharedMesh.SetColors(cList);//set data
}
private Vector3 Obj2Tangent(Vector3 ori, int id)
{
Vector4 t4 = meshFilter.sharedMesh.tangents;
//tbn
Vector3 t = new Vector3(t4.x, t4.y, t4.z);
Vector3 n = meshFilter.sharedMesh.normals;
Vector3 b = Vector3.Cross(n, t) * t4.w;
Vector3 tNor = Vector3.zero;
tNor.x = t.x * ori.x + t.y * ori.y + t.z * ori.z;
tNor.y = b.x * ori.x + b.y * ori.y + b.z * ori.z;
tNor.z = n.x * ori.x + n.y * ori.y + n.z * ori.z;
return tNor;
}
private void ResetData()
{
meshFilter.sharedMesh.SetColors(ori_cList);
}
}
shader
Shader &#34;Outline/Outline_Surf&#34;
{
Properties
{
_Color (&#34;Color&#34;, Color) = (1,1,1,1)
_MainTex (&#34;Albedo (RGB)&#34;, 2D) = &#34;white&#34; {}
_Glossiness (&#34;Smoothness&#34;, Range(0,1)) = 0.5
_Metallic (&#34;Metallic&#34;, Range(0,1)) = 0.0
_LineCol (&#34;Line Color&#34;, Color) = (1, 1, 1, 1)
_LineWidth (&#34;Line Width&#34;, Float) = 1
}
SubShader
{
Tags { &#34;RenderType&#34;=&#34;Opaque&#34; }
LOD 200
Stencil
{
Ref 1
Comp GEqual //该ref值(1)比缓冲中的值大于等于时通过
Pass Replace
}
CGPROGRAM
#pragma surface surf Standard fullforwardshadows addshadow
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
float4 _Color;
float _Glossiness, _Metallic;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
//outline pass
Tags { &#34;RenderType&#34;=&#34;Opaque&#34; }
Cull Front
Stencil
{
Ref 0
Comp Equal
Pass Keep
}
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
struct Input
{
float2 uv_MainTex;
};
float4 _LineCol;
float _LineWidth;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void vert(inout appdata_full v)
{
float3 t = v.tangent.xyz;
float3 n = v.normal;
float3 b = cross(n, t) * v.tangent.w;
float3x3 TBN_Line = float3x3(t, b, n);//tangent to obj
float3 dir = mul((v.color.xyz * 2 - 1), TBN_Line);
v.vertex.xyz += dir * _LineWidth * 0.1;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
o.Albedo = 0;
o.Metallic = 0;
o.Smoothness = 0;
o.Alpha = 1;
o.Emission = _LineCol;
}
ENDCG
}
FallBack &#34;Diffuse&#34;
}
更加完美的解决方案: 还可以降采样在屏幕空间做 blender里计算切线空间的软边法线的确会有问题,blender里的tangent信息和unity里的是不太一样的。[捂脸]
页:
[1]