乘以顶点数据导致轮廓不均匀(shaderlab unity3d)
multiplying vertex data causes uneven outline (shaderlab unity3d)
我在 Unity3D 的着色器实验室中创建了一个简单的轮廓着色器,其中包含两个通道:一个通道通过沿顶点法线乘以顶点信息来放大对象,两个通道绘制对象的常规(基础通道)版本。问题出在大纲传递的代码中:
Pass {
Name "OUTLINE"
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha // Normal
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 position : POSITION;
float3 normal : NORMAL;
};
uniform float _OutlineWidth;
uniform float4 _OutlineColor;
v2f vert(appdata v) {
v.vertex.xyz *= _OutlineWidth;
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : COLOR {
return _OutlineColor;
}
ENDCG
}
_OutlineWidth
和_OutlineColor
分别是Range和Color类型。我已将此着色器应用于几个以编程方式创建的 "tetromino-like" 网格。结果是这样的(点击链接):
image one image two
如您所见,创建了轮廓,但轮廓沿对象的外边缘 宽度不均匀 。沿着其中一张脸,轮廓更大——沿着离形状中心最远的脸更大。对于非凸形状,问题会被放大;轮廓甚至可能根本不包含形状:
image three
我知道这是因为顶点位置是相对于形状的中心的,而线 v.vertex.xyz *= _OutlineWidth
只是将该位置乘以一个常数(使其远离形状的中心)目的)。如何修改我的代码,使轮廓像素的计算独立于形状的中心并与对象的真实轮廓保持一致?
正如@Gnietschow所说,你需要使用那些顶点的"smooth normals"来知道展开轮廓的方向:
资料来源:FrostSoft
一种方法是在 C# 中计算软法线,然后将它们指定为顶点数据。此示例使用 uv2
和 uv3
通道来保存平滑的法线分量:
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] meshVertices = mesh.vertices;
//map vertex positions to the ids of all vertices at that position
Dictionary<Vector3, List<int>> vertexMerge = new Dictionary<Vector3, List<int>>();
for(int i = 0; i < mesh.vertexCount; i++) {
Vector3 vectorPosition = meshVertices[i];
if(!vertexMerge.ContainsKey(vectorPosition)) {
//if not already in our collection as a key, add it as a key
vertexMerge.Add(vectorPosition, new List<int>());
}
//add the vertex id to our collection
vertexMerge[vectorPosition].Add(i);
}
//map vertexIDs to the averaged normal
Vector3[] meshNormals = mesh.normals;
Vector3[] vertexAveragedNormals = new Vector3[mesh.vertexCount];
foreach (List<int> duplicatedVertices in vertexMerge.Values) {
//calculate average normal
Vector3 sumOfNormals = Vector3.zero;
foreach (int vertexIndex in duplicatedVertices) {
sumOfNormals += meshNormals[vertexIndex];
}
Vector3 averagedNormal = (sumOfNormals /= duplicatedVertices.Count).normalized; //average is sum divided by the number of summed elements
//write the result to our output
foreach (int vertexIndex in duplicatedVertices) {
vertexAveragedNormals[vertexIndex] = averagedNormal;
}
}
//write the result to mesh.
//x and y components shoved into uv3, z component shoved into uv4, with w component of 1.
Vector2[] vertexAveragedNormalsXY = new Vector2[mesh.vertexCount];
Vector2[] vertexAveragedNormalsZW = new Vector2[mesh.vertexCount];
for(int i = 0; i < mesh.vertexCount; i++) {
Vector3 normal = vertexAveragedNormals[i];
vertexAveragedNormalsXY[i] = new Vector2(normal.x, normal.y);
vertexAveragedNormalsZW[i] = new Vector2(normal.z, 1);
}
mesh.uv3 = vertexAveragedNormalsXY;
mesh.uv4 = vertexAveragedNormalsZW;
来源:Reddeyfish-
然后,您使用 TEXCOORD2 和 TEXCOORD3 重建 vert
中的平滑法线。使用它们相应地移动你的顶点位置,同时保持顶点法线与任何照明目的相同的硬法线:
Pass {
Name "OUTLINE"
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha // Normal
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
};
struct v2f {
float4 position : POSITION;
float3 normal : NORMAL;
};
uniform float _OutlineWidth;
uniform float4 _OutlineColor;
v2f vert(appdata v) {
// add the outline width in the direction of the shared normal
float3 sharedNormal = float3(v.texcoord2.xy, v.texcoord3.x);
v.vertex.xyz += _OutlineWidth * sharedNormal;
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : COLOR {
return _OutlineColor;
}
ENDCG
}
我在 Unity3D 的着色器实验室中创建了一个简单的轮廓着色器,其中包含两个通道:一个通道通过沿顶点法线乘以顶点信息来放大对象,两个通道绘制对象的常规(基础通道)版本。问题出在大纲传递的代码中:
Pass {
Name "OUTLINE"
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha // Normal
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 position : POSITION;
float3 normal : NORMAL;
};
uniform float _OutlineWidth;
uniform float4 _OutlineColor;
v2f vert(appdata v) {
v.vertex.xyz *= _OutlineWidth;
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : COLOR {
return _OutlineColor;
}
ENDCG
}
_OutlineWidth
和_OutlineColor
分别是Range和Color类型。我已将此着色器应用于几个以编程方式创建的 "tetromino-like" 网格。结果是这样的(点击链接):
image one image two
如您所见,创建了轮廓,但轮廓沿对象的外边缘 宽度不均匀 。沿着其中一张脸,轮廓更大——沿着离形状中心最远的脸更大。对于非凸形状,问题会被放大;轮廓甚至可能根本不包含形状:
image three
我知道这是因为顶点位置是相对于形状的中心的,而线 v.vertex.xyz *= _OutlineWidth
只是将该位置乘以一个常数(使其远离形状的中心)目的)。如何修改我的代码,使轮廓像素的计算独立于形状的中心并与对象的真实轮廓保持一致?
正如@Gnietschow所说,你需要使用那些顶点的"smooth normals"来知道展开轮廓的方向:
一种方法是在 C# 中计算软法线,然后将它们指定为顶点数据。此示例使用 uv2
和 uv3
通道来保存平滑的法线分量:
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] meshVertices = mesh.vertices;
//map vertex positions to the ids of all vertices at that position
Dictionary<Vector3, List<int>> vertexMerge = new Dictionary<Vector3, List<int>>();
for(int i = 0; i < mesh.vertexCount; i++) {
Vector3 vectorPosition = meshVertices[i];
if(!vertexMerge.ContainsKey(vectorPosition)) {
//if not already in our collection as a key, add it as a key
vertexMerge.Add(vectorPosition, new List<int>());
}
//add the vertex id to our collection
vertexMerge[vectorPosition].Add(i);
}
//map vertexIDs to the averaged normal
Vector3[] meshNormals = mesh.normals;
Vector3[] vertexAveragedNormals = new Vector3[mesh.vertexCount];
foreach (List<int> duplicatedVertices in vertexMerge.Values) {
//calculate average normal
Vector3 sumOfNormals = Vector3.zero;
foreach (int vertexIndex in duplicatedVertices) {
sumOfNormals += meshNormals[vertexIndex];
}
Vector3 averagedNormal = (sumOfNormals /= duplicatedVertices.Count).normalized; //average is sum divided by the number of summed elements
//write the result to our output
foreach (int vertexIndex in duplicatedVertices) {
vertexAveragedNormals[vertexIndex] = averagedNormal;
}
}
//write the result to mesh.
//x and y components shoved into uv3, z component shoved into uv4, with w component of 1.
Vector2[] vertexAveragedNormalsXY = new Vector2[mesh.vertexCount];
Vector2[] vertexAveragedNormalsZW = new Vector2[mesh.vertexCount];
for(int i = 0; i < mesh.vertexCount; i++) {
Vector3 normal = vertexAveragedNormals[i];
vertexAveragedNormalsXY[i] = new Vector2(normal.x, normal.y);
vertexAveragedNormalsZW[i] = new Vector2(normal.z, 1);
}
mesh.uv3 = vertexAveragedNormalsXY;
mesh.uv4 = vertexAveragedNormalsZW;
来源:Reddeyfish-
然后,您使用 TEXCOORD2 和 TEXCOORD3 重建 vert
中的平滑法线。使用它们相应地移动你的顶点位置,同时保持顶点法线与任何照明目的相同的硬法线:
Pass {
Name "OUTLINE"
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha // Normal
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
};
struct v2f {
float4 position : POSITION;
float3 normal : NORMAL;
};
uniform float _OutlineWidth;
uniform float4 _OutlineColor;
v2f vert(appdata v) {
// add the outline width in the direction of the shared normal
float3 sharedNormal = float3(v.texcoord2.xy, v.texcoord3.x);
v.vertex.xyz += _OutlineWidth * sharedNormal;
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : COLOR {
return _OutlineColor;
}
ENDCG
}