unity版本:2022.3.27
Standard Shader第一个Pass,指定顶点着色器和片元着色器:
#pragma vertex vertBase
#pragma fragment fragBase
#include "UnityStandardCoreForward.cginc"
着色器定义在UnityStandardCoreForward.cginc中:
#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
看非简化版本;
1. 顶点着色器 vertForwardBase:
VertexOutputForwardBase vertForwardBase (VertexInput v)
顶点着色器输入:
struct VertexInput
{
float4 vertex : POSITION;
half3 normal : NORMAL; //模型法线
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
float2 uv2 : TEXCOORD2;
#endif
#ifdef _TANGENT_TO_WORLD
half4 tangent : TANGENT; //切线
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID //uint instanceID (gpu instance)
};
顶点着色器输出:
struct VertexOutputForwardBase
{
UNITY_POSITION(pos);
float4 tex : TEXCOORD0;
float4 eyeVec : TEXCOORD1; // eyeVec.xyz | fogCoord
float4 tangentToWorldAndPackedData[3] : TEXCOORD2; // [3x3:tangentToWorld | 1x3:viewDirForParallax or worldPos]
half4 ambientOrLightmapUV : TEXCOORD5; // SH or Lightmap UV
UNITY_LIGHTING_COORDS(6,7) //光源空间坐标,阴影坐标
// next ones would not fit into SM2.0 limits, but they are always for SM3.0+
#if UNITY_REQUIRE_FRAG_WORLDPOS && !UNITY_PACK_WORLDPOS_WITH_TANGENT
float3 posWorld : TEXCOORD8;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID //gpu instance
UNITY_VERTEX_OUTPUT_STEREO //stereo, vr
};
顶点着色器执行流程:
VertexOutputForwardBase vertForwardBase (VertexInput v)
{
UNITY_SETUP_INSTANCE_ID(v); //gpu instance,设置static uint unity_InstanceID;
VertexOutputForwardBase o; //顶点着色器输出
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o); //初始化
UNITY_TRANSFER_INSTANCE_ID(v, o); //gpu instance,设置o.instanceID
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //vr
float4 posWorld = mul(unity_ObjectToWorld, v.vertex); //顶点在世界空间的位置
#if UNITY_REQUIRE_FRAG_WORLDPOS
#if UNITY_PACK_WORLDPOS_WITH_TANGENT //将位置存储在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
UNITY_TRANSFER_LIGHTING(o, v.uv1); //计算光源空间坐标和阴影坐标
o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld); //计算顶点球谐光照或用于存储光照贴图uv
#ifdef _PARALLAXMAP
TANGENT_SPACE_ROTATION; //计算rotation,用于对象空间转化到切线空间
half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex)); //切线空间下的viewDir
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); //计算雾效系数,存放在o.eyeVec的w分量
return o;
}
其中CreateTangentToWorldPerVertex:
half3x3 CreateTangentToWorldPerVertex(half3 normal, half3 tangent, half tangentSign)
{
// For odd-negative scale transforms we need to flip the sign
half sign = tangentSign * unity_WorldTransformParams.w; //tangentSign确定副切线方向
half3 binormal = cross(normal, tangent) * sign;
return half3x3(tangent, binormal, normal); //按行存储tangent, binormal, normal
}
2. 片元着色器fragForwardBaseInternal:
half4 fragForwardBaseInternal (VertexOutputForwardBase i)
片元着色器执行流程:
FRAGMENT_SETUP(s) //获取BRDF输入参数,FragmentCommonData s
展开:
#define FRAGMENT_SETUP(x) FragmentCommonData x = \
FragmentSetup(i.tex, i.eyeVec.xyz, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndPackedData, IN_WORLDPOS(i));
// parallax transformed texcoord is used to sample occlusion
inline FragmentCommonData FragmentSetup (inout float4 i_tex, float3 i_eyeVec, half3 i_viewDirForParallax, float4 tangentToWorld[3], float3 i_posWorld)
{
i_tex = Parallax(i_tex, i_viewDirForParallax); //使用视差纹理修改uv坐标
half alpha = Alpha(i_tex.xy); //获取主纹理以及颜色设置中的alpha
#if defined(_ALPHATEST_ON)
clip (alpha - _Cutoff);
#endif
FragmentCommonData o = UNITY_SETUP_BRDF_INPUT (i_tex); //设置各工作流下BRDF的Input
o.normalWorld = PerPixelWorldNormal(i_tex, tangentToWorld); //应用法线贴图
o.eyeVec = NormalizePerPixelNormal(i_eyeVec);
o.posWorld = i_posWorld;
// NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
o.diffColor = PreMultiplyAlpha (o.diffColor, alpha, o.oneMinusReflectivity, /*out*/ o.alpha); //在颜色中预乘alpha混合值
return o;
}
UNITY_SETUP_BRDF_INPUT 代表当前的工作流,分别有MetallicSetup,SpecularSetup,RoughnessSetup;
我们先看金属工作流MetallicSetup:
//金属工作流
inline FragmentCommonData MetallicSetup (float4 i_tex)
{
half2 metallicGloss = MetallicGloss(i_tex.xy); //获取金属度metallic和平滑度smoothness
half metallic = metallicGloss.x;
half smoothness = metallicGloss.y; // this is 1 minus the square root of real roughness m.
half oneMinusReflectivity; //1-高光反射 = 漫反射
half3 specColor;
//计算漫反射颜色diffColor,高光反射颜色specColor,以及oneMinusReflectivity
half3 diffColor = DiffuseAndSpecularFromMetallic (Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
FragmentCommonData o = (FragmentCommonData)0;
o.diffColor = diffColor;
o.specColor = specColor;
o.oneMinusReflectivity = oneMinusReflectivity;
o.smoothness = smoothness;
return o;
}
展开DiffuseAndSpecularFromMetallic:
inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
//为了保持能量守恒,albedo一部分用于高光反射,另一部分用于漫反射,漫反射部分系数为oneMinusReflectivity
//其中高光反射最小值为unity_ColorSpaceDielectricSpec,表示电解质的高光反射
specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic); //高光反射颜色
oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic); //漫反射部分系数
return albedo * oneMinusReflectivity;
}
我们再看高光工作流SpecularSetup:
//高光工作流
inline FragmentCommonData SpecularSetup (float4 i_tex)
{
half4 specGloss = SpecularGloss(i_tex.xy); //获取高光颜色和平滑度smoothness
half3 specColor = specGloss.rgb;
half smoothness = specGloss.a;
half oneMinusReflectivity; //1-高光反射 = 漫反射
//计算漫反射颜色diffColor,高光反射颜色specColor,以及oneMinusReflectivity
half3 diffColor = EnergyConservationBetweenDiffuseAndSpecular (Albedo(i_tex), specColor, /*out*/ oneMinusReflectivity);
FragmentCommonData o = (FragmentCommonData)0;
o.diffColor = diffColor;
o.specColor = specColor;
o.oneMinusReflectivity = oneMinusReflectivity;
o.smoothness = smoothness;
return o;
}
展开EnergyConservationBetweenDiffuseAndSpecular:
// Diffuse/Spec Energy conservation
inline half3 EnergyConservationBetweenDiffuseAndSpecular (half3 albedo, half3 specColor, out half oneMinusReflectivity)
{
//为保持能量守恒,漫反射颜色 = 白色 - 高光反射颜色
oneMinusReflectivity = 1 - SpecularStrength(specColor);
#if !UNITY_CONSERVE_ENERGY
return albedo;
#elif UNITY_CONSERVE_ENERGY_MONOCHROME
return albedo * oneMinusReflectivity;
#else
return albedo * (half3(1,1,1) - specColor);
#endif
}
获取世界空间下的法线PerPixelWorldNormal:
float3 PerPixelWorldNormal(float4 i_tex, float4 tangentToWorld[3])
{
#ifdef _NORMALMAP
//使用法线贴图
half3 tangent = tangentToWorld[0].xyz;
half3 binormal = tangentToWorld[1].xyz;
half3 normal = tangentToWorld[2].xyz;
#if UNITY_TANGENT_ORTHONORMALIZE //正交化处理,当前未使用
normal = NormalizePerPixelNormal(normal);
// ortho-normalize Tangent
tangent = normalize (tangent - normal * dot(tangent, normal));
// recalculate Binormal
half3 newB = cross(normal, tangent);
binormal = newB * sign (dot (newB, binormal));
#endif
half3 normalTangent = NormalInTangentSpace(i_tex); //采样法线贴图获取切线空间下的法线
//转化到世界空间中
float3 normalWorld = NormalizePerPixelNormal(tangent * normalTangent.x + binormal * normalTangent.y + normal * normalTangent.z); // @TODO: see if we can squeeze this normalize on SM2.0 as well
#else
//使用顶点法线
float3 normalWorld = normalize(tangentToWorld[2].xyz);
#endif
return normalWorld;
}
预乘alpha,并计算加上高光反射后的最终alpha值:
inline half3 PreMultiplyAlpha (half3 diffColor, half alpha, half oneMinusReflectivity, out half outModifiedAlpha)
{
#if defined(_ALPHAPREMULTIPLY_ON)
// NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
// Transparency 'removes' from Diffuse component
diffColor *= alpha; //预先计算alpha混合值,因为后面的alpha值会加上高光占比
#if (SHADER_TARGET < 30)
// SM2.0: instruction count limitation
// Instead will sacrifice part of physically based transparency where amount Reflectivity is affecting Transparency
// SM2.0: uses unmodified alpha
outModifiedAlpha = alpha;
#else
// Reflectivity 'removes' from the rest of components, including Transparency
// outAlpha = 1-(1-alpha)*(1-reflectivity) = 1-(oneMinusReflectivity - alpha*oneMinusReflectivity) =
// = 1-oneMinusReflectivity + alpha*oneMinusReflectivity
//最终透明度 = 高光占比 + alpha * 漫反射占比
outModifiedAlpha = 1-oneMinusReflectivity + alpha*oneMinusReflectivity;
#endif
#else
outModifiedAlpha = alpha;
#endif
return diffColor;
}
继续看片元处理器执行流程:
UNITY_SETUP_INSTANCE_ID(i); //gpu instance,设置static uint unity_InstanceID;
UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy); //LOD_FADE_CROSSFADE,感兴趣可研究
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); //vr相关
UnityLight mainLight = MainLight (); //获取平行光颜色和方向
UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld); //计算应用阴影后的光照缩减值atten
half occlusion = Occlusion(i.tex.xy); //采样光照遮挡
//获取直接光照和间接光照
UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
//使用BRDF,计算光照影响后的颜色值
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
c.rgb += Emission(i.tex.xy); //加上自发光颜色
UNITY_EXTRACT_FOG_FROM_EYE_VEC(i); //获取雾效系数_unity_fogCoord
UNITY_APPLY_FOG(_unity_fogCoord, c.rgb); //使用雾效系数_unity_fogCoord,计算雾效
return OutputForward (c, s.alpha); //设置输出的alpha值
展开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)
//提取光照贴图uv坐标
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);
}
}
继续展开UnityGlobalIllumination:
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:
inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
UnityGI o_gi;
ResetUnityGI(o_gi);
// Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason
#if defined(HANDLE_SHADOWS_BLENDING_IN_GI)
half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos);
float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz);
float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist);
//光照衰减值
data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist));
#endif
//直接光
o_gi.light = data.light;
o_gi.light.color *= data.atten;
//间接光
//将顶点球谐光,以及光照贴图中采样的光,保存在间接光的diffuse分量,烘培时不包含高光反射
#if UNITY_SHOULD_SAMPLE_SH
//片元中计算球谐光
o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
#endif
... //获取光照贴图中的光,累加在间接光的diffuse分量
o_gi.indirect.diffuse *= occlusion; //间接光屏蔽
return o_gi;
}
最后我们看基于物理的BRDF实现:
// Default BRDF to use:
#if !defined (UNITY_BRDF_PBS) // allow to explicitly override BRDF in custom shader
// still add safe net for low shader models, otherwise we might end up with shaders failing to compile
#if SHADER_TARGET < 30 || defined(SHADER_TARGET_SURFACE_ANALYSIS) // only need "something" for surface shader analysis pass; pick the cheap one
#define UNITY_BRDF_PBS BRDF3_Unity_PBS
#elif defined(UNITY_PBS_USE_BRDF3)
#define UNITY_BRDF_PBS BRDF3_Unity_PBS //Blinn-Phong BRDF
#elif defined(UNITY_PBS_USE_BRDF2)
#define UNITY_BRDF_PBS BRDF2_Unity_PBS //简化的BRDF
#elif defined(UNITY_PBS_USE_BRDF1)
#define UNITY_BRDF_PBS BRDF1_Unity_PBS //完整的BRDF
#else
#error something broke in auto-choosing BRDF
#endif
#endif
展开BRDF1_Unity_PBS:
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
float3 normal, float3 viewDir,
UnityLight light, UnityIndirect gi)
{
float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir);
// NdotV should not be negative for visible pixels, but it can happen due to perspective projection and normal mapping
// In this case normal should be modified to become valid (i.e facing camera) and not cause weird artifacts.
// but this operation adds few ALU and users may not want it. Alternative is to simply take the abs of NdotV (less correct but works too).
// Following define allow to control this. Set it to 0 if ALU is critical on your platform.
// This correction is interesting for GGX with SmithJoint visibility function because artifacts are more visible in this case due to highlight edge of rough surface
// Edit: Disable this code by default for now as it is not compatible with two sided lighting used in SpeedTree.
#define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0
#if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV
// The amount we shift the normal toward the view vector is defined by the dot product.
half shiftAmount = dot(normal, viewDir);
normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal;
// A re-normalization should be applied here but as the shift is small we don't do it to save ALU.
//normal = normalize(normal);
float nv = saturate(dot(normal, viewDir)); // TODO: this saturate should no be necessary here
#else
half nv = abs(dot(normal, viewDir)); // This abs allow to limit artifact
#endif
float nl = saturate(dot(normal, light.dir));
float nh = saturate(dot(normal, halfDir));
half lv = saturate(dot(light.dir, viewDir));
half lh = saturate(dot(light.dir, halfDir));
// HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
// Diffuse term,漫反射部分,没有除以Pi,为了和老的shader看起来一样
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
// Specular term,高光反射部分
float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
#if UNITY_BRDF_GGX
// GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping.
roughness = max(roughness, 0.002);
float V = SmithJointGGXVisibilityTerm (nl, nv, roughness); //遮挡函数
float D = GGXTerm (nh, roughness); //法线分布函数
#else
// Legacy
half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
#endif
//因为漫反射放大了Pi,高光反射也乘以Pi
float specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
# ifdef UNITY_COLORSPACE_GAMMA
specularTerm = sqrt(max(1e-4h, specularTerm));
# endif
// specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value
specularTerm = max(0, specularTerm * nl);
#if defined(_SPECULARHIGHLIGHTS_OFF)
specularTerm = 0.0;
#endif
// surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1)
half surfaceReduction; //用于间接高光反射
# ifdef UNITY_COLORSPACE_GAMMA
surfaceReduction = 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
# else
surfaceReduction = 1.0 / (roughness*roughness + 1.0); // fade \in [0.5;1]
# endif
// To provide true Lambert lighting, we need to be able to kill specular completely.
specularTerm *= any(specColor) ? 1.0 : 0.0;
half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) //漫反射部分
+ specularTerm * light.color * FresnelTerm (specColor, lh) //菲涅尔高光反射
+ surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); //间接高光反射
return half4(color, 1);
}