先创建一个带天空盒的空场景,再放入一个球。创建一个材质,赋予standard shader。然后到freepbr.com随便下一个unity支持的pbr贴图,然后按名字赋给材质中的变量。
2
2
2
2
2
2
2
2
2
22
2
2
1
可以看到,用五张贴图就能实现pbr的效果。
入口代码在standard.shader中。
#if defined(UNITY_NO_FULL_STANDARD_SHADER)
# define UNITY_STANDARD_SIMPLE 1
#endif
#if UNITY_STANDARD_SIMPLE
#include "UnityStandardCoreForwardSimple.cginc"
VertexOutputBaseSimple vertBase (VertexInput v) { return vertForwardBaseSimple(v); }
VertexOutputForwardAddSimple vertAdd (VertexInput v) { return vertForwardAddSimple(v); }
half4 fragBase (VertexOutputBaseSimple i) : SV_Target { return fragForwardBaseSimpleInternal(i); }
half4 fragAdd (VertexOutputForwardAddSimple i) : SV_Target { return fragForwardAddSimpleInternal(i); }
#else
#include "UnityStandardCore.cginc"
VertexOutputForwardBase vertBase (VertexInput v) { return vertForwardBase(v); }
VertexOutputForwardAdd vertAdd (VertexInput v) { return vertForwardAdd(v); }
half4 fragBase (VertexOutputForwardBase i) : SV_Target { return fragForwardBaseInternal(i); }
half4 fragAdd (VertexOutputForwardAdd i) : SV_Target { return fragForwardAddInternal(i); }
#endif
其中UNITY_NO_FULL_STANDARD_SHADER宏决定了是不是走simple分支。这个宏的设置是在 graphics setting里面
中,如果设置为Low那么就走simple分支
接下来进入vs的主体函数
VertexOutputForwardBase vertForwardBase (VertexInput v)
{
//这一段是和gpu instancing相关的设置代码。可以不管
UNITY_SETUP_INSTANCE_ID(v);
VertexOutputForwardBase o;
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
//如果在fragment shader中要用到世界坐标的话,那么这里就先算好。
float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
#if UNITY_REQUIRE_FRAG_WORLDPOS
#if UNITY_PACK_WORLDPOS_WITH_TANGENT
//如果在ps中有切空间的计算,那就更节约了,放到切空间的矩阵多出来的w分量多
o.tangentToWorldAndPackedData[0].w = posWorld.x;
o.tangentToWorldAndPackedData[1].w = posWorld.y;
o.tangentToWorldAndPackedData[2].w = posWorld.z;
#else
o.posWorld = posWorld.xyz;
#endif
#endif
o.pos = UnityObjectToClipPos(v.vertex);
o.tex = TexCoords(v);
o.eyeVec.xyz = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
float3 normalWorld = UnityObjectToWorldNormal(v.normal);
#ifdef _TANGENT_TO_WORLD
float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
o.tangentToWorldAndPackedData[0].xyz = tangentToWorld[0];
o.tangentToWorldAndPackedData[1].xyz = tangentToWorld[1];
o.tangentToWorldAndPackedData[2].xyz = tangentToWorld[2];
#else
o.tangentToWorldAndPackedData[0].xyz = 0;
o.tangentToWorldAndPackedData[1].xyz = 0;
o.tangentToWorldAndPackedData[2].xyz = normalWorld;
#endif
//We need this for shadow receving
// 如果这个物体接收阴影的话,那么将世界坐标转到光源坐标系下存到o._LightCoord中
//同时计算出屏幕空间的阴影坐标,放入_ShadowCoord中
UNITY_TRANSFER_LIGHTING(o, v.uv1);
//计算环境光或光图的UV
o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);
#ifdef _PARALLAXMAP
//如果 开启了视差贴图,那么把观察方向转到切空间
TANGENT_SPACE_ROTATION;
half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
o.tangentToWorldAndPackedData[0].w = viewDirForParallax.x;
o.tangentToWorldAndPackedData[1].w = viewDirForParallax.y;
o.tangentToWorldAndPackedData[2].w = viewDirForParallax.z;
#endif
UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o,o.pos);
return o;
}
其中VertexGIForward函数的实现如下
inline half4 VertexGIForward(VertexInput v, float3 posWorld, half3 normalWorld)
{
half4 ambientOrLightmapUV = 0;
// Static lightmaps
#ifdef LIGHTMAP_ON
ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
ambientOrLightmapUV.zw = 0;
// Sample light probe for Dynamic objects only (no static or dynamic lightmaps)
#elif UNITY_SHOULD_SAMPLE_SH
#ifdef VERTEXLIGHT_ON
// Approximated illumination from non-important point lights
ambientOrLightmapUV.rgb = Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, posWorld, normalWorld);
#endif
ambientOrLightmapUV.rgb = ShadeSHPerVertex (normalWorld, ambientOrLightmapUV.rgb);
#endif
#ifdef DYNAMICLIGHTMAP_ON
ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
return ambientOrLightmapUV;
}
它的主要工作是如果开启了光图,那么返回值存的是光图的UV,以及realtime GI光图的UV。否则的话,返回值存的是四个逐顶点光源的球谐光照加上超过四个顶点光源的其它光照,超过四个顶点光源的其它光照它们通通被计算成1个球谐展开的系数。
vs部分的准备工作完毕,接下来是ps部分
half4 fragForwardBaseInternal (VertexOutputForwardBase i)
{
// LOD相关的抖动
UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);
//设置片元通用数据
FRAGMENT_SETUP(s)
其中设置片元通用数据的代码如下
FragmentCommonData FragmentSetupSimple(VertexOutputBaseSimple i)
{
// 是否通过ALPHA测试
half alpha = Alpha(i.tex.xy);
#if defined(_ALPHATEST_ON)
clip (alpha - _Cutoff);
#endif
// 接下来UNITY_SETUP_BRDF_INPUT的实现决定于是走金属工作流还是高光工作流
FragmentCommonData s = UNITY_SETUP_BRDF_INPUT (i.tex);
// NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
s.diffColor = PreMultiplyAlpha (s.diffColor, alpha, s.oneMinusReflectivity, /*out*/ s.alpha);
s.normalWorld = i.normalWorld.xyz;
s.eyeVec = i.eyeVec.xyz;
s.posWorld = IN_WORLDPOS(i);
s.reflUVW = i.fogCoord.yzw;
#ifdef _NORMALMAP
s.tangentSpaceNormal = NormalInTangentSpace(i.tex);
#else
s.tangentSpaceNormal = 0;
#endif
return s;
}
如果走的是金属工作流,那么需要两个数据,金属度和光泽度
图中的smoothness也就是光泽度的意思
这两个数据通过MetallicGloss函数来取得
接下来通过DiffuseAndSpecularFromMetallic函数来计算漫反射光和镜面反射光。传入的参数需要有albedo和金属度
inline half OneMinusReflectivityFromMetallic(half metallic)
{
// We'll need oneMinusReflectivity, so
// 当metallic 是0是,那么返回1-绝缘体的反射率,否则返回0,表示全部光都被反射。没有产生漫反射
half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}
inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
//
Unity的内置变量Unity_ColorSpaceDielectricSpec定义了绝缘体的高光颜色和反射率,
specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
return albedo * oneMinusReflectivity;
}
最终FragmentCommonData存放了四个东西:漫反射颜色,反射颜色,光滑度,1减反射比
接下来采样遮挡贴图,occlusion值对应图的g通道
数据准备好后,就进入关键函数 FragmentGI
inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light, bool reflections)
{
UnityGIInput d;
d.light = light;
d.worldPos = s.posWorld;
d.worldViewDir = -s.eyeVec;
d.atten = atten;
#if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
d.ambient = 0;
d.lightmapUV = i_ambientOrLightmapUV;
#else
d.ambient = i_ambientOrLightmapUV.rgb;
d.lightmapUV = 0;
#endif
d.probeHDR[0] = unity_SpecCube0_HDR;
d.probeHDR[1] = unity_SpecCube1_HDR;
#if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
d.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending
#endif
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
d.boxMax[0] = unity_SpecCube0_BoxMax;
d.probePosition[0] = unity_SpecCube0_ProbePosition;
d.boxMax[1] = unity_SpecCube1_BoxMax;
d.boxMin[1] = unity_SpecCube1_BoxMin;
d.probePosition[1] = unity_SpecCube1_ProbePosition;
#endif
if(reflections)
{
Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.smoothness, -s.eyeVec, s.normalWorld, s.specColor);
// Replace the reflUVW if it has been compute in Vertex shader. Note: the compiler will optimize the calcul in UnityGlossyEnvironmentSetup itself
#if UNITY_STANDARD_SIMPLE
g.reflUVW = s.reflUVW;
#endif
return UnityGlobalIllumination (d, occlusion, s.normalWorld, g);
}
else
{
return UnityGlobalIllumination (d, occlusion, s.normalWorld);
}
}
函数的前半部分是设置一些参数,环境光或lightmap的UV之类的。
d.probeHDR[0] = unity_SpecCube0_HDR;
d.probeHDR[1] = unity_SpecCube1_HDR;
是用来设置两个HRD环境贴图的解压参数
在unity shader中
unity_SpecCube0 代表第一个反射探针和天空盒,这两个是混合在一起的。
unity_SpecCube1 代表第二个反射探针。
接下来UnityGlossyEnvironmentSetup设置感性粗糙度和反射方向
接下来进入
inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
{
UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld);
o_gi.indirect.specular = UnityGI_IndirectSpecular(data, occlusion, glossIn);
return o_gi;
}
UnityGI_Base是计算直接光照和间接光照
直接光就是主光源颜色乘以衰减值
间接光就是采样光图,如果用了方向光图,那么,还需要用半兰伯特光照来计算diffuse
inline half3 DecodeDirectionalLightmap (half3 color, fixed4 dirTex, half3 normalWorld)
{
// In directional (non-specular) mode Enlighten bakes dominant light direction
// in a way, that using it for half Lambert and then dividing by a "rebalancing coefficient"
// gives a result close to plain diffuse response lightmaps, but normalmapped.
// Note that dir is not unit length on purpose. Its length is "directionality", like
// for the directional specular lightmaps.
half halfLambert = dot(normalWorld, dirTex.xyz - 0.5) + 0.5;
return color * halfLambert / max(1e-4h, dirTex.w);
}
最后将diffuse乘以遮蔽系数
然后进入间接光反射 计算UnityGI_IndirectSpecular
首先调用Unity_GlossyEnvironment,来采样第一个反射贴图的颜色。中间用了把感性粗糙度转为线性粗糙度的算法,然后用粗糙度采样贴图的MIPMAP
最后把颜色也乘以遮蔽系数
到此,所有的数据已经准备好,放在UnityGI这个结构中,下一步就是交给PBR的算法来进行计算了
关于这个流程
https://zhuanlan.zhihu.com/p/137039291
这篇文章讲得不错