上篇中分析了Simple Lit Forward Pass的Vertex Shader都计算和输出了什么数据,本篇就看看在Fragment shader中是怎么使用这些数据计算最终的光照颜色的。
由于代码较短,就都贴出来方便查看:
half4 LitPassFragmentSimple(Varyings input) : SV_Target
{UNITY_SETUP_INSTANCE_ID(input);UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);float2 uv = input.uv;half4 diffuseAlpha = SampleAlbedoAlpha(uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap));half3 diffuse = diffuseAlpha.rgb * _BaseColor.rgb;half alpha = diffuseAlpha.a * _BaseColor.a;AlphaDiscard(alpha, _Cutoff);#ifdef _ALPHAPREMULTIPLY_ONdiffuse *= alpha;#endifhalf3 normalTS = SampleNormal(uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap));half3 emission = SampleEmission(uv, _EmissionColor.rgb, TEXTURE2D_ARGS(_EmissionMap, sampler_EmissionMap));half4 specular = SampleSpecularSmoothness(uv, alpha, _SpecColor, TEXTURE2D_ARGS(_SpecGlossMap, sampler_SpecGlossMap));half smoothness = specular.a;InputData inputData;InitializeInputData(input, normalTS, inputData);half4 color = UniversalFragmentBlinnPhong(inputData, diffuse, specular, smoothness, emission, alpha);color.rgb = MixFog(color.rgb, inputData.fogCoord);color.a = OutputAlpha(color.a, _Surface);return color;
}
之前在分析Depth Only Pass的时候就看过alpha test的处理,和这儿有些类似,但是由于Depth only pass更加通用,所以会在使用albedo贴图alpha的时候做一些判断,在某些情况下这个贴图的alpha是不用来做alpha test的(贴图的alpha通道存储的是smoothness, glossiness这样的值),因此就不使用它作为alpha test的参数。而Simple Lit这儿就简单了,因为这个shader里面不使用那些关键字对应的功能,因此也无需判断那些关键字。
注意这儿采样贴图使用的函数名是SampleAlbedoAlpha
,这个函数之前已经看过,就是使用SAMPLE_TEXTURE2D
采样一张2D贴图,采样出来的结果是一个half4
。你可以理解为这个half4
包含了rgb反射率(albedo)和alpha。即不是简单的将贴图中的texel看成是一个颜色。diffuseAlpha.rgb
和_BaseColor.rgb
相乘作为最终的diffuse系数(其实仍然是反射率),而diffuseAlpha.a
和_BaseColor.a
相乘得到最终的alpha
,使用AlphaDiscard
函数进行Alpha Test。
void AlphaDiscard(real alpha, real cutoff, real offset = 0.0h)
{#ifdef _ALPHATEST_ONclip(alpha - cutoff + offset);#endif
}
最终调用的是hlsl的clip函数,clip
函数当接受的参数值小于0时丢弃这个片段。这儿的offset一般都不会设置,就是0,可以忽略,而alpha和cutoff比较,如果alpha < cutoff
则片段没通过alpha test被丢弃。所以cutoff设置的是可通过alpha test的最小的alpha值,大于或等于cutoff的alpha值是通过alpha test的。
还记得一开始我们看unlit shader的属性时说的BaseShaderGUI
吗?预乘alpha开启的条件其实就是blend mode为Premultiply
:
case BlendMode.Premultiply:material.SetInt("_SrcBlend", (int) UnityEngine.Rendering.BlendMode.One);material.SetInt("_DstBlend", (int) UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
此时,srcBlend为one,而dstBlend为1-srcAlpha
,即blend的公式为:
finalColor = srcColor + (1-srcAlpha)*dstColor
和标准的alpha blend模式相比,srcColor在混合时不需要再乘以alpha,因为alpha已经预先乘上去了:
#ifdef _ALPHAPREMULTIPLY_ONdiffuse *= alpha;
#endif
这两种做法有何区别?很显然,这儿只有diffuse使用了alpha,而如果在blend时使用alpha是对最终颜色乘以alpha,所以这种做法得到的颜色应该更亮一些。因为可以认为乘alpha是减淡颜色。再多说一句预乘alpha
这个技术,早期经常用在2d游戏渲染半透明sprite,在载入sprite时即预乘alpha(或是在导入资源时处理sprite图片),这样在渲染时就可以少一个乘法,性能提高了而效果是一样的,当然了这种做法时alpha值是不能变的,不能做fade in/fade out。
half3 normalTS = SampleNormal(uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap));
SampleNormal
函数如下:
half3 SampleNormal(float2 uv, TEXTURE2D_PARAM(bumpMap, sampler_bumpMap), half scale = 1.0h)
{
#ifdef _NORMALMAPhalf4 n = SAMPLE_TEXTURE2D(bumpMap, sampler_bumpMap, uv);#if BUMP_SCALE_NOT_SUPPORTEDreturn UnpackNormal(n);#elsereturn UnpackNormalScale(n, scale);#endif
#elsereturn half3(0.0h, 0.0h, 1.0h);
#endif
}
如果定义了相应的关键字,则采样法线贴图,且根据是否使用了bump scale,使用不同的解码函数。这两个unpcak函数在SRP Core的Packing.hlsl
中,涉及到法线贴图相关的编码方式,这儿不细说,也许会有一篇单独分析法线贴图会说。一般来说,法线贴图会保存切线空间的法线,因此这儿的变量名为normalTS
。
half3 emission = SampleEmission(uv, _EmissionColor.rgb, TEXTURE2D_ARGS(_EmissionMap, sampler_EmissionMap));
half3 SampleEmission(float2 uv, half3 emissionColor, TEXTURE2D_PARAM(emissionMap, sampler_emissionMap))
{
#ifndef _EMISSIONreturn 0;
#elsereturn SAMPLE_TEXTURE2D(emissionMap, sampler_emissionMap, uv).rgb * emissionColor;
#endif
}
就是采样一张自发光贴图再乘以一个自发光颜色。
half4 specular = SampleSpecularSmoothness(uv, alpha, _SpecColor, TEXTURE2D_ARGS(_SpecGlossMap, sampler_SpecGlossMap));
half smoothness = specular.a;
SampleSpecularSmoothness
函数在SimpleLitInput.hlsl
中:
TEXTURE2D(_SpecGlossMap); SAMPLER(sampler_SpecGlossMap);half4 SampleSpecularSmoothness(half2 uv, half alpha, half4 specColor, TEXTURE2D_PARAM(specMap, sampler_specMap))
{half4 specularSmoothness = half4(0.0h, 0.0h, 0.0h, 1.0h);
#ifdef _SPECGLOSSMAPspecularSmoothness = SAMPLE_TEXTURE2D(specMap, sampler_specMap, uv) * specColor;
#elif defined(_SPECULAR_COLOR)specularSmoothness = specColor;
#endif#ifdef _GLOSSINESS_FROM_BASE_ALPHAspecularSmoothness.a = exp2(10 * alpha + 1);
#elsespecularSmoothness.a = exp2(10 * specularSmoothness.a + 1);
#endifreturn specularSmoothness;
}
根据不同的关键字,从高光贴图中采样出高光颜色或者直接使用材质定义的高光色。注意这儿的_GLOSSINESS_FROM_BASE_ALPHA
,前面alpha test那儿说过,这个关键字表示base贴图的alpha存储的是glossiness,否则就使用高光贴图的alpha作为glossiness使用。这儿使用exp2解码smoothness,说明贴图中存储的是log空间。
后面还有两篇讲这个Fragment Shader,这个Shader虽然不复杂,光照计算也只是传统的BlinnPhong,但是涉及到非常多的Unity光照系统的东西以及URP的惯例用法,所以内容较多,咱们慢慢来。
下一篇:独立站的免费流量玩法