在看模特秀的时候,有时候身上会有那种布灵布灵的面片效果。下面两张是我在丝绸的时装秀里面截取的两张模糊图,凑合看一下吧。
如果需要在渲染里面去实现这种效果,该如何实现呢。
当然用我上一篇说到的泰森多边形了
我们可以使用其进行noise扰动,实现面片分离的效果,然后再使用其的id的值转换成为法向,相同片的法向相同,但由于id不同,每个片生成的法向的朝向也不相同,然后用这个法向去做高光效果,你就会得到一个亮片一个亮片的效果。
就比如这种效果,心动了吗,那么,接下来进入实战。
首先,就是用voronoi生成法向。
//亮片
float2 idvoronoi = 0;
float2 uvvoronoi = 0;
voronoi(input.uv * _FlakesScale, _FlakesAngle, idvoronoi, uvvoronoi, 0);
half3 voronoiNormal = half3(idvoronoi * _FlakesRange, 1.0); //使用voronoi函数生成的id作为扰动法向的xy
half3 noiseNormalWS = TransformTangentToWorld(voronoiNormal, tangentToWorld); //转换到世界空间坐标系下面
noiseNormalWS = NormalizeNormalPerPixel(noiseNormalWS);
如果需要voronoi函数,去上一篇去拿即可。
我们还需要一个亮片的颜色,在这里,我们刚好可以使用voronoi生成的uv去随便拾取一张多颜色亮片,然后和一个颜色相乘,还可以控制强度,相乘的颜色如果为0,那么亮片将不显示。
half3 flakesBaseColor = _FlakesColor * SAMPLE_TEXTURE2D(_FlakesMap, sampler_FlakesMap, uvvoronoi).rgb;
接下来就是重头戏了,数据都完成了,需要生成亮片的函数了:
half3 flakesColor = FlakesSpecular(_FlakesRoughness1, _FlakesRoughness2, _FlakesRoughnessMix, flakesBaseColor, positionWS, noiseNormalWS, viewDirWS, shadowCoord, shadowMask);
这里用了两个粗糙度,然后进行混合出来的,给亮片增加了过渡的效果。计算出来的结果和最终颜色相加即可。
接下来,将函数的实现代码粘贴出来,就是主光源和附加光源进行亮片计算即可。
//实现双层的GGX,这个专门是亮片使用的,F项就不需要根据视角进行过渡的效果了
float3 DualSpecularGGXFlakes(float Lobe0Roughness, float Lobe1Roughness, float LobeMix, float3 SpecularColor, float NoH, float NoV, float NoL)
{
float Lobe0Alpha2 = Pow4(Lobe0Roughness);
float Lobe1Alpha2 = Pow4(Lobe1Roughness);
float AverageAlpha2 = Pow4((Lobe0Roughness + Lobe1Roughness) * 0.5);
// Generalized microfacet specular
float D = lerp(D_GGX_UE4(Lobe0Alpha2, NoH), D_GGX_UE4(Lobe1Alpha2, NoH), 1.0 - LobeMix);
float Vis = Vis_SmithJointApprox(AverageAlpha2, NoV, NoL);
float3 F = SpecularColor;
return(D * Vis) * F;
}
//亮片的实现函数
float3 FlakesSpecular(float Lobe0Roughness, float Lobe1Roughness, float LobeMix, float3 SpecularColor, float3 WorldPos,
float3 N, float3 V, float4 shadowCoord, float4 shadowMask)
{
//主光源
half3 DirectLighting_MainLight = half3(0, 0, 0);
{
Light mainLight = GetMainLight(shadowCoord, WorldPos, shadowMask);
half3 L = mainLight.direction;
half3 LightColor = mainLight.color;
half Shadow = mainLight.shadowAttenuation;
float3 H = normalize(L + V);
float NoH = saturate(dot(N, H));
float NoV = saturate(abs(dot(N, V)) + 1e-5);
float NoL = saturate(dot(N, L));
float3 Radiance = NoL * LightColor * Shadow * PI;
DirectLighting_MainLight = DualSpecularGGXFlakes(Lobe0Roughness, Lobe1Roughness, LobeMix, SpecularColor, NoH, NoV, NoL) * Radiance;
}
//附加光源
half3 DirectLighting_AddLight = half3(0, 0, 0);
#ifdef _ADDITIONAL_LIGHTS
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0; lightIndex < pixelLightCount; ++lightIndex)
{
Light light = GetAdditionalLight(lightIndex, WorldPos, shadowMask);
half3 L = light.direction;
half3 LightColor = light.color;
half Shadow = light.shadowAttenuation * light.distanceAttenuation;
float3 H = normalize(L + V);
float NoH = saturate(dot(N, H));
float NoV = saturate(abs(dot(N, V)) + 1e-5);
float NoL = saturate(dot(N, L));
float3 Radiance = NoL * LightColor * Shadow * PI;
DirectLighting_AddLight += DualSpecularGGXFlakes(Lobe0Roughness, Lobe1Roughness, LobeMix, SpecularColor, NoH, NoV, NoL) * Radiance;
}
#endif
return DirectLighting_MainLight + DirectLighting_AddLight;
}