Unity3D培训
美国上市Unity3D培训机构

400-111-8989

热门课程

Unity3D手游项目的角色渲染总结和思考

  • 发布:达内unity3d编辑03
  • 来源:Unity3D教程
  • 时间:2018-09-07 10:37

Unity3D手游项目的角色渲染总结和思考,传统光照模型的角色渲染,大部分和场景的差不多,也有一些技巧,会让效果更好,角色需要新的shader来提升效果了。

Unity3D手游项目的角色渲染

项目一开始的时候,考虑到我们是多人同屏的游戏,沿用了以前项目最简单的角色制作标准,一张512贴图,1500面,随着手机硬件的发展和各种牛逼重度手游的出现,角色就需要新的shader来提升效果了.对于大量同屏的怪物,我们采用传统光照模型,对于重要的NPC和玩家,可以使用PBR.除了这些基本的,角色渲染还需要各种效果,比如流光,残影,X-Ray透视,溶解,受击闪光等等.

传统光照模型的角色渲染,大部分和场景的差不多,也有一些技巧,会让效果更好,比如用边缘光来模拟背光.

// back light diffuse

nl = dot(fixedNormal, _GlobalBackLightDir.xyz);

finalColor.rgb += mainColor.rgb * _GlobalBackLightColor.rgb * saturate(nl);

// back light rim

half fresnel = 1 - saturate(dot(viewDir, fixedNormal));

nl = nl * 0.5 + 0.5; // [0, 1]

half rim = saturate(step(_RimSharp, fresnel) * fresnel - _RimSharp); // fixed rim = pow(fresnel, _RimSharp);

fixed3 rimColor = _GlobalBackLightColor.rgb * nl * _RimIntensity;

finalColor.rgb += rimColor * rim;

只有背光的diffuse:

只有背光的diffuse

加上边缘光后:

加上边缘光后

对边缘光做过滤,只留下背光方向的边缘光:

对边缘光做过滤,只留下背光方向的边缘光

然后背光的光照效果就特别明显了.

接下来主要介绍下PBR.Unity5支持PBR技术,所以我们也直接跟进了,由于我们的角色有很多其他效果,所以只能把PBR技术整合进自己的shader中.

PBR(Physically Based Rendering)翻译过来就是指基于物理的渲染,为什么叫物理的渲染,其实就是他用的光照模型的理论和物理原理更加符合,效果也更加真实.由于它与物理性质非常接近,因此我们(尤其是美术师们)可以直接以物理参数为依据来编写表面材质,而不必依靠粗劣的修改与调整来让光照效果看上去正常。使用基于物理参数的方法来编写材质还有一个更大的好处,就是不论光照条件如何,这些材质看上去都会是正确的,而在非PBR的渲染管线当中有些东西就不会那么真实了。

PBR物理原理的近似,体现在三个方面:微表面;能量守恒;基于物理的BRDF。

微表面:简单说,就是在微观尺度下,没有任何平面是完全光滑的,这个光滑或者粗糙的程度,我们用粗糙度贴图来表示.

能量守恒:出射光线的能量永远不能超过入射光线的能量(发光面除外)。当一束光线碰撞到一个表面的时候,它就会分离成一个折射部分和一个反射部分。反射部分就是会直接反射开来而不会进入平面的那部分光线,这就是我们所说的镜面光照(高光)。而折射部分就是余下的会进入表面并被吸收的那部分光线,这也就是我们所说的漫反射光照。能量守恒公式:漫反射+镜面反射= 1

BRDF:Cook-Torrance BRDF光照模型兼有漫反射和镜面反射两个部分:

漫反射用Lambert,镜面反射比较复杂,包含3个函数,

1.D(法线分布函数)

用统计学来估算在受到表面粗糙度的影响下,微观法线方向与宏观法线一致的微平面的数量。对于美术来说,主要受粗糙贴图的影响,它决定了高光的大小强度和形状。

2.V(自阴影遮挡函数)

描述了微平面自成阴影的属性。当一个平面相对比较粗糙的时候,平面表面上的微平面有可能挡住其他的微平面从而减少表面所反射的光线。如果没有遮挡函数,就可能造成能量不守恒。

3.F(菲涅尔方程)

菲涅尔方程描述的是在不同的表面角下表面所反射的光线所占的比率。

以上三项,每一项都会有一些变种算法,比如法线分布函数,常见的是BlinnPhong和GGX,由于移动平台的性能限制,我们的PBR采用的算法是:

D: BlinnPhong

V: Modified Kelemen and Szirmay-Kalos

F: Schlick Fresnel

CharacterStandard_PBR.shader:

Shader "Luoyinan/Character/CharacterStandard_PBR"

{

Properties

{

_MainTex("Main Texture", 2D) = "white" {}

_Color ("Main Color", Color) = (1, 1, 1, 1)

_NormalTex("Normal Texture", 2D) = "bump" {}

_GlossTex ("Gloss Texture (R:gloss) (G:roughness) B(:flowlight)", 2D) = "white" {}

_SpecularColor ("Specular Color", Color) = (1, 1, 1, 0.5)

_SpecularColor2 ("Specular Color 2", Color) = (1, 1, 1, 1)

_Roughness ("Roughness", Range (0, 1)) = 0

_RoughnessBias ("Roughness Bias", Float) = 0

_RefectionTex("Refection Texture (Cubemap)", Cube) = "" {}

_RefectionColor ("Refection Color", Color) = (1, 1, 1, 1)

_DissolveTex ("Dissolve Texture", 2D) = "white" {}

_DissolveColor ("Dissolve Color", Color) = (1, 0, 0, 1)

_DissolveCutoff ("Dissolve Cutoff", Range (0, 1)) = 0

_FlowLightTex ("Flow Light Texture", 2D) = "white" {}

_FlowLightColor ("Flow Light Color", Color) = (1, 0, 0, 1)

_FlowDirectionX("Flow Speed&Direction X", Float) = 0

_FlowDirectionY("Flow Speed&Direction Y", Float) = 8

_FlowDistortionIntensity("Flow Distortion Intensity", Range (0, 0.5)) = 0.2

_FlashColor ("Flash Color", Color) = (1, 1, 1, 1)

[HideInInspector] _FlashIntensity("", Range (0, 1)) = 0

[HideInInspector] _DoubleSided("", Float) = 2.0

[HideInInspector] _CutOff("", Float) = 0

[HideInInspector] _UseRoughness("", Float) = 0

}

SubShader

{

Tags

{

"Queue" = "Geometry"

"RenderType" = "Opaque" // 支持渲染到_CameraDepthNormalsTexture

}

Pass

{

Lighting Off

Cull[_DoubleSided] // CullMode: Off(0) Front(1) Back(2)

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#pragma multi_compile_fog

#pragma fragmentoption ARB_precision_hint_fastest

#include "UnityCG.cginc"

#include "TerrainEngine.cginc"

#include "UnityStandardBRDF.cginc"

#include "UnityStandardUtils.cginc"

#pragma shader_feature _ALPHA_TEST_ON

#pragma shader_feature _REFLECTION_ON

#pragma shader_feature _NORMAL_MAP

#pragma shader_feature _DISSOLVE_ON

#pragma shader_feature _FLOW_LIGHT_ON

#pragma shader_feature _USE_ROUGHNESS

#pragma multi_compile __ _ORIGIN_ALPHA

#pragma multi_compile __ _POINT_LIGHT

#pragma multi_compile __ _FANCY_STUFF

struct appdata_custom

{

half4 vertex : POSITION;

half2 texcoord : TEXCOORD0;

#if _FANCY_STUFF

half3 normal : NORMAL;

#if _NORMAL_MAP

half4 tangent : TANGENT;

#endif

#endif

};

// SM2.0的texture interpolator只有8个,要合理规划.

struct v2f

{

half4 pos : SV_POSITION;

half2 uv0 : TEXCOORD0;

#if _FLOW_LIGHT_ON

half2 uv1 : TEXCOORD1;

#endif

UNITY_FOG_COORDS(2)

#if _FANCY_STUFF

float3 posWorld : TEXCOORD3;

half3 normalWorld : TEXCOORD4;

#if _NORMAL_MAP

half3 tangentWorld : TEXCOORD5;

half3 binormalWorld : TEXCOORD6;

#endif

#endif

};

sampler2D _MainTex;

half4 _MainTex_ST;

fixed4 _Color;

fixed4 _FlashColor;

fixed _FlashIntensity;

sampler2D _GlossTex;

fixed4 _SpecularColor;

fixed4 _SpecularColor2;

#if _DISSOLVE_ON

sampler2D _DissolveTex;

fixed4 _DissolveColor;

half _DissolveCutoff;

#endif

#if _FLOW_LIGHT_ON

sampler2D _FlowLightTex;

half4 _FlowLightTex_ST;

fixed4 _FlowLightColor;

fixed _FlowDirectionX;

fixed _FlowDirectionY;

fixed _FlowDistortionIntensity;

#endif

#if _FANCY_STUFF

fixed4 _GlobalAmbientColor;

half4 _GlobalMainLightDir;

fixed4 _GlobalMainLightColor;

half4 _GlobalBackLightDir;

fixed4 _GlobalBackLightColor;

#if _POINT_LIGHT

float4 _GlobalPointLightPos;

fixed4 _GlobalPointLightColor;

fixed _GlobalPointLightRange;

#endif

fixed _Roughness;

fixed _RoughnessBias;

#if _REFLECTION_ON

uniform samplerCUBE _RefectionTex;

fixed4 _RefectionColor;

#endif

#if _NORMAL_MAP

uniform sampler2D _NormalTex;

#endif

#endif

v2f vert(appdata_custom i)

{

v2f o;

o.pos = mul(UNITY_MATRIX_MVP, i.vertex);

o.uv0 = TRANSFORM_TEX(i.texcoord, _MainTex);

#if _FLOW_LIGHT_ON

o.uv1 = TRANSFORM_TEX(i.texcoord, _FlowLightTex);

#endif

#if _FANCY_STUFF

o.posWorld = mul(unity_ObjectToWorld, i.vertex).xyz;

o.normalWorld = UnityObjectToWorldNormal(i.normal);

#if _NORMAL_MAP

o.tangentWorld = UnityObjectToWorldDir(i.tangent);

o.binormalWorld = cross(o.normalWorld, o.tangentWorld) * i.tangent.w;

#endif

#endif

UNITY_TRANSFER_FOG(o, o.pos);

return o;

}

#if _ALPHA_TEST_ON

uniform fixed _CutOff;

#endif

fixed4 frag(v2f i) : COLOR

{

// main color

#if _FANCY_STUFF

fixed4 mainColor = tex2Dbias(_MainTex, fixed4(i.uv0, 0, -1.5)) * _Color; // 通过TextureImporter来设置mipmapbias对mobile平台无效

#else

fixed4 mainColor = tex2D(_MainTex, i.uv0) * _Color;

#endif

fixed alpha = mainColor.a;

// alpha test

#if _ALPHA_TEST_ON

clip(alpha - _CutOff);

#endif

// dissolve

#if _DISSOLVE_ON

if (_DissolveCutoff > 0) // performance

{

fixed dissolve = tex2D(_DissolveTex, i.uv0).r;

fixed clipValue = dissolve - _DissolveCutoff;

clip(clipValue);

// edge range [0, 0.1)

if (clipValue < 0.1)

{

fixed3 edgeColor = fixed3(_DissolveColor.x, clipValue / 0.1, _DissolveColor.z);

fixed colorTotal = edgeColor.x + edgeColor.y + edgeColor.z;

mainColor.rgb = (mainColor.rgb * edgeColor * colorTotal * colorTotal) * 3;

}

}

#endif

// gloss

fixed4 glossTex = tex2D(_GlossTex, i.uv0);

if (glossTex.r > _SpecularColor.a)

mainColor.rgb *= _SpecularColor.rgb;

if (glossTex.a > _SpecularColor2.a)

mainColor.rgb *= _SpecularColor2.rgb;

fixed4 finalColor = mainColor;

#if _FANCY_STUFF

// normalmap

#if _NORMAL_MAP

fixed3x3 tangentToWorld = fixed3x3(i.tangentWorld, i.binormalWorld, i.normalWorld);

half3 normalMap = UnpackNormal(tex2D(_NormalTex, i.uv0));

half3 fixedNormal = normalize(mul(normalMap, tangentToWorld));

#else

half3 fixedNormal = normalize(i.normalWorld);

#endif

// common PBR params

#if _USE_ROUGHNESS

fixed roughness = _Roughness;

#else

fixed roughness = saturate(glossTex.g + _RoughnessBias);

#endif

fixed oneMinusRoughness = 1 - roughness;

half specularPower = RoughnessToSpecPower(roughness);

// specular workflow

fixed oneMinusReflectivity;

fixed3 specColor = glossTex.r * mainColor.rgb; // monochrome specular -> color specular

fixed3 diffColor = EnergyConservationBetweenDiffuseAndSpecular (mainColor.rgb, specColor, /*out*/ oneMinusReflectivity);

// metallic workflow

//fixed metallic = glossTex.r;

//fixed3 specColor;

//fixed oneMinusReflectivity;

//fixed3 diffColor = DiffuseAndSpecularFromMetallic(mainColor.rgb, metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);

fixed reflectivity = 1 - oneMinusReflectivity;

half3 viewDir = normalize(_WorldSpaceCameraPos - i.posWorld);

half nv = DotClamped(fixedNormal, viewDir);

alpha *= reflectivity;

// main light PBR

half3 halfDir = Unity_SafeNormalize(_GlobalMainLightDir + viewDir);

half nl = DotClamped(fixedNormal, _GlobalMainLightDir);

half nh = DotClamped(fixedNormal, halfDir);

half lh = DotClamped(_GlobalMainLightDir, halfDir);

half invV = lh * lh * oneMinusRoughness + roughness * roughness;

half invF = lh;

half specular = ((specularPower + 1) * pow (nh, specularPower)) / (8 * invV * invF + 1e-4h);

if (IsGammaSpace())

specular = sqrt(max(1e-4h, specular));

specular = clamp(specular, 0.0, 100.0); // Prevent FP16 overflow on mobiles

finalColor.rgb = _GlobalAmbientColor * diffColor + (diffColor + specular * specColor) * _GlobalMainLightColor * nl;

// env reflection

#if _REFLECTION_ON

half3 reflUVW = normalize(reflect(-viewDir, fixedNormal));

fixed3 envColor = texCUBE(_RefectionTex, reflUVW) * _RefectionColor.rgb;

half realRoughness = roughness * roughness;

half surfaceReduction = IsGammaSpace() ? 0.28 : (0.6 - 0.08*roughness);

surfaceReduction = 1.0 - realRoughness * roughness * surfaceReduction;

half grazingTerm = saturate(oneMinusRoughness + reflectivity);

finalColor.rgb += surfaceReduction * envColor * FresnelLerpFast(specColor, grazingTerm, nv);

#endif

// back light PBR

halfDir = Unity_SafeNormalize(_GlobalBackLightDir + viewDir);

nl = DotClamped(fixedNormal, _GlobalBackLightDir);

nh = DotClamped(fixedNormal, halfDir);

lh = DotClamped(_GlobalBackLightDir, halfDir);

invV = lh * lh * oneMinusRoughness + roughness * roughness;

invF = lh;

specular = ((specularPower + 1) * pow (nh, specularPower)) / (8 * invV * invF + 1e-4h);

if (IsGammaSpace())

specular = sqrt(max(1e-4h, specular));

specular = clamp(specular, 0.0, 100.0); // Prevent FP16 overflow on mobiles

finalColor.rgb += (diffColor + specular * specColor) * _GlobalBackLightColor * nl;

// point light PBR

#if _POINT_LIGHT

half3 toLight = _GlobalPointLightPos.xyz - i.posWorld ;

half ratio = saturate(length(toLight) / _GlobalPointLightRange);

//half attenuation = 1 - ratio; // linear attenuation

ratio *= ratio;

half attenuation = 1.0 / (1.0 + 0.01 * ratio) * (1 - ratio); // quadratic attenuation

if (attenuation > 0) // performance

{

halfDir = Unity_SafeNormalize(toLight + viewDir);

nl = DotClamped(fixedNormal, toLight);

nh = DotClamped(fixedNormal, halfDir);

lh = DotClamped(toLight, halfDir);

invV = lh * lh * oneMinusRoughness + roughness * roughness;

invF = lh;

specular = ((specularPower + 1) * pow (nh, specularPower)) / (8 * invV * invF + 1e-4h);

if (IsGammaSpace())

specular = sqrt(max(1e-4h, specular));

specular = clamp(specular, 0.0, 100.0); // Prevent FP16 overflow on mobiles

finalColor.rgb += (diffColor + specular * specColor) * _GlobalPointLightColor * nl * attenuation;

}

#endif

#else

finalColor.rgb *= 1.4;

#endif

// flow light

#if _FLOW_LIGHT_ON

fixed3 flow = tex2D(_FlowLightTex, frac(i.uv1 + _Time.xx * half2(_FlowDirectionX, _FlowDirectionY)) + mainColor.xy * _FlowDistortionIntensity).rgb;

finalColor.rgb += flow * _FlowLightColor.rgb *_FlowLightColor.a * 2 * glossTex.b;

#endif

// flash

finalColor.rgb += _FlashIntensity * _FlashColor;

// fog

UNITY_APPLY_FOG(i.fogCoord, finalColor);

// alpha

#if _ORIGIN_ALPHA

finalColor.a = mainColor.a;

#else

finalColor.a = alpha;

#endif

return finalColor;

}

ENDCG

}

// 没用Unity自带的阴影,只是用来来渲染_CameraDepthsTexture.

Pass

{

Tags{ "LightMode" = "ShadowCaster" }

Fog { Mode Off }

ZWrite On

Offset 1, 1

Cull[_DoubleSided]

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#pragma multi_compile_shadowcaster

#pragma fragmentoption ARB_precision_hint_fastest

#pragma shader_feature _ALPHA_TEST_ON

#include "UnityCG.cginc"

struct v2f

{

V2F_SHADOW_CASTER;

#if _ALPHA_TEST_ON

fixed2 uv0 : TEXCOORD1;

#endif

};

#if _ALPHA_TEST_ON

sampler2D _MainTex;

half4 _MainTex_ST;

uniform fixed _Cutoff;

#endif

v2f vert(appdata_base v)

{

v2f o;

TRANSFER_SHADOW_CASTER(o)

#if _ALPHA_TEST_ON

o.uv0 = TRANSFORM_TEX(v.texcoord, _MainTex);

#endif

return o;

}

fixed4 frag(v2f i) : COLOR

{

#if _ALPHA_TEST_ON

fixed4 col = tex2D(_MainTex, i.uv0);

clip(col.a - _Cutoff);

#endif

SHADOW_CASTER_FRAGMENT(i)

}

ENDCG

}

}

Fallback off

CustomEditor "CharacterStandard_PBR_ShaderGUI"

}

这个shader除了PBR以外,还实现了以下几个功能:

1.mipmap偏移

为什么纹理采样要做mipmap偏移呢,因为对于斜45度角的游戏来说,角色一般用的都不是最清晰的mip0级的贴图,而是mip2或者mip3.就会导致角色看起来模糊.所以要适当偏移,让角色清晰.

2.dissolve溶解

dissolve溶解

3.flow light流光

最简单的流光就是UV移动,为了效果好,还需要几个改进,一是增加流光掩码,只在固定的区域有流光,比如金属,二是增加扰动,这样的流光就更加漂亮,三是对贴图的UV展开要尽量连续,这样流光流动的路径才连贯.

4.flash受击闪光

被打就闪一下,用指数曲线来表现渐变效果,比线性曲线好.

5.装备换色

衣服颜色和金属颜色都可以改变.Specular Color一个用调节金属颜色,一个用来调节衣服颜色.Gloss Texture:R通道表示单色高光,G通道表示粗糙度,B通道表示流光掩码,A通道表示衣服掩码.是的,就是这么多掩码...

设置面板:

设置面板

新的角色制作标准,头部要和身体分开制作贴图的,头发需要特殊处理高光,但是历史原因很多老的模型只有一张贴图.

我们的PBR实现和网易的镇魔曲类似,可以实现装备自由换色.高光颜色通过从单色高光组合成彩色高光.

fixed3 specColor = glossTex.r * mainColor.rgb; // monochrome specular -> color specular

破解镇魔曲的模型(只是研究用),用我们的shader渲染,效果也差不太多,金属质感效果还不错.

如下图:

效果

感谢大家阅读由Unity3D教程分享的“Unity3D手游项目的角色渲染总结和思考?”希望对大家有所帮助,想了解更多培训信息请关注Unity3D培训机构官网

免责声明:以上内容仅作为信息传播,文中部分信息来源于互联网,仅供阅读参考。

预约申请免费试听课

上一篇:Untiy游戏开发使用什么架构?
下一篇:Unity3D基础教程:教你零基础如何学习U3D开发
游戏角色阴影如何用Unity3D添加?

游戏角色阴影如何用Unity3D添加?

Unity 2018.3一次开发支持多个VR平台

Unity 2018.3一次开发支持多个VR平台

Unity3D游戏开发使用的架构

Unity3D游戏开发使用的架构

Unity Profiler 真机调试以及函数耗时教程

Unity Profiler 真机调试以及函数耗时教程

选择城市和中心
贵州省

广西省

海南省