|
编者按:玻璃的质感表现,在游戏中一直都是比较难制作的,因为玻璃包含了很多特殊的特性。本文作者将分享一种非常低开销的实现unity玻璃材质的方法,希望对大家有所帮助。
一直以来玻璃的质感表现在游戏中都是比较难制作的一种,因为玻璃包含了很多特殊的特性:反射、折射、厚度等,而这些特性在渲染的时候需要消耗大量的计算。这也导致真实的玻璃渲染基本都是离线渲染的。
包括目前主机端游戏的一些玻璃效果也不是很理想,离离线品质还有很大的差距。基本都缺少了折射项,只有高光部分。
(图中玻璃杯来自ff7重置版、大表哥2)
本文分享一种非常低开销的实现一种玻璃材质的思路。它基本可以适应目前的任何平台。同时这个方案在迭代上非常灵活,只需要替换灯光反射与环境反射部分就可以运用到PBR上。
它没有复杂的算法,没有光线追踪。
可以没有RT,没有后处理。
主要从美术表现的视觉层面来实现最终效果。
我们先看下实现出来的效果!(以下所有图片均为unity实时截屏,No tonemapping )
下面是模拟的高脚杯,以及香槟。
杯子、啤酒、泡沫,都是同一个shader实现的。
啤酒里面的气泡,以及香槟里面的气泡。
先来分析下玻璃的特点。
玻璃本身是透明的,影响玻璃质感的主要有2个点:
1)反射
玻璃会反射环境里高亮的地方。
2)折射
玻璃越厚的地方折射越强。
而最难的地方就在于折射的计算。
但在人的视觉上,强烈的折射会打断背景的连续性,在折射率强的地方已经无法看清背后的事物,会让人感觉“这块区域不是完全透明的”。我们主要从这一点入手,来模拟视觉层面的折射表现。
为了节省性能我这里以Matcap来制作环境纹理的采样。(不过这里是很灵活的可以根据需求替换PBR的ibl环境以及实时高光)
关于Matcap
全称MaterailCapture,在一张纹理里面储存光照信息,通过模型法线的xy分量去采样,得到在该方向法线的光照信息。详细的请看wiki链接:
http://wiki.unity3d.com/index.php/MatCap
但是以上面的方法计算出来的效果会在物体不居中的时候产生拉伸变形,这里需要对normal进行矫正。这里放上2种方法的代码:
- //MatCap 普通版
- half2 MatCapUV ;
- matCapUV.x = dot(UNITY_MATRIX_IT_MV[0].xyz,v.normal);
- matCapUV.y = dot(UNITY_MATRIX_IT_MV[1].xyz,v.normal);
- matCapUV = matCapUV * 0.5 + 0.5;
- //MatCap 矫正版
- // float3 N = normalize(UnityObjectToWorldNormal(v.normal));
- // float3 viewPos = UnityObjectToViewPos(v.vertex);
- float2 MatCapUV (in float3 N,in float3 viewPos)
- {
- float3 viewNorm = mul((float3x3)UNITY_MATRIX_V, N);
- float3 viewDir = normalize(viewPos);
- float3 viewCross = cross(viewDir, viewNorm);
- viewNorm = float3(-viewCross.y, viewCross.x, 0.0);
- float2 matCapUV = viewNorm.xy * 0.5 + 0.5;
- return matCapUV;
- }
复制代码
这里如果不需要法线贴图可以直接在VS里计算。
反射部分比较简单,可以处理一张玻璃高光的Matcap纹理来处理酒杯的高光反射。
左边是Matcap直接输出,右边是将Matcap当做alpha输出。
- [HDR]_SpColor("Sp Color", Color) = (1.0,1.0,1.0,1.0)
- //给高光一个单独的色彩来控制反射的色彩与强度
- float3 spmatCap= tex2D(_CapTex,matCapuv);
- spmatCap *=_SpColor.rgb
- o.color.rgb = spmatCap ;
- o.color.a = spmatCap .r;
复制代码
3.1 玻璃折射MASK
制作折射效果前,首先我们需要计算出折射的范围,确定哪些地方需要使用折射。
A.玻璃本身厚度范围
我们需要预处理玻璃杯本身玻璃的厚度。比如杯子底部、以及玻璃杯口部分。
这里的厚度可以通过2种方法来储存输出:
1)预处理一张厚度纹理。
2)通过绘制到顶点色来输出。
- float3 thicknessTex= tex2D(_MaskTex, i.uv);
- float sThickness = thicknessTex.r * i.color.r; //杯体本身实心玻璃部分
复制代码
我将纹理储存在R通道里面,其他通道可以为后面留坑。
这样我们可以得到手动可控的厚度范围。
B.玻璃侧面厚度
边缘部分这里使用了菲尼尔来计算。
- _FenierEdge("Fenier Range", Range(-2, 2)) = 0.0
- _FenierIntensity("Fenier intensity", Range(0, 10)) = 2.0
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
- float NoV = dot(N,V);
- float EdgeThickness (in float NoV)
- {
- float ET = saturate((NoV-_FenierEdge)*_FenierIntensity);
- return ET;
- }
复制代码
通过调节参数我们可以得到杯子的边缘范围。
最后将两种范围合并到一起,我们就得到了完整的玻璃折射区域。
- _FenierEdge("FenierRange", Range(-2, 2)) = 0.0
- _FenierIntensity("Fenierintensity", Range(0, 10)) = 2.0
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
- float NoV = dot(N,V);
- float3 thicknessTex= tex2D(_MaskTex, i.uv) ;
- float sThickness = thicknessTex.r * i.color.r; //杯体本身实心玻璃部分
- float fThickness = thicknessTex.g;// 杯体菲尼尔厚度
- float EdgeThickness (in float NoV ,in float eThickness )
- {
- fThickness = (eThickness -0.5)*0.5;
- float ET = saturate((NoV-_FenierEdge+fThickness)*_FenierIntensity);
- return 1-ET*eThickness ;
- }
复制代码
3.2 模拟玻璃折射
折射同样使用matcap来进行处理,但需要新的UV采样。因为我们需要使用上面得到的折射mask来扭曲这张matcap纹理。(可以和高光使用同一张matcap纹理,也可以单独新建一张。我这里偷懒和高光使用的是同一张)
- float Refintensity = Thickness*_Refintensity;
- float3 rfmatCap = tex2D(_RfCapTex,matCapuv+Refintensity);
- float3 rfmatColor= RFLerpColor(rfmatCap,Thickness)
- //_BaseColor添加一个自定义的颜色参数,就可以自由控制玻璃本体色彩
- float3 RFLerpColor (in float3 rfmatCap,in float Thickness)
- {
- float3 c1 = _BaseColor.rgb*0.5;
- float3 c2 = rfmatCap*_BaseColor.rgb;
- float cMask = Thickness;
- return lerp(c1,c2,cMask ); //这里也可以 *v.color.rgb 用顶点色来控制玻璃局部色彩,制作出彩色玻璃的效果
- }
复制代码
折射的表现做好后,只需要把我们之前制作的折射Mask 当做alpha来输出,整个折射部分就制作完毕。
最后将反射与折射合并在一起输出整个效果基本就完成了。
- float alpha = saturate(max(spmatCap.r*_SpColor.a ,Thickness)*_BaseColor.a);
- //_SpColor 是给高光颜色单独一个色彩控制项
- //alpha这里的计算是为了可以分别控制高光的透明度,以及整体杯子的透明度
- col.rgb = rfColor+spColor;//反射与折射合并
- col.a = alpha;
复制代码
3.3 添加法线细节
因为游戏里的模型精度相对较低,为了提升表面细节,我们还可以添加法线贴图。这样就可以制作更加丰富的表现了。
因为上面的 折射MASK 与 Matcap映射,都是基于 N(normal) 来计算 。所以这里只需要将normalMap计算进来就行了。
- o.worldTangent =normalize(UnityObjectToWorldNormal(v.tangent));
- o.worldBinormal = cross(o.worldNorm, o.worldTangent) * v.tangent.w;
- o.uv.zw = TRANSFORM_TEX(v.texcoord.xy,_NormalTex) ;//(给法线单独的UV这样可以使用细节法线,
- 也可以使用多套法线纹理来混合)
- o.uv.xy = TRANSFORM_TEX(v.texcoord.xy,_MaskTex) ;
- //------↑VSout----------------------------------------------------------------------------------------------
- void GetNormal(v2f i, inout float3 N)
- {
- float4 normalTex = tex2D(_NormalTex, i.uv.zw);
- float3 normalTS = normalize(UnpackNormal(normalTex));
- float3x3 tbn = float3x3(i.worldTangent, i.worldBinormal, i.worldNorm);
- N = normalize(mul(normalTS, tbn));
- }
复制代码
PS:如果需要更加真实的表现,可以使用一个RT来制作背后物体被折射扭曲的效果,但其实完全可以不用加。
到此材质思路方面就完结了,下面会用一些实例来讲一下运用方面的细节。
4.1 磨砂玻璃
其实磨砂玻璃在Matcap下非常非常简单,我们只需要将Matcap的纹理拿到Photoshop里面模糊处理下就可以了。
如果是IBL、Phone、GGX等高光反射, 直接按粗糙度方式处理就行。
4.2 多层玻璃和液体
现在我只做了杯子的外表面,玻璃杯内部还没处理。
先看看这个杯子模型,这里有一些需要注意的点。
我把杯子模型分成了3个部分:
区分内外壁的原因主要是为了解决半透明排序引起的前后关系错误的问题。
在这里我们新建1个材质球,也可以将之前的外壁材质球拷贝一份 赋予杯子内壁模型。
内壁材质 :建议调节参数把边缘厚度去掉,除非你做的是双层玻璃杯。杯底部分以厚度mask控制(纹理、顶点色都行)。
添加液体材质
同样的方式创建一个新的液体材质球。修改颜色,可以做出水、啤酒、红酒或其他饮料。
在这里将厚度mask贴图纹理替换成了一张小气泡的纹理。用来模拟啤酒里面的小气泡。
同时用动画给这张纹理采样K关键帧,添加一个UV流动的动画。就能做出啤酒气泡流动的效果。
关于渲染层级
半透明材质---需要修改内部材质球的 RenderQueue 在外部材质的参数上-1。否则在某些角度或者复杂模型的时候深度的前后关系会出错。
例如:上面的啤酒杯的渲染顺序与RenderQueue。
杯子外部(3000)←杯内液体(2999)←杯子内部(2998)
半透明部分:其实就是以我们看到的顺序来排就行。因为我们看到整个杯子的时候优先是外壁,其次看到的是液体部分,最后才是内壁部分。数值越小越优先渲染。
4.3 冰裂效果的玻璃
使用一张裂纹的纹理,搭配顶点色绘制的玻璃基础厚度。能模拟出类似冰裂玻璃的效果。
关于PBR动态环境的适配
可以将环境的球协光照、LightProbes等低配颜色与折射部分做混合,就能随环境颜色变化而变化。
作者:Blood
来源:腾讯GWB游戏无界
原地址:https://mp.weixin.qq.com/s/-ukjq_pJqCCAYHQEcmzO3w
|
|