我想改善我最近的question并在SceneKit中使用Metal(片段)着色器实现卡通效果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
答案 0 :(得分:3)
这种单次通过视图空间技术必定会比普通拉伸技术产生更差的结果,但是要使其完全起作用,您需要掌握坐标空间。
这里的目标是什么?好吧,我们想注意到何时表面法线与视图方向几乎垂直,并绕过我们通常的光照计算,而是返回纯色轮廓颜色。
确定垂直度通常意味着获取点积,但是在(+ Y向上,右手)视图空间中,视图方向为(0,0,-1),V
仅为(0, 0,1),并且V和视空间法线之间的点积就是视空间法线的z
分量。
掌握了这些知识之后,我们只需要确保将视图空间法线正确地传递给片段着色器即可即可。
首先,将我们从顶点着色器传递的法线的名称更改为eyeNormal
,以便我们清楚要在哪个空间中进行操作。然后将其计算为
out.eyeNormal = (scn_node.modelViewTransform * float4(in.normal, 0)).xyz;
(此处我们通常会假设MV矩阵不包含任何不均匀的缩放比例或剪切比例)。像往常一样在片段着色器中进行标准化:
float3 normal = normalize(in.eyeNormal);
摆脱三元垃圾;布尔表达式具有布尔类型,可以强制转换为float
:
float edgeFactor = normal.z <= 0.3;
请注意,由于我们在视图空间中进行操作,因此实际上不会看到具有normal.z < 0
的任何片段,因此我们也将abs
删除了。
最后,由于我们实际上不会在提前退出的情况下保存任何周期,因此我们可以使用mix
函数在返回时在亮色和边缘色之间进行选择:
return mix(float4(ambientTerm + diffuseTerm, 1.0), float4(1, 1, 0, 1), edgeFactor);
这里有一半的价格,粗糙的轮廓: