unity shader - 毛发渲染,飘逸的毛发

渲染原理:
第一步:
分层layer,渲染不同长度的毛发。 层越多,绘制的越细节。
因为毛发有长短,所以,在上层,没有毛发的部分alpha设置为0,不显示
多个layer,可以使用shader等多个pass进行绘制
每一个 Pass 即表示一层。 当渲染每一层时, 使用法线将顶点位置挤出模型表面
第二步:
根据噪音贴图, 在随机毛发的长度。控制alpha的值
层次不同,毛发的uv位置偏移应该也有所不同,这样制作的才能更自然,否则uv一直不变,那么毛发的方向不变就变成了刺猬
毛发不同层次的偏移,也可能收到外力的影响,比如,风等,所以外边可以设置一个偏移量
第三步
处理环境光,反射和漫反射光等影响,设置颜色

实现:

1 多pass渲染。 对于皮毛来说,每一层的渲染方式是一样的,所以采用 在每个pass中引用, 避免重复代码。
不同的层次需要传参数控制表示. 用 【 1/总的层数 】 就是每一层毛所对应的长度
//表面皮肤渲染
Pass
{
    CGPROGRAM
               
    #pragma vertex vert_surface//点着色器 , 对应引用cginc中的方法
    #pragma fragment frag_surface// 片段着色器 , 对应引用cginc中的方法
    #define FURSTEP 0.0 
    #include "FurHelper.cginc" // 引用的shader
               
    ENDCG
               
}
// 毛的渲染
Pass
{
    CGPROGRAM
               
    #pragma vertex vert_base //点着色器 , 对应引用cginc中的方法
    #pragma fragment frag_base // 片段着色器 , 对应引用cginc中的方法
    #define FURSTEP 0.05 // 皮毛的长度
    #include "FurHelper.cginc" // 引用的shader
               
    ENDCG
               
}

2 顶点外扩, 实现多层次偏移
思路:在顶点的法线方向,向外扩展一定的距离。 所以在顶点着色器中处理
            v2f vert(a2v v)
            {
                v2f o;
                float3 OffetVertex = v.vertex.xyz + v.normal * _LayerOffset *_FurLength;//顶点外扩
                OffetVertex += mul(unity_WorldToObject, _FurOffset);//顶点受力偏移【加一个方向上的偏移,看起来更自然,例如向下垂】
            }

3 使用噪音贴图,进行透明度处理
思路: 利用uv2 存储噪音贴图, 然后根据噪音贴图随机性,处理显示毛发的透明度 。 
fixed3 noise = tex2D(_FurTex, i.uv.zw ).rgb; 
fixed alpha = saturate(noise - (_LayerOffset * _LayerOffset + _LayerOffset * _FurDensity));

4 优化处理。提炼参数
    · 毛发长度参数。限制最外层的扩展长度
    · 毛发层数
    · 颜色参数
    · 毛发密集参数(噪音贴图中使用,控制uv的tilling)
    · 添加偏移参数 (例如, 长毛宠物的毛会下垂)
5 *优化处理, 添加毛发移动
使用c#代码控制毛发,缓慢飘逸移动 【lerp】
思路:
shader只渲染一层毛发
c#代码控制,克隆多个游戏对象, 都赋值该毛发的shader,通过改变偏移量,生成多个【需要多少层就是生成多少层】
c#代码中,update中lerp控制每一层毛发缓慢移动。 从而实现飘逸的移动
// 生成, material赋值, shader赋值
void CreateShell()
{
    CheckParent();
    layers = new GameObject[LayerCount];
    float furOffset = 1.0f/ LayerCount;
    for (int i = 0; i< LayerCount; i++)
    {
        //复制渲染的原模型一遍
        GameObject layer = Instantiate(Target.gameObject, Target.transform.position, Target.transform.rotation);
        layer.transform.parent = _parent;
        layer.GetComponent<Renderer>().sharedMaterials = new Material[1];
        layer.GetComponent<Renderer>().sharedMaterial = ResetSharedMaterials(i, furOffset);
        layers[i] = layer;
    }
}

// shader 赋值
Material ResetSharedMaterials(int index, float furOffset)
{
    Material special = new Material(ShellShader);
    if (special != null)
    {
        special.SetTexture("_MainTex", Target.sharedMaterial.GetTexture("_MainTex"));
        special.SetColor("_Color", FurColor);
        special.SetColor("_RootColor", FurRootColor);
        special.SetColor("_RimColor", FurRimColor);
        special.SetColor("_Specular", FurSpecularColor);
        special.SetFloat("_Shininess", Shininess);
        special.SetFloat("_RimPower", RimPower);
        special.SetFloat("_FurShadow", FurShadow);
        special.SetTexture("_FurTex",FurPattern);
        special.SetFloat("_FurLength", FurLength);
        special.SetFloat("_FurDensity", FurDensity);
        special.SetFloat("_FurThinness", FurThinness);
        special.SetFloat("_LayerOffset", index * furOffset);            //不同Shell的偏移参数不一样
        special.SetVector("_FurOffset", FurForce* Mathf.Pow(  index*furOffset,FurTenacity));//计算受力、层数和硬度共同影响的Shell偏移
        special.renderQueue = 3000 + index;//由于在单通道渲染半透明材质,进行了深度写入,为了防止被深度剔除,所以要手动更改渲染队列
    }
    return special;
}


// 移动
void UpdateShellTrans()
{
    if (layers == null || layers.Length == 0)
    {
        return;
    }
    for (int i = 0; i < layers.Length; i++)
    {
        //位置和旋转Lerp到目标模型
        layers[i].gameObject.transform.position = Vector3.Lerp(layers[i].gameObject.transform.position, Target.transform.position, lerpSpeed * Time.deltaTime *20);
        layers[i].gameObject.transform.rotation = Quaternion.Lerp(layers[i].gameObject.transform.rotation, Target.transform.rotation, lerpSpeed * Time.deltaTime *10);
    }
}

头发各向异性渲染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、付费专栏及课程。

余额充值