Mask的艺术.docx
- 文档编号:4890040
- 上传时间:2022-12-11
- 格式:DOCX
- 页数:4
- 大小:20.12KB
Mask的艺术.docx
《Mask的艺术.docx》由会员分享,可在线阅读,更多相关《Mask的艺术.docx(4页珍藏版)》请在冰豆网上搜索。
Mask的艺术
Mask的艺术
Dota2的美术效果一直是所有moba类型游戏的标杆,如果你不是很服这句话的话,请看下面的几个皮肤感受下比XX荣耀还是高明到不知道哪里去了吧。
对于Dota2美术详细的解释可以看v社的角色设计指南,知乎上也有对应的翻译。
环境:
Unity5.3.8资源概览在建模的时候,脚部的多边形面數最低,而头部和躯干上部用到最密集的面。
每一个三角面都不要浪费!
每一个多边形面都要能对表现轮廓有所帮助,同时(或者)对于表现体积的变化有所助益。
避免创建过长的三角面,因为这样的面往往会造成阴影计算错误,特别是在角色运动过程中。
比如食人魔法师
对称建模,然后加入独立的元素打破对称(例如:
臂带,护膝等),镜像贴图会有助于你在有限贴图上获得更多的表现空间。
例如大圣身上的贴图。
整个模型分了很多个submesh,目测是为了方便做avatar,每个部位都可以作为皮肤的一部分替换。
任何不对称建模部分,无论在游戏画面或角色画面下都应该造成最大的可见影响。
独立元素也被用于定义清晰的边缘,这是创造一个细节更为丰富形象的非常厉害的一招。
尽可能使用镜像贴图。
不对称贴图要留给那些在整体形象或游戏画面中最突出的部分。
在右图例子中,所有绿色填充区域的贴图都是对称镜像的。
脸部的UV应该占整个身体贴图的至少25%,这样才能保证形象有足够的细节。
所有单独使用(同时可用于其他角色)的小物品贴图应当新建一张单独的贴图。
UV贴图也有分布梯度,脚部需要分配的UV面积应该最小而头部\躯干上部分配的贴图面积最大。
眼睛应该是一个独立的UV在规划整张贴图上的各区域时,在各区域之间不会过于密集的前提下,尽可能减少各部分贴图之间的浪费空间。
各分块之间确保留下约5~10像素的距离(一些引擎在会在某些情况下压缩贴图,导致贴图分块区域间未留出足够空间的情况产生接缝)把颜色相似的分块排布在贴图中接近的区域,有助于加载较小版本纹理时保持颜色。
对于mask贴图,是存在两张mask贴图的rgba通道里面的,减少采样次数。
然而官方给的贴图都是分开的(为了好理解?
)。
写Shader基础Shader由于并不是正真的pbr,所以基础的还是原始的Blinn-Phong模型,然后慢慢的加上tricks。
下面来一点点搞个最基本的Blinn-Phong模型的diffuse+normal+specularshader,那jugg的模型为例。
只绘制albedo贴图带上normalmap和diffuse加最后加上油腻的高光可以加个wrapdiffuse不要那么黑基础shader的代码Shader'Custom/DotaHero'{Properties{_MainColor('MainColor',Color)=(0.5,0.5,0.5,0.5)_MainTex('MainColorTexture',2D)='white'{}_Wrap('LightWrap',float)=0.25_NormalMap('NormalMap',2D)='bump'{}_BumpDepth('BumpDepth',Range(0.1,4.0))=1}SubShader{Pass{Tags{'LightMode'='ForwardBase'}CGPROGRAM#pragmavertexvert#pragmafragmentfrag#pragmatarget3.0#pragmamulti_compile_fwdadd_fullshadows#include'UnityCG.cginc'#include'AutoLight.cginc'//userdefineduniformsampler2D_MainTex;uniformsampler2D_NormalMap;uniformfloat4_MainTex_ST;uniformfloat4_NormalMap_ST;uniformfloat4_MainColor;uniformfloat_Wrap;uniformfloat_BumpDepth;//unitydefineduniformfloat4_LightColor0;//baseinputstructstructvertexInput{float4vertex:
POSITION;float3normal:
NORMAL;float4texcoord:
TEXCOORD0;float4tangent:
TANGENT;};structvertexOutput{float4pos:
SV_POSITION;float4tex:
TEXCOORD0;float4posWorld:
TEXCOORD1;float3normalWorld:
TEXCOORD2;float3tangentWorld:
TEXCOORD3;float3binormalWorld:
TEXCOORD4;LIGHTING_COORDS(5,6)};//vertexfunctionvertexOutputvert(vertexInputv){vertexOutputo;float4x4modelMatrix=_Object2World;float4x4modelMatrixInverse=_World2Object;o.normalWorld=normalize(mul(float4(v.normal,0.0),_World2Object).xyz);o.tangentWorld=normalize(mul(_Object2World,half4(half3(v.tangent.xyz),0)));o.binormalWorld=normalize(cross(o.normalWorld,o.tangentWorld)*v.tangent.w);o.posWorld=mul(_Object2World,v.vertex);o.pos=mul(UNITY_MATRIX_MVP,v.vertex);o.tex=v.texcoord;TRANSFER_VERTEX_TO_FRAGMENT(o);//forshadowsreturno;}//takea-1to1rangeandfitit0to1floatclamp01(floattoBeNormalized){returntoBeNormalized*0.5+0.5;}float3calculateAmbientReflection(float3rsrm,floattexM){floatmask=(rsrm.x+rsrm.y+rsrm.z)*0.33;float3amb=UNITY_LIGHTMODEL_AMBIENT.xyz;returnfloat3(1.5*rsrm*amb+amb*0.5*texM);}//fragmentfunctionfloat4frag(vertexOutputi):
COLOR{floatshadAtten=LIGHT_ATTENUATION(i);float4tex=tex2D(_MainTex,i.tex.xy*_MainTex_ST.xy+_MainTex_ST.zw);//tex=tex*_MainColor;float4texN=tex2D(_NormalMap,i.tex.xy*_NormalMap_ST.xy+_NormalMap_ST.zw);floatnDepth=8/(_BumpDepth*8);//UnpackNormalhalf3localCoords=half3(2.0*texN.ag-float2(1.0,1.0),0.0);localCoords.z=nDepth;//normaltransposematrixfloat3x3local2WorldTranspose=float3x3(i.tangentWorld,i.binormalWorld,i.normalWorld);//Calculatenormaldirectionfloat3normalDir=normalize(mul(localCoords,local2WorldTranspose));float3N=normalize(normalDir);float3V=normalize(_WorldSpaceCameraPos.xyz-i.posWorld.xyz);float3fragmentToLight=_WorldSpaceLightPos0.xyz-i.posWorld.xyz;floatdistanceToLight=length(fragmentToLight);floatatten=pow(2,-0.1*distanceToLight*distanceToLight)*_WorldSpaceLightPos0.w+1-_WorldSpaceLightPos0.w;//(-0.1x^2)^2forpointlights1fordirlightsfloat3L=(normalize(fragmentToLight))*_WorldSpaceLightPos0.w+normalize(_WorldSpaceLightPos0.xyz)*(1-_WorldSpaceLightPos0.w);float3H=normalize(V+L);float3worldReflect=reflect(V,N);//lightingfloatNdotL=dot(N,L);floatNdotV=1-max(0.0,dot(N,V));floatNdotH=clamp(dot(N,H),0,1);floatVdotL=clamp01(dot(V,L));floatwrap=clamp(_Wrap,-0.25,1.0);float4texdesat=dot(tex.rgb,float3(0.3,0.59,0.11));float3difftex=tex.xyz;VdotL=pow(VdotL,0.85);floatbellclamp=(1/(1+pow(0.65*acos(dot(N,L)),16)));float3spec=NdotH;spec=pow(spec,32.0);float3diff=max(0,(pow(max(0,(NdotL*(1-wrap)+wrap)),(2*wrap+1))));diff*=lerp(shadAtten,1,wrap)*atten*difftex.xyz*_LightColor0.rgb;returnfloat4(atan(clamp(spec+diff,0,2)),1);//thisisusedtoroundoffvaluesaboveoneandgivebettercolorreproductioninbrightscenes}ENDCG}}Fallback'Diffuse'}
虽说直接用unity的builtinshader拖两下也能得到一样的效果,但是自己用vertex+fragment搞一边就没那么简单了,需要去了解tagentspace,需要去了解forward渲染内部的各个pass…虽然比较基础,但是有不明白的最好还是google搞清楚。
后面的内容都是基于上面的情况进行修改。
AlphamaskDota2中用一张贴图来决定皮肤上某个像素是可见的还是不可见的,这样贴图是黑白的,没有灰度
比如下面是大圣的披风,没有mask的话是这样Mask是这样的
而且披风为了实现双面的cutoff,是有两个mesh的
这里为了简单就直接用discard来处理了(discard在移动端貌似性能貌似不佳,最好用alphablend来实现,但是这个时候又要注意渲染次序了)。
float4texTransparency=tex2D(_TransparencyTex,i.tex.xy*_TransparencyTex_ST.xy+_TransparencyTex_ST.zw);if(texTransparency.r
结果还有头发这里Jugg的模型里面没有需要做透明处理的部分。
SpecularMask有了SpecularMask就不会所有地方都油腻腻了。
比如剑圣面具,上边是albedo,下边是specularmask
floattexSpecular=tex2D(_SpecularMask,i.tex.xy*_SpecularMask_ST.xy+_SpecularMask_ST.zw);texSpecular*=_SpecularPower;算specular的时候
float3spec=NdotH;spec=pow(spec,32.0)*texSpecular;皮带的部分就比较明显了SpecularExponent贴图SpecularExponent值用来控制表面specular高光的的大小,越黑表示表面越粗糙,越白表示表面越光滑,对于不同的材质金属:
高specular,中Exponent羽毛:
中specular,高Exponent木头:
低specular,超低Exponent如下图所示这个值其实就是Unity中standardshader中的Smoothness,不过这里是用一张贴图来控制。
比如剑圣的身上的肌肉
加了贴图控制之后,可以看到胸肌部分是“比较Spec“的。
MatalnessMask这个mask和specularmask类似,在Unity的standardshader中,Matalness和specular是作为两种不同的处理方案,在Dota2中,两种贴图都用到了。
MetalnessMask的作用是用来模拟真实世界的金属效果,主要做的就是让diffuse和rimlight更黑一些。
金箍棒的Albedo和Metalness如下
采样的时候这样处理一下floattexMatal=tex2D(_MetalnessMask,i.tex.xy*_MetalnessMask_ST.xy+_MetalnessMask_ST.zw);texMatal=min(texMatal+_MetalnessPower,1.0);在算diffuse和rim的时候把texMatal乘进去就可以了。
对比一下Diffuse+specular+rim+Matalness
SelfIlluminationMask自发光贴图用来控制颜色由albedo贴图决定的程度,比如jugg的面具,白色的部分即使在很暗的灯光下也会很亮。
原理就是标记的地方不受光照影响,直接使用albedo贴图采样出来的颜色。
首先是采贴图floattexEmmision=tex2D(_EmissionMask,i.tex.xy*_EmissionMask_ST.xy+_EmissionMask_ST.zw);texEmmision*=_EmissionIntensity;
然后在算diffuse的时候把emmision加上去float3diff=max(0,(pow(max(0,(NdotL*(1-wrap)+wrap)),(2*wrap+1))));diff*=lerp(shadAtten,1,wrap)*atten*difftex.xyz*_LightColor0.rgb*2*_LightColor0.rgb*difftex.xyz;diff+=difftex.xyz*texEmmision;
在光照强度很暗的情况下,有emmision和没emmsion还是区别很大的RimlightRimlight是用来给角色轮廓光的,可以让角色在复杂环境中很容易辨识出来最简单的rim//RimtermfloatrimPower=1;float3rim=rimPower*saturate(1-saturate(dot(N,V))*1.8);Rimmask用来精细地控制rim的强度,越黑的地方rim越强Jugg的面部的rimmask如下对比有没有mask和有mask的情况最后的结果BaseTintMask
BaseTintMask用来控制受speccularlight影响的地方specular的颜色,比如黄金的spec的颜色就是黄色的白银的spec的颜色就是白色,用于模拟光穿透物质表面然后反射出来物质的颜色,可以让表面表现得更加丰满。
越黑的地方表示albedo贴图对spec影响得越多。
之前的specular的颜色是这样的float3specColor=_LightColor0.rgb;现在要这样算float3specColor=(1-texSpecularTint)*_LightColor0.rgb+texSpecularTint*difftex.xyz;对比下
基于Cubemap的reflection通过读一张cubemap来作为反射部分。
反射和metalness还有smoothness都有关。
Cubemap贴图要设定一下
采样非常简单float3reflection=texCUBE(_ReflectionMap,worldReflect);单纯输出reflection
将roughness加入,通过采样不同的mipmap的cubemap来达到roughness的效果,
调用的采样接口是texCUBElod:
Samplesacubetexturewithmipmaps.ThemipmapLODisspecifiedint.w.采样lod8的效果如下完成之后要将relection和之前的合在一起,按照近似的经验公式,就是直接和diffuselerp一下。
多谢@易恺铭的提示。
float3reflection=texCUBElod(_ReflectionMap,float4(worldReflect,roughness*8))*texSpecular;…//calculatediffusediff=lerp(diff,reflection,matalValue*_Reflectance).xyz;MaskedCombined加上之前的部分,一起调整一下
Wrapdiffusemask这个在TF2的那篇论文中有提到过。
最简单的wrapdiffuse就是halflambert,halfdiffuse=dot(normal,lightDir)*wrapAmount+(1-wrapAmount);如果用一张一维的Wrapdiffuse贴图来处理的话,diffuse值就是从texture1DrampTex;floatDiffuseCoeff(infloat3pos,infloat3normal,infloat3lightPos){float3lightDir=lightPos-pos;lightDir.normalize();//Mapvaluefrom[-1,1]to[0,1]floatrampCoord=Dot(lightDir,normal)*0.5+0.5;returntex1D(rampTex,rampCoord);}Dota2中rampTex变成了一张二维的图
通过不同的mask来控制采样的位置。
加上自阴影,最后调一个炫酷的剑圣
小结利用家里蹲的这段时间把这篇文章给写出来了,撒花.PBR有待更加深入的学习。
还有很多可以深入学习的地方,比如各种trick的性能profile,比如放到移动端会是怎样,怎么在低端机上也能跑。
再次感谢恺大和申屠小羊同学。
参考在Unity中复刻基于PBS的Dota2着色器//UnitySkinShaderOptimized//Dota2Workshop-ItemShaderMasks//【OpenGL】Dota2Shader分析
(1)//GameShadersVolume1-Dota2[Released]//ExtensiontoEnergy-ConservingWrappedDiffuse//Dota2Workshop-CharacterArtGuide//HowtoExploreUnity5'sShaderSystemCode-IV-GlobalIlluminationcodeoverview//翻译Tri-Ace:
在Shader里近似渲染公式//CodingLabs:
:
PhysicallyBasedRendering//Physically-inspiredShader//TechnicalGraphicDirector//Rendering8,Reflections,aUnityC#Tutorial//Dota2ShaderMaskGuide//BlenderGLSLviewportshader//WhatisRampShadingorLighting?
//Guideoncreating/modifyingStatusEffects(withTemplate)//GPUGEM4PracticalPlanarReflectionsUsingCubemapsandImageProxies//
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Mask 艺术