shader思路以及解决方案汇总:
手游性能指标
三角形数上限 的建议是 200K
---------------------------------------
#0.调试shader
Nsight调试
Adreno Profiler
---------------------------------------
#1. 下雨:雨丝,潮湿模拟,涟漪,水花,积水,雷电,水交互
1.1雨丝实现方法:
1.1.1.粒子
优点:容易表现方向和深度。缺点:消耗大,很难表现密集
1.1.2.在屏幕空间做一个纹理动画(后处理,首选)
优点:密集与否性能消耗都一样
难点:雨的方向与视角变换;深度视差;场景遮挡,湿滑的衣服质感
方向:控制椎体的倾斜
场景遮挡:在3D空间中创建一个椎体包围相机,在椎体上做纹理动画,通过调整椎体的倾斜度表现雨的方向,还可以通过增大椎体倾斜和拉长、加快纹理动画表现出镜头移动加速的效果。
椎体的上下两个顶点解决了向上向下看的问题
远近层次,不规则性模拟:四层纹理,表现远近,越远的层纹理tilling值越大,运动速度越慢,而且每层的动画方向稍有不同,表现出雨滴运动的不规则、交错感
潮湿模拟:调整材质的Gloss、Specular,加深diffuse
---------------------------------------
#2.次表面散射
常用于皮肤,牛奶,奶油奶酪,番茄酱,土豆等
基于光源深度图实现sss图
简单的用深度映射模拟吸收的方式。该方法的基本思想是:光在材质中的传播的越远,它的散射和吸收的越厉害。为模拟此效果我们需要计算光在材质中传播距离。计算传播距离的方式则是通过深度图,具体步骤如下:
1. 以光源为视点渲染整个场景,存储光源到物体对象的最近距离。
2. 在相机空间绘制对象时,先将顶点数据转化到光源视点空间,由光源深度图读出深度信息与该顶点在光源空间距离灯光的距离(深度)做差,求出光在物体对象中的传播距离如下图:
注: 在灯光空间绘制深度图时,记录Object对象距离灯光的最近距离如di1 di2(Object 上半部分)。 在视图空间绘制时do1 do2时,
先将do1 do2转化到灯光空间(转化到灯光空间后 di2 do2将会有相同的x y值 zi2 zo2为其在灯光空间的深度信息也就是到灯光的距离)用两者在灯光空间深度值做差可以计算出光在该点传播的距离。
3. 由距离s可以通过美术给的一张一维颜色索引取得颜色值,也可以通过指数e(-s)*lightColor计算最终颜色。
次表面散射的实时近似方法,总结起来有三个要点:
https://www.jianshu.com/p/3645fc40d0ff
https://gitee.com/lengfuganjue/Game-Programmer-Study-Notes/blob/master/Content/%E3%80%8AGPU%20Gems%201%E3%80%8B%E5%85%A8%E4%B9%A6%E6%8F%90%E7%82%BC%E6%80%BB%E7%BB%93/README.md?_from=gitee_search#42-%E7%AE%80%E5%8D%95%E7%9A%84%E6%95%A3%E5%B0%84%E8%BF%91%E4%BC%BC%EF%BC%88simple-scattering-approximations%EF%BC%89
1) 基于环绕照明(Warp Lighting)的简单散射近似,Oren-Nayar光照模型。
Lambert漫反射提供的照明度是0。而环绕光照修改漫反射函数,使得光照环绕在物体的周围,越过那些正常时会变黑变暗的点。这减少了漫反射光照明的对比度,从而减少了环境光和所要求的填充光的量。环绕光照是对Oren-Nayar光照模型的一个粗糙的近似。原模型力图更精确地模拟粗糙的不光滑表面
wrap变量为环绕值,是一个范围为0到1之间的浮点数,用于控制光照环绕物体周围距离
2) 使用深度贴图来模拟半透明材质的最重要特性之一——吸收(Absorption)。
在第一个通道(first pass)中,我们从光源的视点处渲染场景,存储从光源到某个纹理的距离。然后使用标准的投射纹理贴图(standard projective texture mapping),将该图像投射回场景。在渲染通道(rendering pass)中,给定一个需要着色的点,我们可以查询这个纹理,来获得从光线进入表面的点(d_i)到光源间距离,通过从光线到光线离开表面的距离(d_o)里减去这个值,我们便可以获得光线转过物体内部距离长度的一个估计值(S)
3)基于纹理空间中的漫反射模拟(Texture-Space Diffusion),来模拟次表面散射最明显的视觉特征之一——模糊的光照效果
可以用顶点程序展开物体的网格,程序使用纹理坐标UV作为顶点的屏幕位置。程序简单地把[0,1]范围的纹理坐标重映射为[-1,1]的规范化的坐标。
另外,为了模拟吸收和散射与波长的相关的事实,可以对每个彩色通道分为地改变滤波权重
关键词;
皮肤渲染(Skin Rendering)
次表面散射(Subsurface Scattering)
纹理空间漫反射(Texture-Space Diffusion)
环绕照明(Warp Lighting)
深度映射(Depth Maps)
---------------------------------------
#3.皮肤,两种做法
1.预积分皮肤,也叫曲率皮肤,效率较高,游戏里选这个
https://blog.csdn.net/toughbro/article/details/7391935 <- 曲率图的计算
https://zhuanlan.zhihu.com/p/43244741
https://zhuanlan.zhihu.com/p/70390192
https://zhuanlan.zhihu.com/p/43239846
公式理解:使用用积分,根据profile计算球面上每一点入射的光线,经过球体的折射之后从某一点出射的光通量。这个光通量基本上是跟球体的半径成反比的。
曲线的曲率定义(curvature)就是针对曲线上某个点的切线方向角对弧长的转动率,通过微分来定义,表明曲线偏离直线的程度。数学上表明曲线在某一点的弯曲程度的数值。
LUT图:所有的入射光线,通过散射之后出射的光通量跟曲率近似成正比关系。通过一系列的复杂计算和经验积累,加上法线和光线方向的影响,得出左黑右白的曲率图,可参看以上链接
从美术角度理解这张LUT图:图用来模拟明暗交界线处颜色会要更加饱和这一现象,加上距离越远出射光越少
曲率图需要模糊:曲率图如果不进行模糊处理的话,会呈现块状的效果
比较取巧的方法:Matcap图辅助控制光照过渡区域的高饱和效果
2.Screen-Space Subsurface Scattering,这种方法效率较差
http://blog.csdn.net/wolf96/article/details/49678163
是依赖次表面散射,而散射体现出来的效果包括反射,漫射和透射。Unity内置的BRDF1公式,可以基本满足我们对于皮肤表层正常反射的要求,我们通过一个受一张深度渲染控制的高斯模糊后处理来实现漫射效果,
从而模拟出皮肤的柔软质感,即Screen-Space Subsurface Scattering, 然后再通过一张Cull Front Shader渲染出的深度图,进行对物体厚度的模拟,进而模拟出光线透射的效果,最后再加上Unity官方的Postprocessing
---------------------------------------
#4.光照模型比较
Lambert 光照模型(环境光+漫反射)
Idiff = kd * Ia + kd * Il * (N·L) = kd * Ia + kd * Il * dot(N, L)
Ia 是环境光的强度
kd 为材质对环境光的反射系数(0 < kd < 1)
Il 是方向光的强度
kd 为材质对环境光的反射系数(0 < kd < 1)
θ 是入射光方向和顶点法线的夹角。当夹角为 0°,说明入射光平行于法线(垂直于表面),此时反射强度最大;当夹角为 90° 时,说明入射光同表面顶点切线平行,此时物体不会反射任何光线。
N 顶点单位法向量
L 与从顶点指向光源的单位向量
Unity 中的 Lambert 光照模型的源码:
inline fixed4 UnityLambertLight (SurfaceOutput s, UnityLight light)
{
fixed diff = max (0, dot (s.Normal, light.dir));
fixed4 c;
c.rgb = s.Albedo * light.color * diff;
c.a = s.Alpha;
return c;
}
Half Lambert 光照模型(环境光+漫反射+提亮暗部)
Half Lambert 用来给在比较暗的区域显示物体
inline half4 LightingCustomLambert(SurfaceOutput s, half3 lightDir, half atten)
{
// Lambert
half diffLight = dot(s.Normal, lightDir);
// half Lambert
diffLight = diffLight * 0.5 + 0.5;
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (diffLight * atten * 1);
c.a = s.Alpha;
return c;
}
在 Lambert 的基础上,通过 diffLight = diffLight * 0.5 + 0.5,使得 diffLight 变大了从而增强了在光线暗的区域的视觉效果。
Phong 光照模型(环境光+漫反射+高光反射)
Phong在Lambert模型的基础上,加入对光滑表面的高光反射
Ispec = ks * Il * (R·V) ^ p
R + L = 2 * N * (N·L) = > R = 2 * N * (N·L) - L
得到:Ispec = ks * Il * ((2 * N * (N·L) - L)·V) ^ p
ks 是材质的镜面反射系数
Il 是光强
R 为反射光的方向
V 表示从顶点到视点的方向
p 是高光指数,p 越大反射越集中,当慢慢视线方向偏离反射方向光线开始慢慢衰减,反之 p 越小观察到的光斑区域也就越小,反射光强度也很弱。
N 单位法向量
L 从顶点指向光源的单位向量
Blin-Phong光照模型(环境光+漫反射+高光反射,表现和Phong 光照模型差不多,但优化了效率)
Ispec = ks * Il * (N·H) ^ p
ks 是材质的镜面反射系数
Il 是光强
N 为入射点的单位法向量
H 表示光线方向和视角方向的半角向量
p 是高光指数,p 越大反射越集中,当慢慢视线方向偏离反射方向光线开始慢慢衰减,反之 p 越小观察到的光斑区域也就越小,反射光强度也很弱
用到了视角方向和光线方向构成的半角向量
Unity 中的 BlinnPhong 光照模型的代码:
inline fixed4 UnityBlinnPhongLight (SurfaceOutput s, half3 viewDir, UnityLight light)
{
half3 h = normalize (light.dir + viewDir);
fixed diff = max (0, dot (s.Normal, light.dir));
float nh = max (0, dot (s.Normal, h));
float spec = pow (nh, s.Specular*128.0) * s.Gloss;
fixed4 c;
c.rgb = s.Albedo * light.color * diff + light.color * _SpecColor.rgb * spec;
c.a = s.Alpha;
return c;
}
首先计算了光线方向和视角方向的半角向量 h,接着计算了 Lambert 光照模型中计算光强的乘法因子 diff,然后又计算了法向量和 h 的点积 nh,
最后通过指数计算得到了高光乘法因子 spec,最终输出就是 Lambert 光照模型得到的漫反射值以及 BlinnPhong 光照模型得到的高光反射值的和
BRDF
基于物理的光照模型
一次反射光照的计算是在光线交点的法线半球上的球面积分
双向反射分布函数(Bidirectional Reflectance Distribution Function,BRDF)用来定义给定入射方向上的辐射照度(Irradiance)如何影响给定出射方向上的辐射率(Radiance)。
宏观来看,它描述了入射光线经过某个表面反射后如何在各个出射方向上分布——可以是从理想镜面反射到漫反射、各向同性(Isotropic)或者各向异性(Anisotropic)的各种反射。
计算机图形学中实现BRDF理论模型的一个方法是用微小面元对物体表面进行建模,每一个小平面都是表面上的一个小平面镜,具有随机的大小和角度。这些小平面通常被赋予一个高斯分布的尺寸和角度。
BSSRDF
基于物理的光照模型
用于模拟皮肤,BSSRDF可以指定不同的光线入射位置和出射的位置
每一次反射在物体表面上每一个位置都要做一次半球面积分,是一个嵌套积分
最初来源于Jensen在2001年的论文是次表面材质建模最重要的一篇论文[Jensen, Henrik Wann, Stephen R. Marschner, Marc Levoy, and Pat Hanrahan. 2001. "A Practical Model for Subsurface Light Transport." In Proceedings of SIGGRAPH 2001.],
推导了许多重要的物理公式,计算模型,渲染时的参数转换,以及测量了许多生活中常见材质的散射系数等
#5.《镇魔曲》手游水、皮肤材质渲染的思路
https://www.zhihu.com/question/265769844
第一个思路:
皮肤用Pre-Integrated Skin 或者 pbr + translucent 简单模拟都是可以的。下面是translucent函数。// Translucency.
half3 transLightDir = lightDir + s.Normal * _Distortion;
float transDot = pow(max(0, dot(viewDir, -transLightDir)), _Power ) * _Scale;
fixed3 transLight = atten * transDot * s.Alpha * _SubColor.rgb;
fixed3 transAlbedo = s.Albedo * _LightColor0.rgb * transLight;
只是指个方向和关键之处,计算的结果最后需要用一张mask图控制一下。从贴图上大致看是否一致。但感觉题主更像Pre-Integrated那种,或者两者混合。对于unity5x 不再是 (atten * 2)了。
水的话运行可以简单一个法线uv动画叠加搞定,至于深度,可以在摄像机上开启Depth_camera.depthTextureMode |= DepthTextureMode.Depth;这样在shader中可以访问屏幕depth, 这里简单按深度差线性计算了一个alpha值UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
//vert shader:
o.projPos = ComputeScre