使用Metal shader和SceneKit实现卡通效果 [英] Implementing toon effect with Metal shader and SceneKit

查看:233
本文介绍了使用Metal shader和SceneKit实现卡通效果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想改善我最近的问题并实现像这样在SceneKit中使用Metal(片段)着色器.

I would like to improve my recent question and implement toon effect like this using Metal (fragment) shader in SceneKit.

这是我的片段着色器,实现了简单的phong照明和卡通效果:

Here is my fragment shader implementing both simple phong lighting and toon effect:

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

正如我所说,我受到这篇文章,但结果却截然不同...

As I said I go inspired by this article but I get very different result...

有什么想法吗?这是整个项目

推荐答案

这种单遍视图空间技术必定会比普通拉伸技术产生更差的结果,但要使其完全起作用,您需要掌握坐标空间.

This type of single-pass view-space technique is necessarily going to produce worse results than the normal-extrusion technique, but in order for it to work at all, you need to get a grip on your coordinate spaces.

这里的目标是什么?好吧,我们要注意何时表面法线与视图方向几乎垂直,并绕过我们通常的光照计算,而是返回纯色轮廓颜色.

What's the aim here? Well, we want to notice when the surface normal is nearly perpendicular to the view direction, and bypass our usual lighting calculations, returning a solid silhouette color instead.

确定垂直度通常意味着获取点积,但是在(+ Y向上,右手)视图空间中,视图方向仅为(0,0,-1),V仅为(0,0, 1),并且V与视空间法线之间的点积就是视空间法线的z分量.

Determining perpendicularity usually means taking dot products, but in (a +Y up, right-handed) view space, the view direction is just (0, 0, -1), V is just (0, 0, 1), and the dot product between V and the view-space normal is just the z component of the view-space normal.

掌握了这些知识之后,我们只需要确保将视图空间法线正确地传递给片段着色器即可即可.

With that knowledge in-hand, we just need to make sure we're passing the view-space normal to our fragment shader correctly.

首先,将我们从顶点着色器传出的法线的名称更改为eyeNormal,以便我们清楚要在其中操作的空间.然后将其计算为

First, change the name of the normal we pass out from the vertex shader to eyeNormal so we're clear on what space we're operating in. It's then computed as

out.eyeNormal = (scn_node.modelViewTransform * float4(in.normal, 0)).xyz;

(在这里,我们通常会假设MV矩阵不包含任何不均匀的缩放比例或剪切比例).像往常一样在片段着色器中进行标准化:

(where we're making the usual glib assumption that the MV matrix doesn't contain any nonuniform scaling or shearing). Normalize as usual in the fragment shader:

float3 normal = normalize(in.eyeNormal);

摆脱三元垃圾;布尔表达式具有布尔类型,可以强制转换为float:

Get rid of that ternary garbage; a boolean expression has boolean type, which coerces just fine into a float:

float edgeFactor = normal.z <= 0.3;

请注意,由于我们在视图空间中进行操作,因此实际上不会看到具有normal.z < 0的任何片段,因此我们也将abs删除.

Note that because we're operating in view space, we won't actually see any fragments that have normal.z < 0, so we drop the abs too.

最后,由于实际上我们不会在提前退出时保存任何周期,因此我们可以使用mix函数在返回时在亮色和边缘色之间进行选择:

Finally, since we won't actually save any cycles with that early exit, we can use the mix function to select between the lit color and the edge color as we return:

return mix(float4(ambientTerm + diffuseTerm, 1.0), float4(1, 1, 0, 1), edgeFactor);

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

And there you have it, cruddy silhouettes for half the price:

这篇关于使用Metal shader和SceneKit实现卡通效果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆