用Metal shader和SceneKit实现卡通效果
Implementing toon effect with Metal shader and SceneKit
我想在 SceneKit 中使用金属(片段)着色器来改进我最近的 and implement toon effect like this。
这是我的片段着色器,实现了简单的 phong 光照和卡通效果:
fragment float4 lightingFragment(VertexOut in [[stage_in]]) {
float3 normal = normalize(in.normal);
// For edges set color to yellow
float3 V = normalize(in.eye - in.position.xyz);
float edgeDetection = (abs(dot(V, normal)) > 0.1) ? 1 : 0;
if ( edgeDetection != 1 ) {
return float4(1, 1, 0, 1);
}
// Compute simple phong
float3 lightDirection = normalize(light.position - in.position.xyz);
float diffuseIntensity = saturate(dot(normal, lightDirection));
float3 diffuseTerm = light.diffuseColor * material.diffuseColor * diffuseIntensity;
// Ambient color
float3 ambientTerm = light.ambientColor * material.ambientColor;
return float4(ambientTerm + diffuseTerm, 1.0);
}
正如我所说,我受到 this article 的启发,但我得到的结果却截然不同...
有什么想法吗?这是 whole project
这种 single-pass view-space 技术必然会产生比 normal-extrusion 技术更差的结果,但为了让它发挥作用,您需要获得抓住你的坐标 spaces.
这里的目的是什么?好吧,我们想要注意何时表面法线 几乎垂直于 观察方向,并绕过我们通常的光照计算,return 改为使用纯色轮廓颜色。
判断垂直度通常是取点积,但是在(a +Y up, right-handed)视图中space,视图方向正好是(0, 0, -1), V
只是 (0, 0, 1),V 和 view-space 法线之间的点积只是 view-space 法线的 z
分量。
有了这些知识 in-hand,我们只需要确保我们正确地将 view-space 法线传递给我们的片段着色器。
首先,将我们从顶点着色器传出的法线的名称更改为 eyeNormal
,这样我们就清楚了我们正在操作的 space。然后计算为
out.eyeNormal = (scn_node.modelViewTransform * float4(in.normal, 0)).xyz;
(我们通常会油嘴滑舌地假设 MV 矩阵不包含任何非均匀缩放或剪切)。在片段着色器中正常化:
float3 normal = normalize(in.eyeNormal);
摆脱那个三元垃圾;布尔表达式具有布尔类型,它可以很好地强制转换为 float
:
float edgeFactor = normal.z <= 0.3;
请注意,因为我们在视图 space 中操作,我们实际上不会看到任何具有 normal.z < 0
的片段,所以我们也删除了 abs
。
最后,因为我们实际上不会通过提前退出来保存任何循环,所以我们可以使用 mix
函数在点亮颜色和边缘颜色之间 select ,因为我们 return:
return mix(float4(ambientTerm + diffuseTerm, 1.0), float4(1, 1, 0, 1), edgeFactor);
你有它,半价的粗糙轮廓:
我想在 SceneKit 中使用金属(片段)着色器来改进我最近的
这是我的片段着色器,实现了简单的 phong 光照和卡通效果:
fragment float4 lightingFragment(VertexOut in [[stage_in]]) {
float3 normal = normalize(in.normal);
// For edges set color to yellow
float3 V = normalize(in.eye - in.position.xyz);
float edgeDetection = (abs(dot(V, normal)) > 0.1) ? 1 : 0;
if ( edgeDetection != 1 ) {
return float4(1, 1, 0, 1);
}
// Compute simple phong
float3 lightDirection = normalize(light.position - in.position.xyz);
float diffuseIntensity = saturate(dot(normal, lightDirection));
float3 diffuseTerm = light.diffuseColor * material.diffuseColor * diffuseIntensity;
// Ambient color
float3 ambientTerm = light.ambientColor * material.ambientColor;
return float4(ambientTerm + diffuseTerm, 1.0);
}
正如我所说,我受到 this article 的启发,但我得到的结果却截然不同...
有什么想法吗?这是 whole project
这种 single-pass view-space 技术必然会产生比 normal-extrusion 技术更差的结果,但为了让它发挥作用,您需要获得抓住你的坐标 spaces.
这里的目的是什么?好吧,我们想要注意何时表面法线 几乎垂直于 观察方向,并绕过我们通常的光照计算,return 改为使用纯色轮廓颜色。
判断垂直度通常是取点积,但是在(a +Y up, right-handed)视图中space,视图方向正好是(0, 0, -1), V
只是 (0, 0, 1),V 和 view-space 法线之间的点积只是 view-space 法线的 z
分量。
有了这些知识 in-hand,我们只需要确保我们正确地将 view-space 法线传递给我们的片段着色器。
首先,将我们从顶点着色器传出的法线的名称更改为 eyeNormal
,这样我们就清楚了我们正在操作的 space。然后计算为
out.eyeNormal = (scn_node.modelViewTransform * float4(in.normal, 0)).xyz;
(我们通常会油嘴滑舌地假设 MV 矩阵不包含任何非均匀缩放或剪切)。在片段着色器中正常化:
float3 normal = normalize(in.eyeNormal);
摆脱那个三元垃圾;布尔表达式具有布尔类型,它可以很好地强制转换为 float
:
float edgeFactor = normal.z <= 0.3;
请注意,因为我们在视图 space 中操作,我们实际上不会看到任何具有 normal.z < 0
的片段,所以我们也删除了 abs
。
最后,因为我们实际上不会通过提前退出来保存任何循环,所以我们可以使用 mix
函数在点亮颜色和边缘颜色之间 select ,因为我们 return:
return mix(float4(ambientTerm + diffuseTerm, 1.0), float4(1, 1, 0, 1), edgeFactor);
你有它,半价的粗糙轮廓: