先放出结果图片。。。由于网上下的模型是拼的,所以眼皮,脸颊,嘴唇看起来像存在裂痕,解决方式是加入曲面细分和置换贴图 进行一定隆起,但是博主试了一下fragment shader的曲面细分,虽然细分成功了但是着色效果变的很奇怪,这里就不用曲面细分了,大家如果有在fragment shader上用曲面细分的好办法,可以的话请告诉我

参数设置1

参数设置2

细致到毛孔的高光

次表面散射的耳朵

人皮渲染是十多年的课题了,人们想尽一切办法想让其变得真实可信,大型3A级次时代游戏近来做的又来越真实了如《罗马之子》,他们的皮肤自称已经超过了NVIDIA的例子

这是在2005年SIGGRAPH的多层皮肤渲染,他们的参数都是经过精密的医学上的测量的,而且渲染花费了5分钟的时间。。。

研究这个找了许多资料,在结合之前的知识弄出了一个看起来还算入眼的人皮
本例做到了以下几点
1.    次表面散射

2.    基于物理的渲染

包括specular和brdf等等,brdf我用了一张贴图调整曲率来代替,specular在之前这篇文章有详细讲解链接在此

3.    法线模糊

等等之类。。。

为什么皮肤渲染这么难?
1.    大多数的漫反射光来自次表面散射
2.    皮肤颜色主要来自上表皮
3.    粉或红色主要为真皮中的血液
此图为人皮的组成模拟,人有好多层表皮,这就说明在真实情况下要进行数次折射与反射,这就更难达到真实

光的折射与反射
 

上图直观的表明了光线“被怎么样了”
光线接触到皮肤时,有大约96%被皮肤各层散射了,只有大约4%被反射

再说specular,人的皮肤会出油,所以就会有反射,但是人的皮肤不能像镜子那样反射,因为人的皮肤是粗糙的,在这种情况下用基于物理的(physically based)方法就最好不过了,没了解过physically based的看官们可以先了解下这篇文章和上面的一样
 
使用了之前试出效果最好的的方法,也就是使命召唤2中用到的的方法,同时试了下beckmann的方法,但是效果并不好,phong等方法也没有试
这里,我们的实现方法是这样的:

<span style="font-size:14px;">            /*
            *this part is compute Physically-Based Rendering
            *the method is in the ppt about "ops2"
            */             float _SP = pow(8192, _GL);
            float d = (_SP + 2) / (8 * PIE) * pow(dot(n2, H), _SP);
            float f = _SC + (1 - _SC)*pow(2, -10 * dot(H, lightDir));
            float k = min(1, _GL + 0.545);
            float v = 1 / (k* dot(viewDir, H)*dot(viewDir, H) + (1 - k));             float all = d*f*v;
            float3 refDir = reflect(-viewDir, n2);
            float3 ref = texCUBElod(_Cubemap, float4(refDir, _nMips - _GL*_nMips)).rgb;</span>

然后发现尽管gloss调到最大也没有达到我们预期的那种效果,
又进行了 “智能补光”
也就是常规的求高光的方式,我们在此加入了高光贴图,不让不该高光的地方(如眉毛)产生高光

			float specBase = max(0, dot(n2, H));
float spec = pow(specBase, 10) *(_GL + 0.2);
spec = lerp(0, 1.2, spec);
float3 spec3 = spec * (tex2D(_SpecularTex, i.uv_MainTex) - 0.1);
spec3 *= Luminance(diff);
spec3 = saturate(spec3);
spec3 *= _SpecularPower;

光经过哪,就带一部分那里的颜色,可以发现光从入射到出射,位置和方向都变了

光走的路径数量是无穷大,光反射回来的都为漫反射,油脂表面的透明度也都是不一样的,
这就产生了次表面散射

 
NVIDIA在GDC2007年的演讲中提到把图像blur个六遍达到柔和的次表面散射效果
每次blur都是在不同的颜色通道以不同的范围和程度进行blur
 
由于我们的贴图是这样的“高配”
 
用在本例上会丢失少许本来贴图上的细节,但是确实有一定的次表面散射效果,各位看官自行取舍,而且千万不要只做高斯模糊,这样的话细节会丢失更多,而且没有什么次表面散射的感觉

为了节省花销,省去了ppt中的rendering时blur,直接在ps上做了6张高斯模糊的贴图放入material,并线性混合

			float3 c = tex2D(_MainTex, i.uv_MainTex) * 128;
c += tex2D(_BlurTex1, i.uv_MainTex) * 64;
c += tex2D(_BlurTex2, i.uv_MainTex) * 32;
c += tex2D(_BlurTex3, i.uv_MainTex) * 16;
c += tex2D(_BlurTex4, i.uv_MainTex) * 8;
c += tex2D(_BlurTex5, i.uv_MainTex) * 4;
c += tex2D(_BlurTex6, i.uv_MainTex) * 2;
c /= 256;

我们同时也起到重要作用的是边缘光rim和brdf,
使用了BRDF最明显的好处是,Brdf贴图间接控制了明暗交界线的颜色,可通过曲率控制,模拟了光与阴影交界处光对皮肤的反射与折射,如果全黑的话说明光只是普通的漫反射。

而且使人皮有了次表面散射的质感

			/*
*this part is to add the sss
*used front rim,back rim and BRDF
*/ float3 rim = (1 - dot(viewDir, n2))*_RimPower * _RimColor *tex2D(_RimTex, i.uv_MainTex);
float3 frontrim = (dot(viewDir, n2))*_FrontRimPower * _FrontRimColor *tex2D(_FrontRimTex, i.uv_MainTex); float3 sss = (1 - dot(viewDir, n2)) / 50 * _SSSPower;
sss = lerp(tex2D(_SSSFrontTex, i.uv_MainTex), tex2D(_SSSBackTex, i.uv_MainTex), sss * 20)*sss; fixed atten = LIGHT_ATTENUATION(i);
float curvature = length(fwidth(mul(_Object2World, float4(normalize(i.normal), 0)))) /
length(fwidth(i.worldpos)) * _CurveScale; float3 brdf = tex2D(_BRDFTex, float2((dot(normalize(i.normal), lightDir) * 0.5 + 0.5)* atten, curvature)).rgb;

对于rim的话添加了前向rim和后向rim其实本质上还是rim,后向rim使用了白色的图片产生一种玉质的感觉(好吧其实更像羊羹),前向rim使用了红色的图片,相当于添加了光线在血液层的散射,让人的脸蛋有了真实的血色

这是次表面散射的结果:

光源在嘴里

像不像把手指或者耳朵放在手电筒前面的那种效果?那就是次表面散射
需要一张Intense strips贴图来混合原有颜色,方法就是在点光源情况下,求出当前点与点光源的距离,距离越近就越亮

关于法线,
用了一种新的混合方式,这样能保有更多法线细节,
这里简单讲解一下法线混合,

float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;
float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;
float3 r = normalize(n1 + n2);
return r*0.5 + 0.5;

大家可能用过这种方式来混合两个法线贴图,这种线性的方式折中了两个贴图,得到的细节权重是平均的,效果并不好,得到的是这样的结果
 

改进了一下,变成了覆盖混合

float3 n1 = tex2D(texBase,   uv).xyz;
float3 n2 = tex2D(texDetail, uv).xyz;
float3 r = n1 < 0.5 ? 2*n1*n2 : 1 - 2*(1 - n1)*(1 - n2);
r = normalize(r*2 - 1);
return r*0.5 + 0.5;

就是法线1的法线比较深的地方,就多一些权重,比较浅的地方就被法线2适当覆盖,但是这样效果还是不够真实
 

在GDC2012的Mastering DX11 with unity中讲到了一种官方的办法如下:

float3x3 nBasis = float3x3(
float3(n1.z, n1.y, -n1.x), //绕着y轴+90度旋转
float3(n1.x, n1.z, -n1.y),// 绕着x轴-90度旋转
float3 (n1.x, n1.y, n1.z )); n = normalize (n2.x*nBasis[0] + n2.y*nBasis[1] + n2.z*nBasis[2]);

得到的结果是这样的,是不是好了许多?

双方的细节程度都有很多提升,

他们用了一个basis来变换第二法线。具体可以看 这篇文章—链接


AutoLight.cginc里定义的一个函数LIGHT_ATTENUATION求出光的衰减atten,atten在directional
light中固定是1,在点光源中才有衰减效果,因为directional light在unity中是没有位置区别的,在哪里都一样。

fixed atten = LIGHT_ATTENUATION(i);

对于细节方面,如毛孔,在本例的贴图和法线贴图都很细致,已经包括毛孔和皮肤的纹路,如果贴图精度低还想要高细节的话,可以再贴上细节
 

全部可设置变量:

全部代码已共享至GitHub链接
                                                       ---- by wolf96

unity3d Human skin real time rendering 真实模拟人皮实时渲染的更多相关文章

  1. unity3d Human skin real time rendering 真实模拟人皮实时渲染(转)

    先放出结果图片...由于网上下的模型是拼的,所以眼皮,脸颊,嘴唇看起来像 存在裂痕,解决方式是加入曲面细分和置换贴图 进行一定隆起,但是博主试了一下fragment shader的曲面细分,虽然细分成 ...

  2. unity3d Human skin real time rendering with blood and water drop effect真实模拟人皮实时渲染之血液和水珠掉落效果

    在之前的一篇(链接在此)文章中写了下关于真实模拟皮肤渲染,在此基础之上又想加上血液效果,在洗澡的时候(=  =:)又想在skin上加上水珠的效果,所以研究了下,做出来效果感觉还不错,放下效果图: 水珠 ...

  3. unity3d Human skin real time rendering plus 真实模拟人皮实时渲染 plus篇

    最近逃课做游戏,逃的有几门都要停考了,呵呵呵,百忙之中不忘超炒冷饭,感觉之前的人皮效果还是不够好,又改进了一些东西 首先上图 放大看细节 显而易见的比上次的效果要好很多,此次我把模型用3dmax进行了 ...

  4. Unity3d 屏幕空间人体皮肤知觉渲染&次表面散射Screen-Space Perceptual Rendering & Subsurface Scattering of Human Skin

    之前的人皮渲染相关 前篇1:unity3d Human skin real time rendering 真实模拟人皮实时渲染 前篇2:unity3d Human skin real time ren ...

  5. 结合 CSS3 & Canvas 模拟人行走的效果

    HTML5 和 CSS3 技术给 Web 带来了新的利器,点燃了 Web 开发人员的激情.所谓只有想不到,没有做不到,的确如此.下面给大家分享一个结合 CSS3 & Canvas 模拟人行走的 ...

  6. DirectUI中模态对话框和菜单的原理(自己控制整个Windows消息循环。或者,用菜单模拟窗体打开时用SetCapture取得控制权,一旦窗体收到WM_CAPTURECHANGED消息就把窗体退出)

    经常有人问关于模态对话框和系统菜单内部实现原理方面的问题, 因为系统通过API隐藏了太多细节,这2个问题确实令初学者甚至是有经验的开发者困扰, 下面是我个人的一些经验总结. 先说模态对话框,外部看模态 ...

  7. python利用requests库模拟post请求时json的使用

    我们都见识过requests库在静态网页的爬取上展现的威力,我们日常见得最多的为get和post请求,他们最大的区别在于安全性上: 1.GET是通过URL方式请求,可以直接看到,明文传输. 2.POS ...

  8. unity3d Hair real time rendering 真实头发实时渲染

    先放上效果 惊现塞拉酱 算法是Weta Digital根据siggraph2003的论文加以改进,改进之前使用的是Kajiya and Kay’s 模型,它能量不守恒,也就是说不是基于物理的,不准确 ...

  9. unity3d Hair real time rendering 真实头发实时渲染(转)

    惊现塞拉酱 算法是Weta Digital根据siggraph2003的论文加以改进,改进之前使用的是Kajiya and Kay’s 模型,它能量不守恒,也就是说不是基于物理的,不准确 电镜下真实头 ...

随机推荐

  1. Universal-Image-Loader 使用步骤

    开源框架利与弊 开源框架给开发者提供了便利,避免了重复造轮子,但是却隐藏了一些开发上的细节,如果不关注其内部实现,那么将不利于开发人员掌握核心技术,当然也谈不上更好的使用它,计划分析项目的集成使用和低 ...

  2. asp.net命名规范

    以下命名规范是在编程中,可以辅助快速编程的良好方式之一,我一点点的整理出来,以便形成自己的编程规范.还有待完善... 0.产品命名规范: 结构 层次 产品 模块 功能 命名规则 UI(界面层) Web ...

  3. Oracle Application Express (APEX 4.2) 中进行ajax调用

    Oracle Application Express 4.2 (APEX 4.2)是一个快捷的web应用开发工具.在开发网页的过程中有时候会用到ajax请求. 建立ajax请求的方式: 1.建立后台的 ...

  4. React学习笔记(二) 组件状态

    组件的状态(this.state): 组件免不了要与用户互动,React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI getIni ...

  5. 利用CART算法建立分类回归树

    常见的一种决策树算法是ID3,ID3的做法是每次选择当前最佳的特征来分割数据,并按照该特征所有可能取值来切分,也就是说,如果一个特征有四种取值,那么数据将被切分成4份,一旦按某特征切分后,该特征在之后 ...

  6. 《C++ Primer》P314中使用insert重写单词统计程序的扩展

    编写程序统计并输出所读入的单词出现的次数 想与习题10-1相结合,也就是先输入几组 map<string, int>类型,存入vector中. 再输入单词word,如果已经存在则在key对 ...

  7. 设计模式之 Singleton 单例模式

    先上两段代码,区别仅在于是否涉及线程安全. 首先是不涉及多线程的单例: public class Singleton { private final static Singleton INSTANCE ...

  8. windows phone 之手势识别(Manipulation)

    在Windows Phone 7的多触摸屏上可以检测到至少四根同时存在的手指,并且一起操作使触摸屏充分发挥效果. 在silverlight开发中通过事件来实现触屏事件的检测,包括低级别的和高级别的接口 ...

  9. JavaScript 高阶函数 + generator生成器

    map/reduce map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果: function pow(x ...

  10. 有效解决js中添加border后动画bug问题

    做了个demo发现如果一个div不加border属性,用对象的offsetWidth属性来控制width没问题,但是如果一旦加了border属性,问题就来了. 其实offsetWidth属性获取的的是 ...