PBR渲染(四)——PBR头发渲染

本文聚焦于Unity游戏引擎中的PBR头发渲染。介绍了渲染方案,选用Marschner理论模型及头发半透明处理方案;阐述头发资源制作流程,包括纹理图集生成和头发面片制作;说明了贴图方案;还给出具体实现,如用AlphaTest+TAA过渡、采样BaseColor变色等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PBR头发渲染

1.渲染方案
  • 首先在光照模型的选择上,选择了Marschner基于物理的理论模型。请添加图片描述
    理论模型:头发不是完美的圆柱体或管子,实际上,头发更像是一系列堆叠的圆锥体。每一根头发纤维由角质层(cuticle)和皮层(cortex)两部分组成
    R:R为反射路径,反射出来的光线颜色为原始光源颜色
    TRT:光线进入头发纤维内部,部分能量被吸收,所以最终反射出来的光线具有头发纤维的颜色
    TT:透射-透射路径,TT出来的光线也是有色的,但是颜色比TRT的饱和度要低,因为只经过了一次折射
  • 头发半透明处理方案,AlphaTest+TAA
  • 材质接口
    相比原来的PBR渲染框架,头发渲染shader会改变几个材质接口。
    /第一个是Scatter,该属性替换了主着色器节点上的 Metallic,并且值限制在 0.0 和 1.0 之间。Scatter控制有多少光穿过角色整个头发。浅色头发,Scatter数值更高;深色头发,Scatter数值偏低。这遵循自然世界的物理规则,因为较深的头发往往会吸收更多的光。
    /第二个是Normal,此处输入的是切线而不是法线数据,或者使用Flowmap来制定切线方向。注意,渲染头发一般不用NormalMap。
2.头发资源的制作流程
  • 首先会生成头发的纹理图集,一般会在Maya 中先利用Xgen头发系统制作出头发,再使用渲染器(Arnold/xNormal)烘培出对应的纹理图集。
  • 然后制作对应纹理图集的头发面片,一般是手动制作,然后再手动摆放和调整。
3.贴图方案
  • 上面在生成头发纹理图集时,会生成一系列贴图,例如Diffuse Map / Albedo,Alpha Map,Root Map / Gradient map(头发根部到发梢的渐变图),Depth Map(深度纹理由像素深度偏移使用,以产生将头发推入头发更深处的错觉。它还可以用作更改不同深度头发颜色或阴影值的基础,例如在头发进一步向头皮下落时降低整体镜面反射。),Unique ID Map(ID 纹理只是为给定头发几何体上的每根头发提供唯一的 0 到 1(黑到白)值。这可用于在头发本身内提供细微的变化。比如每根头发做一定程度的offset),Flow Map / Direction Map。
4.具体实现
  • AlphaTest+TAA,使用透明度测试实现从面片到发丝的过渡,为了使AlphaTest的效果进一步接近AlphaBlend的效果,使用TAA的方法减少噪点和锯齿。
  • 传入副切线方向,因为主光照函数的切线参数是切线空间的副切线,所以副切线的值固定为(0,1,0)。这里也可以采样FlowMap纹理传入切线信息。之后在切线的基础上叠加一层头发切线的噪声图,增强头发的各向异性高光。
  • 采样BaseColor并实现头发变色,这里使用的方法是将不同发色的BaseColor集中在一张纹理中,然后再控制UV的V值对不同发色的BaseColor进行采样。
    请添加图片描述
  • 而对于Scatter参数也是一样。Scatter参数是用来控制有多少光穿过角色整个头发。其实通俗一点就是Scatter值越高,穿过头发出射的光线越多,且出射光照的饱和度也越高,视觉感受就是颜色更亮,更鲜艳。同样的每一种发色对应一个Scatter值,将这些Scatter值储存在一张单通道灰度图中,用同样方式进行采样。
    请添加图片描述
  • 最后开启HBAO,增加人物面部细节。
头发各向异性渲染Shader 这个是04年的一个ppt,主要介绍了头发渲染,其追到源头还是要看这个原理。 各向异性的主要计算公式: 主要代码如下: 切线混合扰动部分(这部分也可以用T+k*N,来对切线进行扰动): float3x3 tangentTransform = float3x3(i.tangentDir, i.bitangentDir, i.normalDir); float3 _T_var = UnpackNormal(tex2D(_Tangent, TRANSFORM_TEX(i.uv0, _Tangent))); float3 temp = lerp(_TangentParam.xyz, _T_var, _BlenfTangent); float3 T = normalize(mul(float3(temp.xy,0), tangentTransform)); 主要是通过改变切线的xy值来造成头发高光部分的多样性。 高光部分,按公式计算即可: float StrandSpecular(float3 T, float3 V, float3 L, float exponent) { float3 H = normalize(L + V); float dotTH = dot(T, H); float sinTH = sqrt(1 - dotTH*dotTH); float dirAtten = smoothstep(-1, 0, dotTH); return dirAtten*pow(sinTH, exponent); } 注意,为了模拟的更贴近真实性,应用两层高光,第一层高光代表直射光直接反射出去,第二层代表次表面散射现象具体看代码。 最终渲染部分: float4 HairLighting(float3 T, float3 N, float3 L, float3 V, float2 uv, float3 lightColor) { float diffuse = saturate(lerp(0.25, 1.0, dot(N, L)))*lightColor; float3 indirectDiffuse = float3(0, 0, 0); indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light float3 H = normalize(L + V); float LdotH = saturate(dot(L, H)); float3 specular = _Specular*StrandSpecular(T, V, L, exp2(lerp(1, 11, _Gloss))); //float specMask = tex2D(_SpecMask, TRANSFORM_TEX(uv, _SpecMask)); specular += /*specMask*/_SubColor*StrandSpecular(T, V, L, exp2(lerp(1, 11, _ScatterFactor))); float4 final; float4 base = tex2D(_MainTex, TRANSFORM_TEX(uv, _MainTex)); float3 diffuseColor = (_Color.rgb*base.rgb); //float ao = tex2D(_AO, TRANSFORM_TEX(uv, _AO)).g; final.rgb = (diffuse + indirectDiffuse)*diffuseColor + specular*lightColor* FresnelTerm(_Specular, LdotH); //final.rgb *= ao; final.a = base.a; clip(final.a - _CutOff); return final; } 这里我注释掉了AO和高光遮罩,需要的同学可以加上。 最后一点为了不让头发的边经过clip之后太硬,需要进行两个通道的belnd。 第二个pass使用以下指令: Blend SrcAlpha OneMinusSrcAlpha ZWrite Off 注意第二个通道无需再进行clip操作。 至此,头发渲染完毕。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值