使用Metal Shader和SceneKit实现卡通效果

时间:2018-12-06 20:02:19

标签: shader scenekit metal fragment-shader

我想改善我最近的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

1 个答案:

答案 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);

这里有一半的价格,粗糙的轮廓:

Screenshot