卡通渲染-平均法线描边

【01】从零开始的卡通渲染-描边篇]

最近在学习渲染,入门的时候也学了Back facing描边法,但是表现在正方体上割裂严重,就先略过了。

看了这篇文章才了解到平均法线的做法,作者将平均法线存到模型的切线数据中,也提及了可以转换到切线空间再存到颜色或者uv上。因为他只做了存到切线数据的方案,我就想试试使用切线空间法线的方案,写出来才算融会贯通了。

对作者的工具稍作修改,将网格保存到本地。

《UnityShader入门精要》这本书的法线贴图相关章节,提到了TBN矩阵,在C#实现一下,并且类似法线贴图把数值范围处理到[0,1]。

工具:模型平均法线写入顶点颜色

    [MenuItem("Tools/模型平均法线写入顶点颜色")]
    public static void WriteToColor()
    {
        MeshFilter[] meshFilters = Selection.activeGameObject.GetComponentsInChildren<MeshFilter>();
        foreach (var meshFilter in meshFilters)
        {
            Mesh mesh = Object.Instantiate(meshFilter.sharedMesh);
            ToColor(mesh);
            AssetDatabase.CreateAsset(mesh, "Assets/" + meshFilter.name + "New.mesh");
        }

        SkinnedMeshRenderer[] skinMeshRenders = Selection.activeGameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
        foreach (var skinMeshRender in skinMeshRenders)
        {
            Mesh mesh = Object.Instantiate(skinMeshRender.sharedMesh);
            ToColor(mesh);
            AssetDatabase.CreateAsset(mesh, "Assets/" + skinMeshRender.name + "New.mesh");
        }

    }
    private static void ToColor(Mesh mesh)
    {
        var averageNormalHash = new Dictionary<Vector3, Vector3>();
        for (var j = 0; j < mesh.vertexCount; j++)
        {
            if (!averageNormalHash.ContainsKey(mesh.vertices[j]))
            {
                averageNormalHash.Add(mesh.vertices[j], mesh.normals[j]);
            }
            else
            {
                averageNormalHash[mesh.vertices[j]] =
                    (averageNormalHash[mesh.vertices[j]] + mesh.normals[j]).normalized;
            }
        }

        var averageNormals = new Vector3[mesh.vertexCount];
        for (var j = 0; j < mesh.vertexCount; j++)
        {
            averageNormals[j] = averageNormalHash[mesh.vertices[j]];
            //转到切线空间 并且设置成[0,1]范围
            var mNormal = mesh.normals[j];
            var mTangent = mesh.tangents[j];

            var mBinormal = Vector3.Cross(mNormal, new Vector3(mTangent.x, mTangent.y, mTangent.z)) * mTangent.w;
            //构造是按列,此处需要按行
            Matrix4x4 matrix = new Matrix4x4(new Vector3(mTangent.x, mTangent.y, mTangent.z).normalized, mBinormal.normalized, mNormal.normalized, Vector4.zero);
            Matrix4x4 tmatrix = Matrix4x4.Transpose(matrix);
            //[-1,1]=>[0,2]=>[0,1]
            averageNormals[j] = (tmatrix.MultiplyVector(averageNormals[j]).normalized + Vector3.one) / 2.0f;
        }
        
        var colors = new Color[mesh.vertexCount];
        for (var j = 0; j < mesh.vertexCount; j++)
        {
            colors[j] = new Color(averageNormals[j].x, averageNormals[j].y, averageNormals[j].z, 0);
        }
        mesh.colors = colors;
    }

在Shader是类似的,将矩阵转置一下,就是切线空间转模型空间的矩阵

v2f vert(appdata v)
{
    v2f o;
    float4 pos = UnityObjectToClipPos(v.vertex);
    TANGENT_SPACE_ROTATION;
    float3 anormal = v.colors.rgb * 2 - 1;
    //这里再转置(逆)一下,因为rotation是模型空间转切线空间的矩阵
    anormal = normalize(mul(transpose(rotation), anormal));
   

    float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, anormal.xyz);

    float3 ndcNormal = normalize(TransformViewToProjection(viewNormal.xyz)) ;//将法线变换到NDC空间
    float aspect = _ScreenParams.x / _ScreenParams.y;//求得屏幕宽高比
    ndcNormal.x /= aspect;
    //pos.w跟距离有关 裁剪后会除掉,进行近大远小缩放,但我们希望远处的描边也一样大,所以乘回去,避免被除没了
    pos.xy += 0.01 * _OutlineScale * ndcNormal.xy * pos.w;
    o.vertex = pos;
    return o;
}

TANGENT_SPACE_ROTATION; 会得到模型空间转切线空间矩阵rotation,再做一下转置就是切线转模型。

anormal就是还原好的模型空间下的平均法线

为了在屏幕上,无论远近描边大小都不变,也就是说要还原“透视除法”的处理,将w分量乘回去。
另外,我们在屏幕上沿着发现扩展的x增量,在视口变换后,会根据屏幕分辨率调整,横屏变得更大竖屏更小,因此也要消除掉这个影响。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值