首页 > 代码库 > unity3d 模拟人体皮肤

unity3d 模拟人体皮肤

继续对《unity着色器和屏幕特效开发秘籍》进行学习

本shader可模拟出类似人体皮肤的效果

作者总结出模拟人体皮肤重点的四要素:
1.次表面散射
简称3S,3S的最大用处之一在于表现灯光照射下的人的皮肤,在人体皮肤比较薄部位,可以透过皮肤产生血色,或可见的血管。
说得简单一些就是:光射进表面,在材质里散射,然后从与射入点不同的地方射出表面。
举个例子,当隔着手指打开手电筒看到的效果就是次表面反射。
2.漫反射
漫反射,是投射在粗糙表面上的光向各个方向反射的现象,我们经常用到的。这里同时还需要BRDF技术产生皮肤对光照的影响。
3.镜面高光
皮肤高光与皮肤的油性程度有关。这里要加入菲涅尔和边缘光技术来控制高光位置。使高光逼真的在表面分布。
4.法线模糊:不让法线信息特别精确,产生一个柔和的效果。

这个shader需要一个BRDF贴图来模拟人体不同皮肤的颜色。



我们能明显的看出不同的肤色 = =;


还需要一个法线贴图:




新建一个shader:


先浏览一下变量:

_BumpMap            法线贴图
_CurveScale          曲率程度
_BumpBias            法线贴图的细节程度
_BRDF BRDF         贴图
_FresnelVal            菲涅尔强度
_RimColor              边缘光的颜色
_RimPower            边缘光的强度
_SpecIntensity       反射强度
_SpecWidth           反射高光的宽度



	Properties 
	{
		_MainTint ("Global Tint", Color) = (1,1,1,1)
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BumpMap ("Normal Map", 2D) = "bump" {}//法线贴图
		_CurveScale ("Curvature Scale", Range(0.001, 0.09)) = 0.01//曲率程度
		_BumpBias ("Normal Map Blur", Range(0, 5)) = 2.0
		_BRDF ("BRDF Ramp", 2D) = "white" {}
		_FresnelVal ("Fresnel Amount", Range(0.01, 0.3)) = 0.05
		_RimColor ("Rim Color", Color) = (1,1,1,1)
		_RimPower ("Rim Falloff", Range(0, 5)) = 2 
		_SpecIntensity ("Specular Intensity", Range(0, 1)) = 0.4
		_SpecWidth ("Specular Width", Range(0, 1)) = 0.2
	}



然后我们需要声明一下:

让着色器加载我们定义的新的函数SkinShader
#pragma surface surf SkinShader
告诉着色器使用shader model3.0的渲染模式
#pragma target 3.0
仅用DirectX 9的渲染器编译着色器
#pragma only_renderers d3d9




#pragma surface surf SkinShader
#pragma target 3.0
#pragma only_renderers d3d9


声明一个新的SurfaceOutput结构体:
增加了我们自定义的模糊法线向量曲率信息


		struct SurfaceOutputSkin
		{
			fixed3 Albedo;
			fixed3 Normal;
			fixed3 Emission;
			fixed3 Specular;
			fixed Gloss;
			fixed Alpha;
			float Curvature;//曲率
			fixed3 BlurredNormals;//模糊的法向量
		};

还需要自己定义Input结构体
worldPos             世界坐标
worldNormal       世界法线


<span style="font-size:18px;">		struct Input 
		{
			float2 uv_MainTex;
			float3 worldPos;
			float3 worldNormal;
			INTERNAL_DATA//可获取新法线
		};</span>



关于INTERNAL_DATA宏,查到了一个关于他的讨论查看这里
这里有人说,当你定义 INTERNAL_DATA, IN.worldRefl 包含了 world view 当你不定义dINTERNAL_DATA包含 world reflection。


所有的数据都建立完毕之后,就开始变写自定义光照函数,
首先单位向量化各方向向量
再求模糊法线与光照方向的点积
NdotL = dot(s.BlurredNormals, lightDir);
求出半角向量halfVector
float3 halfVec = normalize ( lightDir + viewDir );
 

  
再根据BRDF贴图获取BRDF的颜色值    
NdotL * 0.5 + 0.5受光源和法线角度影响    atten是光的衰减值,
它的值越大颜色越亮(越白)
通过改变uv值改变BRDF上的颜色值(此处仅对本帖图有效)
float3 brdf = tex2D(_BRDF, float2((NdotL * 0.5 + 0.5) * atten, s.Curvature)).rgb;


再求菲涅尔值
float fresnel = saturate(pow(1 - dot(viewDir, halfVec), 5.0));
    
通过我们外界赋予的_FresnelVal可动态改变菲涅尔值
fresnel += _FresnelVal * (1 - fresnel);
边缘光照
float rim = saturate(pow(1 - dot(viewDir, s.BlurredNormals), _RimPower)) * fresnel;



初次反射高光计算
取法线与半角向量的点积
float specBase = max(0, dot(s.Normal, halfVec));
反射高光:初次反射高光的128*s.Specular次方再乘高光反射中的强度系数Gloss        
float spec = pow (specBase, s.Specular * 128.0) * s.Gloss;
定义该返回的值
fixed4 c;

最终颜色值
(对光源的反射率 * BRDF肤色值 * 光源颜色值 * 光的衰减值) + (反射高光 + (边缘光照 * 边缘光照颜色))        
c.rgb = (s.Albedo * brdf * _LightColor0.rgb * atten) + (spec + (rim * _RimColor));
返回值,传入surf函数;


inline fixed4 LightingSkinShader (SurfaceOutputSkin s, fixed3 lightDir, fixed3 viewDir, fixed atten)
		{
			viewDir = normalize(viewDir);
			lightDir = normalize(lightDir);
			s.Normal = normalize(s.Normal);
			//单位向量化各方向
			float NdotL = dot(s.BlurredNormals, lightDir);
			//模糊的法向量与光照方向的cos值
			float3 halfVec = normalize ( lightDir + viewDir );
			//atten光照的衰减值
			float3 brdf = tex2D(_BRDF, float2((NdotL * 0.5 + 0.5) * atten, s.Curvature)/*uv*/).rgb;//brdf贴图
			float fresnel = saturate(pow(1 - dot(viewDir, halfVec), 5.0));
			//菲涅尔
			fresnel += _FresnelVal * (1 - fresnel);//
			float rim = saturate(pow(1 - dot(viewDir, s.BlurredNormals), _RimPower)) * fresnel;//rim 环绕

			float specBase = max(0, dot(s.Normal, halfVec));//
			float spec = pow (specBase, s.Specular * 128.0) * s.Gloss;//gloss高光反射中的强度系数

			fixed4 c;
			c.rgb = (s.Albedo * brdf * _LightColor0.rgb * atten) + (spec + (rim * _RimColor));//Albedo光源的反射率
			c.a = 1.0f;
			return c;
		}



surf函数:
获取普通法线
fixed3 normals = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
  


获取模糊法线:  
_Bumpbias  是细节程度Level of detail bias value
    
tex2Dbias函数,允许我们偏移或者调高或调低纹理分辨率等级,
用来获得皮肤上精彩的柔和的漫反射光照。详细请看http://http.developer.nvidia.com/Cg/tex2Dbias.html
  
float3 normalBlur = UnpackNormal ( tex2Dbias ( _BumpMap, float4 ( IN.uv_MainTex, 0.0, _BumpBias ) ) );




curvature计算曲率
            
WorldNormalVector函数,通过输入的点及这个点的法线值,来计算它在世界坐标中的方向
    
fwidth函数返回 绝对值abs(ddx(x)) + abs(ddy(x))    only supported in pixel shaders
只支持象素shader    
ddx(x)函数,返回关于屏幕坐标x轴的偏导数

fwidth模型表面向量变化的快慢程度(因为是偏导数)  详细请看  http://http.developer.nvidia.com/Cg/fwidth.html


length函数,计算向量的欧几里得长度   详细请看 http://http.developer.nvidia.com/Cg/length.html
    
float curvature = length(fwidth(WorldNormalVector(IN, normalBlur)))/
length(fwidth(IN.worldPos)) * _CurveScale;
曲率
用来检索BRDF贴图



最后赋值。。完毕



然后最终我们得到这么一个效果,
不得不说次表面散射效果确实很好:




全部代码如下:  


Shader "Custom/shaderTest" {
	Properties 
	{
		_MainTint ("Global Tint", Color) = (1,1,1,1)
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BumpMap ("Normal Map", 2D) = "bump" {}//法线贴图
		_CurveScale ("Curvature Scale", Range(0.001, 0.09)) = 0.01//曲率程度
		_BumpBias ("Normal Map Blur", Range(0, 5)) = 2.0
		_BRDF ("BRDF Ramp", 2D) = "white" {}
		_FresnelVal ("Fresnel Amount", Range(0.01, 0.3)) = 0.05
		_RimColor ("Rim Color", Color) = (1,1,1,1)
		_RimPower ("Rim Falloff", Range(0, 5)) = 2 
		_SpecIntensity ("Specular Intensity", Range(0, 1)) = 0.4
		_SpecWidth ("Specular Width", Range(0, 1)) = 0.2
	}

	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		LOD 200

			CGPROGRAM
#pragma surface surf SkinShader
#pragma target 3.0
#pragma only_renderers d3d9

			sampler2D _MainTex;
		sampler2D _BumpMap;
		sampler2D _BRDF;
		float4 _MainTint;
		float4 _RimColor;
		float _CurveScale;
		float _BumpBias;
		float _FresnelVal;
		float _RimPower;
		float _SpecIntensity;
		float _SpecWidth;

		struct SurfaceOutputSkin
		{
			fixed3 Albedo;
			fixed3 Normal;
			fixed3 Emission;
			fixed3 Specular;
			fixed Gloss;
			fixed Alpha;
			float Curvature;//曲率
			fixed3 BlurredNormals;//模糊的法向量
		};

		struct Input 
		{
			float2 uv_MainTex;
			float3 worldPos;
			float3 worldNormal;
			INTERNAL_DATA//可获取新法线
		};

		inline fixed4 LightingSkinShader (SurfaceOutputSkin s, fixed3 lightDir, fixed3 viewDir, fixed atten)
		{
			viewDir = normalize(viewDir);
			lightDir = normalize(lightDir);
			s.Normal = normalize(s.Normal);
			//单位向量化各方向
			float NdotL = dot(s.BlurredNormals, lightDir);
			//模糊的法向量与光照方向的cos值
			float3 halfVec = normalize ( lightDir + viewDir );
			//atten光照的衰减值
			float3 brdf = tex2D(_BRDF, float2((NdotL * 0.5 + 0.5) * atten, s.Curvature)/*uv*/).rgb;//brdf贴图
			float fresnel = saturate(pow(1 - dot(viewDir, halfVec), 5.0));
			//菲涅尔
			fresnel += _FresnelVal * (1 - fresnel);//
			float rim = saturate(pow(1 - dot(viewDir, s.BlurredNormals), _RimPower)) * fresnel;//rim 环绕

			float specBase = max(0, dot(s.Normal, halfVec));//
			float spec = pow (specBase, s.Specular * 128.0) * s.Gloss;//gloss高光反射中的强度系数

			fixed4 c;
			c.rgb = (s.Albedo * brdf * _LightColor0.rgb * atten) + (spec + (rim * _RimColor));//Albedo光源的反射率
			c.a = 1.0f;
			return c;
		}




		void surf (Input IN, inout SurfaceOutputSkin o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			fixed3 normals = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
			//normalBlur模糊后的法线
			//tex2Dbias用来获得皮肤上精彩的柔和的漫反射光照。这允许我们偏移或者调高或调低纹理分辨率等级//http://http.developer.nvidia.com/Cg/tex2Dbias.html
			float3 normalBlur = UnpackNormal ( tex2Dbias ( _BumpMap, float4 ( IN.uv_MainTex, 0.0, _BumpBias ) ) );//_Bumpbias细节程度Level of detail bias value
			//curvature计算曲率
			//WorldNormalVector通过输入的点及这个点的法线值,来计算它在世界坐标中的方向
			//fwidth 绝对值abs(ddx(x)) + abs(ddy(x)).only supported in pixel shaders
			//ddx(x) 	返回关于屏幕坐标x轴的偏导数
			//fwidth模型表面向量变化的快慢程度//http://http.developer.nvidia.com/Cg/fwidth.html
			//length计算向量的欧几里得长度//http://http.developer.nvidia.com/Cg/length.html
			float curvature = length(fwidth(WorldNormalVector(IN, normalBlur)))/
				length(fwidth(IN.worldPos)) * _CurveScale;//检索brdf

			o.Normal = normals;
			o.Albedo = c.rgb * _MainTint;
			o.Curvature = curvature;
			o.Specular = _SpecWidth;
			o.Gloss = _SpecIntensity;
			o.Alpha = c.a;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}




                                                                          ----------by  wolf96



unity3d 模拟人体皮肤