基于皮肤预积分SSS的面部卡通渲染尝试

基于皮肤预积分SSS的面部卡通渲染尝试

之前我在资源商店上发了一个免费资源,内容是一个中国角色模型,其中还包含了头发,皮肤,眼球渲染的shader。现在,我想尝试做做卡通风格,而对于本例中面部卡通渲染,我打算在SSS的基础上尝试弄弄。

资源地址:https://assetstore.unity.com/packages/3d/characters/humanoids/sci-fi/chinese-cyborg-warrior-free-287098

在渲染面部次表面散射效果的时候,一个通用的做法是采样一张LUT图,这也是常用的预积分次表面散射(pre-integrated subsurface scattering)。
在这里插入图片描述

float3 SkinBRDF(lightDatas lightDat, surfaceDatas surfDat, half3 L, half3 lightCol, float shadow, float curvature)
{
    float a2 = Common.Pow4(surfDat.roughness);

    half3 H = normalize(lightDat.V + L);
    half NdotH = saturate(dot(lightDat.N, H));
    half NdotV = saturate(abs(dot(lightDat.N, lightDat.V)) + 1e-5);
    half NdotL = saturate(dot(lightDat.N, L));
    half VdotH = saturate(dot(lightDat.V, H));//LoH
    half LdotH = saturate(dot(H, L));
    float3 radiance = NdotL * lightCol  *shadow; 

    half wrapNoL = 0.2 + NdotL * 0.8;

    float3 sss = SAMPLE_TEXTURE2D(_SSSLUT, sampler_SSSLUT, float2(wrapNoL, curvature));

    float3 diffuseTerm = surfDat.albedo * OneMinusReflectivityMetallicCustom(surfDat.metallic) * sss;
    #if defined(_DIFFUSE_OFF)
        diffuseTerm = half3(0, 0, 0);
    #endif

        float3 specularTerm = DirectBRDF_Specular(surfDat.roughness, NdotH, LdotH) * lerp(kDieletricSpec.rgb, surfDat.albedo, surfDat.metallic);// * surfDat.metallic;
        
    #if defined(_SPECULAR_OFF)
        specularTerm = half3(0, 0, 0);
    #endif

    return  diffuseTerm * lightCol * shadow + specularTerm * radiance;
}

上面的代码是预积分次表面散射的BRDF函数部分。我们把标准PBR渲染中的diffuseTerm项里的NdotL换成我们的采样LUT得到的颜色。其中的curvature曲率来自

float curvature = SAMPLE_TEXTURE2D(_CurvatureMap, sampler_CurvatureMap, i.uv);
curvature = pow(curvature, _CurvaturePow);

采样一张曲率图得到。当然,如果你手中的模型没有曲率图,也可以使用fwidth函数来计算。

本例中,高光的渲染使用unity中lit.shader中使用的DirectBRDF_Specular函数。

half DirectBRDF_Specular(float roughness, float NdotH, float LdotH)
{
    half roughness2 = Common.Pow2(roughness);
    float d = NdotH * NdotH * (roughness2 - half(1.0)) + 1.00001f;

    half LoH2 = LdotH * LdotH;
    half specularTerm = roughness2 / ((d * d) * max(0.1, LoH2) * (roughness * (half)4.0 + half(2.0)));
    return specularTerm;
}

那么为了完成卡通渲染的效果,我的尝试做法是使用对diffuse的结果做一个posterize

half4 Posterize(half4 col)
{
    col = pow(col, 0.4545);
    float3 c = RgbToHsv(col);
    c.z = round(c.z * _Step) / _Step;
    col = float4(HsvToRgb(c), col.a);
    col = pow(col, 2.3);
    return col;
}

而我们的人物的头部并不全是皮肤,还有很多部分不是使用次表面散射计算的。在原例中,我们使用一张Mask图来区分,模型一些部分使用带SSS的,另一部分使用StandardBRDF。现在我们把StandardBRDF的部分换成卡通渲染的

float3 toonLightCal(lightDatas lightDat, surfaceDatas surfDat, half3 L, half3 lightCol, float shadow)
{
    float a2 = Common.Pow4(surfDat.roughness);
    //float a2 = Common.Pow2(surfDat.roughness);

    half3 H = normalize(lightDat.V + L);
    half NdotH = saturate(dot(lightDat.N, H));
    half NdotV = saturate(abs(dot(lightDat.N, lightDat.V)) + 1e-5);
    half NdotL = saturate(dot(lightDat.N, L));
    half VdotH = saturate(dot(lightDat.V, H));//LoH
    half LdotH = saturate(dot(H, L));
    
    half wrapNL = NdotL * 0.5 + NdotL;
    float shadowNL = smoothstep(_ShadowStep - _ShadowStepSmooth, _ShadowStep + _ShadowStepSmooth, wrapNL);

    float3 radiance = lightCol * shadowNL * shadow;

    float3 diffuseTerm = surfDat.albedo * OneMinusReflectivityMetallicCustom(surfDat.metallic);
#if defined(_DIFFUSE_OFF)
    diffuseTerm = half3(0, 0, 0);
#endif

    float specularNH = smoothstep((1 - _SpecularStep * 0.05) - _SpecularStepSmooth * 0.05, (1 - _SpecularStep * 0.05) + _SpecularStepSmooth * 0.05, NdotH);
    float3 specularTerm = _SpecularColor * shadow * shadowNL * specularNH;
    
#if defined(_SPECULAR_OFF)
    specularTerm = half3(0, 0, 0);
#endif

    return  (diffuseTerm + specularTerm) * radiance;    
}

截个完整点的代码

half DirectBRDF_Specular(float roughness, float NdotH, float LdotH)
{
    half roughness2 = Common.Pow2(roughness);
    float d = NdotH * NdotH * (roughness2 - half(1.0)) + 1.00001f;

    half LoH2 = LdotH * LdotH;
    half specularTerm = roughness2 / ((d * d) * max(0.1, LoH2) * (roughness * (half)4.0 + half(2.0)));
    return specularTerm;
}

half OneMinusReflectivityMetallicCustom(half metallic)
{
    // We'll need oneMinusReflectivity, so
    //   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
    // store (1-dielectricSpec) in kDielectricSpec.a, then
    //   1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
    //                  = alpha - metallic * alpha
    half oneMinusDielectricSpec = kDielectricSpec.a;
    return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}

float3 toonLightCal(lightDatas lightDat, surfaceDatas surfDat, half3 L, half3 lightCol, float shadow)
{
    float a2 = Common.Pow4(surfDat.roughness);
    //float a2 = Common.Pow2(surfDat.roughness);

    half3 H = normalize(lightDat.V + L);
    half NdotH = saturate(dot(lightDat.N, H));
    half NdotV = saturate(abs(dot(lightDat.N, lightDat.V)) + 1e-5);
    half NdotL = saturate(dot(lightDat.N, L));
    half VdotH = saturate(dot(lightDat.V, H));//LoH
    half LdotH = saturate(dot(H, L));
    
    half wrapNL = NdotL * 0.5 + NdotL;
    float shadowNL = smoothstep(_ShadowStep - _ShadowStepSmooth, _ShadowStep + _ShadowStepSmooth, wrapNL);

    float3 radiance = lightCol * shadowNL * shadow;

    float3 diffuseTerm = surfDat.albedo * OneMinusReflectivityMetallicCustom(surfDat.metallic);
#if defined(_DIFFUSE_OFF)
    diffuseTerm = half3(0, 0, 0);
#endif

    float specularNH = smoothstep((1 - _SpecularStep * 0.05) - _SpecularStepSmooth * 0.05, (1 - _SpecularStep * 0.05) + _SpecularStepSmooth * 0.05, NdotH);
    float3 specularTerm = _SpecularColor * shadow * shadowNL * specularNH;
    
#if defined(_SPECULAR_OFF)
    specularTerm = half3(0, 0, 0);
#endif

    return  (diffuseTerm + specularTerm) * radiance;    
}

half4 Posterize(half4 col)
{
    col = pow(col, 0.4545);
    float3 c = RgbToHsv(col);
    c.z = round(c.z * _Step) / _Step;
    col = float4(HsvToRgb(c), col.a);
    col = pow(col, 2.3);
    return col;
}

float3 SkinBRDF(lightDatas lightDat, surfaceDatas surfDat, half3 L, half3 lightCol, float shadow, float curvature)
{
    float a2 = Common.Pow4(surfDat.roughness);
    //float a2 = Common.Pow2(surfDat.roughness);

    half3 H = normalize(lightDat.V + L);
    half NdotH = saturate(dot(lightDat.N, H));
    half NdotV = saturate(abs(dot(lightDat.N, lightDat.V)) + 1e-5);//区分正反面
    half NdotL = saturate(dot(lightDat.N, L));
    half VdotH = saturate(dot(lightDat.V, H));//LoH
    half LdotH = saturate(dot(H, L));
    float3 radiance = NdotL * lightCol  *shadow; 

    half wrapNoL = 0.2 + NdotL * 0.8;

    float3 sss = SAMPLE_TEXTURE2D(_SSSLUT, sampler_SSSLUT, float2(wrapNoL, curvature));

    float3 diffuseTerm = surfDat.albedo * OneMinusReflectivityMetallicCustom(surfDat.metallic) * sss;
    #if defined(_DIFFUSE_OFF)
        diffuseTerm = half3(0, 0, 0);
    #endif

        float3 specularTerm = DirectBRDF_Specular(surfDat.roughness, NdotH, LdotH) * lerp(kDieletricSpec.rgb, surfDat.albedo, surfDat.metallic);// * surfDat.metallic;
        
    #if defined(_SPECULAR_OFF)
        specularTerm = half3(0, 0, 0);
    #endif

    return  Posterize(half4(diffuseTerm, 1)).rgb * lightCol * shadow + specularTerm * radiance;
}


half3 SkinShading(lightDatas lightDat,surfaceDatas surfDat,float3 positionWS,float4 shadowCoord, float curvature)
{


    half3 directLighting = (half3)0;
    #if defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_SURFACE_TYPE_TRANSPARENT)
    	float4 positionCS = TransformWorldToHClip(positionWS);
        shadowCoord = ComputeScreenPos(positionCS);
    #else
        shadowCoord = TransformWorldToShadowCoord(positionWS);
    #endif    
    half4 shadowMask = (half4)1.0;

    
    //add light
    half3 directLighting_AddLight = (half3)0;
    #ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();

    if (surfDat.mask > 0.98)
    {
        //main light
        half3 directLighting_MainLight = (half3)0;
        {
            Light light = GetMainLight(shadowCoord, positionWS, shadowMask);
            half3 L = light.direction;
            half3 lightColor = light.color;
            //SSAO
#if defined(_SCREEN_SPACE_OCCLUSION)
            AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(lightDat.screenUV);
            lightColor *= aoFactor.directAmbientOcclusion;
#endif
            half shadow = light.shadowAttenuation;
            directLighting_MainLight = SkinBRDF(lightDat, surfDat, L, lightColor, shadow, curvature);
        }


        UNITY_LOOP
            for (uint lightIndex = 0; lightIndex < pixelLightCount; lightIndex++)
            {
                Light light = GetAdditionalLight(lightIndex, positionWS, shadowMask);
                half3 L = light.direction;
                half3 lightColor = light.color;
                half shadow = light.shadowAttenuation * light.distanceAttenuation;
                directLighting_AddLight += SkinBRDF(lightDat, surfDat, L, lightColor, shadow, curvature);
            }
        return directLighting_MainLight + directLighting_AddLight;
    }
    else
    {
        //main light
        half3 directLighting_MainLight = (half3)0;
        {
            Light light = GetMainLight(shadowCoord, positionWS, shadowMask);
            half3 L = light.direction;
            half3 lightColor = light.color;
            //SSAO
#if defined(_SCREEN_SPACE_OCCLUSION)
            AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(lightDat.screenUV);
            lightColor *= aoFactor.directAmbientOcclusion;
#endif
            half shadow = light.shadowAttenuation;
            directLighting_MainLight = toonLightCal(lightDat, surfDat, L, lightColor, shadow);
        }

        UNITY_LOOP
            for (uint lightIndex = 0; lightIndex < pixelLightCount; lightIndex++)
            {
                Light light = GetAdditionalLight(lightIndex, positionWS, shadowMask);
                half3 L = light.direction;
                half3 lightColor = light.color;
                half shadow = light.shadowAttenuation * light.distanceAttenuation;
                directLighting_AddLight += toonLightCal(lightDat, surfDat, L, lightColor, shadow);
            }
        return directLighting_MainLight + directLighting_AddLight;
    }
    #endif
    return 0;
}

最终效果展示:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值