赞
踩
微表面模型参考资料:https://blog.uwa4d.com/archives/Study_PBR.html
更精确的微表面模型GGX参考资料:https://blog.uwa4d.com/archives/1582.html
实时全局光照:https://blog.uwa4d.com/archives/Study_PRT.html
接下来我们针对当前的内容看下Unity自带的pbr源码:
#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自己定义了一个Unity_Standard_Simple的宏,用阿里处理是否使用简单版的pbr。
我们先看完整版的:
struct VertexOutputForwardBase
{
UNITY_POSITION(pos);
float4 tex : TEXCOORD0;
float3 eyeVec : TEXCOORD1;
float4 tangentToWorldAndPackedData[3] : TEXCOORD2; // [3x3:tangentToWorld | 1x3:viewDirForParallax or worldPos]
half4 ambientOrLightmapUV : TEXCOORD5; // SH or Lightmap UV
UNITY_SHADOW_COORDS(6)
UNITY_FOG_COORDS(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
UNITY_VERTEX_OUTPUT_STEREO
};
VertexOutputForwardBase vertForwardBase (VertexInput v)
{
UNITY_SETUP_INSTANCE_ID(v); //开启Gpu instance
VertexOutputForwardBase o;
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
UNITY_TRANSFER_INSTANCE_ID(v, o);
//立体渲染,vr相关的
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
//是否需要世界坐标
#if UNITY_REQUIRE_FRAG_WORLDPOS
//是否把世界坐标放在tangent的世界坐标矩阵中
#if UNITY_PACK_WORLDPOS_WITH_TANGENT
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 = 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);
函数说明,下面这个函数就是将切线世界坐标系全部转到世界坐标系
// 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;
// half3 binormal = cross(normal, tangent) * sign;
// return half3x3(tangent, binormal, normal);
// }
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_SHADOW(o, v.uv1);
o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);
函数说明,下面就是从GI中取到环境光的uv坐标值
inline half4 VertexGIForward(VertexInput v, float3 posWorld, half3 normalWorld)
{
half4 ambientOrLightmapUV = 0;
// Static lightmaps
#ifdef LIGHTMAP_ON
//uv1就是烘焙的光照贴图,ST.xy为 下图中的Tiling,zw为下图中的offset.
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
// Used in ForwardBase pass: Calculates diffuse lighting from 4 point lights, with data packed in a special way.
//最多四个顶点光源,可以参考https://zhuanlan.zhihu.com/p/27842876,作用是返回漫反射的颜色
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);
//函数说明:这个就是用来计算球谐光照的函数,如果要让球谐光照都在片段着色器里执行,就啥都不做。
否则,可以使用ShadeSH9 在顶点着色器计算,具体可以参考http://gad.qq.com/article/detail/43699
简单来说,就是首先烘焙光照,会把该顶点的实时全局光照算出来,然后分解成三个函数正交基底的和,并且把他们的系数保存起来。而使用的时候,就是传入法线就可以取得这个方向传过来的环境光。但我们也发现了,这样子似乎完全没考虑光源位置的情况。而且遮挡情况也没有,所以在顶点中计算球谐光照是有有很多限制的。也就是所谓的SIMPLE的原因吧。
half3 ShadeSHPerVertex (half3 normal, half3 ambient)
{
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
// Completely per-pixel
// nothing to do here
#elif (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
// Completely per-vertex
ambient += max(half3(0,0,0), ShadeSH9 (half4(normal, 1.0)));
#else
// L2 per-vertex, L0..L1 & gamma-correction per-pixel
// NOTE: SH data is always in Linear AND calculation is split between vertex & pixel
// Convert ambient to Linear and do final gamma-correction at the end (per-pixel)
#ifdef UNITY_COLORSPACE_GAMMA
ambient = GammaToLinearSpace (ambient);
#endif
ambient += SHEvalLinearL2 (half4(normal, 1.0)); // no max since this is only L2 contribution
#endif
return ambient;
}
#endif
#ifdef DYNAMICLIGHTMAP_ON
ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
return ambientOrLightmapUV;
}
// normal should be normalized, w=1.0
// output in active color space
half3 ShadeSH9 (half4 normal)
{
// Linear + constant polynomial terms
half3 res = SHEvalLinearL0L1 (normal);
// Quadratic polynomials
res += SHEvalLinearL2 (normal);
# ifdef UNITY_COLORSPACE_GAMMA
res = LinearToGammaSpace (res);
# endif
return res;
}
//求出了三组正交积的系数,这里对法线有新的思考。首先,我们认为所有的光照信息已经都可以当做正交基底里面的了。
真正不同的x,其实就是法线而已。因为位置不同,归一化的正交基底是具有旋转不变性的。也就是说,位置不同并不会改变这组正交基底。我们依然可以根据法线,去重建这个光照信息。但顶点的时候,确实只是将颜色返回了。并没有考虑其他任何的情况。我们可继续看后面怎么处理。
// normal should be normalized, w=1.0
half3 SHEvalLinearL0L1 (half4 normal)
{
half3 x;
// Linear (L1) + constant (L0) polynomial terms
x.r = dot(unity_SHAr,normal);
x.g = dot(unity_SHAg,normal);
x.b = dot(unity_SHAb,normal);
return x;
}
// normal should be normalized, w=1.0
half3 SHEvalLinearL2 (half4 normal)
{
half3 x1, x2;
// 4 of the quadratic (L2) polynomials
half4 vB = normal.xyzz * normal.yzzx;
x1.r = dot(unity_SHBr,vB);
x1.g = dot(unity_SHBg,vB);
x1.b = dot(unity_SHBb,vB);
// Final (5th) quadratic (L2) polynomial
half vC = normal.x*normal.x - normal.y*normal.y;
x2 = unity_SHC.rgb * vC;
return x1 + x2;
}
更多细节参考:https://blog.csdn.net/leonwei/article/details/78269765
这是视差贴图,通过把顶点从模型空间转到摄像机空间,然后在转到切线空间,
#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(o,o.pos);
return o;
}
half4 fragForwardBaseInternal (VertexOutputForwardBase i)
{
这个是LOD相关,先放着,意外发现国外一个大神
https://catlikecoding.com/unity/tutorials/
UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);
FRAGMENT_SETUP(s)
UNITY_SETUP_INSTANCE_ID(i);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
UnityLight mainLight = MainLight ();
UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);
//根据世界坐标转到光线坐标空间中,然后,根据输入的值,算出光线的衰减值,
然后去到阴影坐标,/目标结果就是从光照贴图中,取得
#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL * shadow;
#endif
half occlusion = Occlusion(i.tex.xy);
half Occlusion(float2 uv)
{
#if (SHADER_TARGET < 30)
// SM20: instruction count limitation
// SM20: simpler occlusion
return tex2D(_OcclusionMap, uv).g;
#else
half occ = tex2D(_OcclusionMap, uv).g;
return LerpOneTo (occ, _OcclusionStrength);
#endif
}
这个是AO图,AO就是根据每个顶点射出射线,看是否碰到物体来决定这个部位被遮挡的信息。
UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
//函数说明
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
unity_SpecCube0
, unity_SpecCube0_HDR
from the built-in shader variables. unity_SpecCube0 contains data for the active reflection probe. //unity_SpecCube0包含了现在激活的反射探头, unity_SpecCube0_HDR是其HDR颜色数据。
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);
}
}
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;
}
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;
#if UNITY_SHOULD_SAMPLE_SH
o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
half3 ShadeSHPerPixel (half3 normal, half3 ambient, float3 worldPos)
{
half3 ambient_contrib = 0.0;
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
// Completely per-pixel
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
if (unity_ProbeVolumeParams.x == 1.0)
ambient_contrib = SHEvalLinearL0L1_SampleProbeVolume(half4(normal, 1.0), worldPos);
else
ambient_contrib = SHEvalLinearL0L1(half4(normal, 1.0));
#else
ambient_contrib = SHEvalLinearL0L1(half4(normal, 1.0));
#endif
ambient_contrib += SHEvalLinearL2(half4(normal, 1.0));
ambient += max(half3(0, 0, 0), ambient_contrib);
#ifdef UNITY_COLORSPACE_GAMMA
ambient = LinearToGammaSpace(ambient);
#endif
#elif (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
// Completely per-vertex
// nothing to do here. Gamma conversion on ambient from SH takes place in the vertex shader, see ShadeSHPerVertex.
#else
// L2 per-vertex, L0..L1 & gamma-correction per-pixel
// Ambient in this case is expected to be always Linear, see ShadeSHPerVertex()
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
if (unity_ProbeVolumeParams.x == 1.0)
ambient_contrib = SHEvalLinearL0L1_SampleProbeVolume (half4(normal, 1.0), worldPos);
else
ambient_contrib = SHEvalLinearL0L1 (half4(normal, 1.0));
#else
ambient_contrib = SHEvalLinearL0L1 (half4(normal, 1.0));
#endif
ambient = max(half3(0, 0, 0), ambient+ambient_contrib); // include L2 contribution in vertex shader before clamp.
#ifdef UNITY_COLORSPACE_GAMMA
ambient = LinearToGammaSpace (ambient);
#endif
#endif
return ambient;
}
#endif
#if defined(LIGHTMAP_ON)
// Baked lightmaps
half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
half3 bakedColor = DecodeLightmap(bakedColorTex);
#ifdef DIRLIGHTMAP_COMBINED
fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
o_gi.indirect.diffuse += DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
ResetUnityLight(o_gi.light);
o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
#endif
#else // not directional lightmap
o_gi.indirect.diffuse += bakedColor;
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
ResetUnityLight(o_gi.light);
o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
#endif
#endif
#endif
#ifdef DYNAMICLIGHTMAP_ON
// Dynamic lightmaps
fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);
#ifdef DIRLIGHTMAP_COMBINED
half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
#else
o_gi.indirect.diffuse += realtimeColor;
#endif
#endif
o_gi.indirect.diffuse *= occlusion;
return o_gi;
}
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
/ Main Physically Based BRDF
// Derived from Disney work and based on Torrance-Sparrow micro-facet model
//
// BRDF = kD / pi + kS * (D * V * F) / 4
// I = BRDF * NdotL
//
// * NDF (depending on UNITY_BRDF_GGX):
// a) Normalized BlinnPhong
// b) GGX
// * Smith for Visiblity term
// * Schlick approximation for Fresnel
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);
half 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
half 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));
// Diffuse term
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
// Specular term
// 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
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);
half 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
half 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);
}
// Based on Minimalist CookTorrance BRDF
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
//
// * NDF (depending on UNITY_BRDF_GGX):
// a) BlinnPhong
// b) [Modified] GGX
// * Modified Kelemen and Szirmay-Kalos for Visibility term
// * Fresnel approximated with 1/LdotH
// Old school, not microfacet based Modified Normalized Blinn-Phong BRDF
// Implementation uses Lookup texture for performance
//
// * Normalized BlinnPhong in RDF form
// * Implicit Visibility term
// * No Fresnel term
//
// TODO: specular is too weak in Linear rendering mode
half4 BRDF3_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
float3 normal, float3 viewDir,
UnityLight light, UnityIndirect gi)
{
float3 reflDir = reflect (viewDir, normal);
half nl = saturate(dot(normal, light.dir));
half nv = saturate(dot(normal, viewDir));
// Vectorize Pow4 to save instructions
half2 rlPow4AndFresnelTerm = Pow4 (float2(dot(reflDir, light.dir), 1-nv)); // use R.L instead of N.H to save couple of instructions
half rlPow4 = rlPow4AndFresnelTerm.x; // power exponent must match kHorizontalWarpExp in NHxRoughness() function in GeneratedTextures.cpp
half fresnelTerm = rlPow4AndFresnelTerm.y;
half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
half3 color = BRDF3_Direct(diffColor, specColor, rlPow4, smoothness);
color *= light.color * nl;
color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm);
return half4(color, 1);
}
c.rgb += Emission(i.tex.xy);
UNITY_APPLY_FOG(i.fogCoord, c.rgb);
return OutputForward (c, s.alpha);
}
其中有些部分还是过于复杂,暂时先这样打个笔记。
以后用到在详细研究
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。