Created by miccall (转载请注明出处 miccall.tech)
unity PBS 分为 metallic 和 specular 两种
一. Metallic workflow :
fixed3 Albedo; // base (diffuse or specular) color
float3 Normal; // tangent space normal, if written
half3 Emission;
half Metallic; // 0 = 0 non-metal , 1 = 1 metal
// Smoothness is the user facing name, it should be perceptual smoothness but user should not have to deal with it.
// Everywhere in the code you meet smoothness it is perceptual smoothness
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
在 这个工作流中 ,untiy 又分为 前向渲染和 延时渲染两种 :
LightingStandard
LightingStandard_Deferred
同时,还有一个计算 GI 的
LightingStandard_GI
Albedo的计算 :
DiffuseAndSpecularFromMetallic
Normal的计算略
Albedo的 alpha 通道计算 :
PreMultiplyAlpha
最终结果 = UNITY_BRDF_PBS
延迟渲染不同的是 ,我们计算 albedo 之后 ,
我们要把数据发送给Gbuffer
UnityStandardDataToGbuffer
从中获取计算数据 。
GI的计算 :
UnityGlobalIllumination
二. Specular workflow
fixed3 Albedo; // diffuse color
fixed3 Specular; // specular color
float3 Normal; // tangent space normal, if written
half3 Emission;
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
albedo 的计算 :
EnergyConservationBetweenDiffuseAndSpecular
Normal的计算
Albedo的 alpha 通道计算 :
PreMultiplyAlpha
最终结果 =
UNITY_BRDF_PBS
其余基本一样
三. 写一个最简单的 PBS shader
最后 ,根据unity的提高 ,写一个最简单的 PBS shader :
首先 ,定义的两个变量 ,选用metallic 工作流 :
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
metallic 前面加 【Gamma】,是因为一个细节是金属滑块本身应该处于伽马空间。
但是,在线性空间中渲染时,Unity不会自动对单个值进行伽马校正。
我们可以使用该Gamma属性告诉Unity它还应该对我们的金属滑块应用伽马校正。
其次 ,我们还有一张 Albedo 贴图 ,以及一个颜色
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
在vertshader中计算 clip Pos , wordpos ,normal 和 uv ,然后传入 fragmentshader
fragmentshader 中 ,计算 lightdir ,viewdir 。
然后我们先将颜色计算到 albedo
float3 specularTint;
float oneMinusReflectivity;
albedo = DiffuseAndSpecularFromMetallic(
albedo, _Metallic, specularTint, oneMinusReflectivity
);
然后,我们计算 直接光和间接光 :
UnityLight light;
light.color = lightColor;
light.dir = lightDir;
light.ndotl = DotClamped(i.normal, lightDir);
UnityIndirect indirectLight;
indirectLight.diffuse = 0;
indirectLight.specular = 0;
最后 ,我们用这些信息 ,计算最后的PBS :
return UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, _Smoothness,
i.normal, viewDir,
light, indirectLight
);
四. 复杂的 PBS shader
灯光判断
光照衰减
阴影投射和接受
顶点光
球谐光
自发光
Normalmap法线
IBL 环境映射
- 属性 :
Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
[NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {}
_Emission ("Emission", Color) = (0, 0, 0)
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
}
float4 _Tint;
sampler2D _MainTex, _DetailTex;
float4 _MainTex_ST, _DetailTex_ST;
sampler2D _NormalMap, _DetailNormalMap;
float _BumpScale, _DetailBumpScale;
sampler2D _MetallicMap;
float _Metallic;
float _Smoothness;
sampler2D _EmissionMap;
float3 _Emission;
- Interpolators
struct Interpolators {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
#if defined( BINORMAL_PER_FRAGMENT )
float4 tangent : TEXCOORD2;
#else
float3 tangent : TEXCOORD2;
float3 binormal : TEXCOORD3;
#endif
float3 worldPos : TEXCOORD4;
SHADOW_COORDS(5)
#if defined(VERTEXLIGHT_ON)
float3 vertexLightColor : TEXCOORD6;
#endif
};
- 直接光
UnityLight CreateLight (Interpolators i) {
UnityLight light;
#if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT)
light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
#else
light.dir = _WorldSpaceLightPos0.xyz;
#endif
UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);
light.color = _LightColor0.rgb * attenuation;
light.ndotl = DotClamped(i.normal, light.dir);
return light;
}
- 顶点光
一个带有四个点光源和六个物体的场景。所有物体都在四个灯的范围内。这需要每个对象五次绘制调用。(一个用于基础传递,加上四个附加传递。)这总共有30次调用 。
要包含Unity支持的所有四个顶点灯,我们必须执行四次相同的顶点灯计算,并将结果一起添加。
我们可以使用UnityCG中Shade4PointLights定义的函数,而不是自己编写所有代码。我们必须给它提供位置矢量,光色,衰减系数,加上顶点位置和法线。
void ComputeVertexLightColor (inout Interpolators i) {
#if defined(VERTEXLIGHT_ON)
i.vertexLightColor = 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, i.worldPos, i.normal
);
#endif
}
- 球谐光:
像顶点灯一样,我们会将球面谐波光数据添加到漫反射间接光中。另外,让我们确保它永远不会产生负面影响。毕竟,这是一个近似值
#if defined(FORWARD_BASE_PASS)
indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
#endif
// 启用球谐光
#define FORWARD_BASE_PASS
6.环境映射
float3 reflectionDir = reflect(-viewDir,i.normal);
float4 envSample = UNITY_SAMPLE_TEXCUBE ( unity_SpecCube0,reflectionDir);
indirectLight.specular=DecodeHDR(envSample,unity_SpecCube0_HDR);
为了增加模糊度:
float3 reflectionDir = reflect(-viewDir,i.normal);
Unity_GlossyEnvironmentData envData ;
envData.roughness = 1-_Smoothness;
envData.reflUVW = reflectionDir;
indirectLight.specular = Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE( unity_SpecCube0 ) , unity_SpecCube0_HDR,envData );
然而
roughness = 1-_Smoothness 也不是完全正确的 。
envData.roughness*=1.7-0.7*envData.roughness;
这个操作在
Unity_GlossyEnvironment 里面帮我们定义好了 也就不用我们计算了
BoxProjection:
float3 BoxProjection( float3 direction, float3 position, float4 cubemapPosition, float3 boxMin, float3 boxMax
)
{
UNITY_BRANCH
if(cubemapPosition.w>0){
float3 factors=
((direction>0?boxMax:boxMin)-position)/direction;
floatscalar=min(min(factors.x,factors.y),factors.z);
direction=direction*scalar+(position-cubemapPosition);
}
returndirection;
}
Unity_GlossyEnvironmentData envData;
envData.roughness = 1 - _Smoothness;
envData.reflUVW = BoxProjection(
reflectionDir, i.worldPos,
unity_SpecCube0_ProbePosition,
unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax
);
indirectLight.specular = Unity_GlossyEnvironment(
UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData
);
- 间接光:
UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
UnityIndirect indirectLight;
indirectLight.diffuse = 0;
indirectLight.specular = 0;
#if defined(VERTEXLIGHT_ON)
indirectLight.diffuse = i.vertexLightColor;
#endif
#if defined(FORWARD_BASE_PASS)
indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
float3 reflectionDir = reflect(-viewDir, i.normal);
Unity_GlossyEnvironmentData envData;
envData.roughness = 1 - GetSmoothness(i);
envData.reflUVW = BoxProjection(
reflectionDir, i.worldPos,
unity_SpecCube0_ProbePosition,
unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax
);
float3 probe0 = Unity_GlossyEnvironment(
UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData
);
envData.reflUVW = BoxProjection(
reflectionDir, i.worldPos,
unity_SpecCube1_ProbePosition,
unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax
);
#if UNITY_SPECCUBE_BLENDING
float interpolator = unity_SpecCube0_BoxMin.w;
UNITY_BRANCH
if (interpolator < 0.99999) {
float3 probe1 = Unity_GlossyEnvironment(
UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0),
unity_SpecCube0_HDR, envData
);
indirectLight.specular = lerp(probe1, probe0, interpolator);
}
else {
indirectLight.specular = probe0;
}
#else
indirectLight.specular = probe0;
#endif
#endif
return indirectLight;
}
8.获取metallic ,smoothness ,emssion
float GetMetallic (Interpolators i) {
#if defined(_METALLIC_MAP)
return tex2D(_MetallicMap, i.uv.xy).r;
#else
return _Metallic;
#endif
}
float GetSmoothness (Interpolators i) {
float smoothness = 1;
#if defined(_SMOOTHNESS_ALBEDO)
smoothness = tex2D(_MainTex, i.uv.xy).a;
#elif defined(_SMOOTHNESS_METALLIC) && defined(_METALLIC_MAP)
smoothness = tex2D(_MetallicMap, i.uv.xy).a;
#endif
return smoothness * _Smoothness;
}
float3 GetEmission (Interpolators i) {
#if defined(FORWARD_BASE_PASS)
#if defined(_EMISSION_MAP)
return tex2D(_EmissionMap, i.uv.xy) * _Emission;
#else
return _Emission;
#endif
#else
return 0 ;
#endif
}
- 顶点和片元着色
Interpolators MyVertexProgram (VertexData v) {
Interpolators i;
i.pos = UnityObjectToClipPos(v.vertex);
i.worldPos = mul(unity_ObjectToWorld, v.vertex);
i.normal = UnityObjectToWorldNormal(v.normal);
#if defined( BINORMAL_PER_FRAGMENT )
i.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
#else
i.tangent = UnityObjectToWorldDir(v.tangent.xyz);
i.binormal = CreateBinormal(i.normal, i.tangent, v.tangent.w);
#endif
i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);
TRANSFER_SHADOW(i);
ComputeVertexLightColor(i);
return i;
}
void InitializeFragmentNormal(inout Interpolators i) {
float3 mainNormal =
UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal =
UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal);
#if defined(BINORMAL_PER_FRAGMENT)
float3 binormal = CreateBinormal(i.normal, i.tangent.xyz, i.tangent.w);
#else
float3 binormal = i.binormal;
#endif
i.normal = normalize(
tangentSpaceNormal.x * i.tangent +
tangentSpaceNormal.y * binormal +
tangentSpaceNormal.z * i.normal
);
}
float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
InitializeFragmentNormal(i);
float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
albedo *= tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;
float3 specularTint;
float oneMinusReflectivity;
albedo = DiffuseAndSpecularFromMetallic(
albedo, GetMetallic(i), specularTint, oneMinusReflectivity
);
float4 color = UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, GetSmoothness(i),
i.normal, viewDir,
CreateLight(i), CreateIndirectLight(i, viewDir)
);
color.rgb += GetEmission(i);
return color;
}