本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