渲染原理:
第一步:
分层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);
}
}