用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);

你有它,半价的粗糙轮廓: