首页 > 代码库 > unity3d 模拟人体皮肤
unity3d 模拟人体皮肤
本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 模拟人体皮肤