UnityShader32:PBR(上)

发布于:2021-10-21 02:47:12

?


一、整装待发
1.1:万事俱备

UnityShader相关:


UnityShader1:渲染流水线?~?UnityShader24:最简单的屏幕后处理例子最好还是了解一个图形接口:OpenGL 或者 DirectX永远别忘记 U3D 的官方手册

PBR相关:


PBR理论基础1:辐射度与BRDFPBR理论基础2:光照模型与微面元理论PBR理论基础3:基于图像的光照(上)PBR理论基础3.1:基于图像的光照(下)
1.2:只欠东风

准备好你的参考资料,以及……新建一个 Shader,就可以开始愉快的造轮子了:


Shader "Jaihk662/PBR"
{
Properties
{
_Color("MainColor", Color) = (1, 1, 1, 1)
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_Glossiness("Smoothness", Range(0, 1)) = 1 //光滑度
_Metallic("Metalness", Range(0, 1)) = 0 //金属度
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 200

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"

float4 _Color;
float4 _SpecularColor;
float _Glossiness;
float _Metallic;
//……
ENDCG
}
}
FallBack "Specular"
}

金属度(Metalness)和光滑度(Smoothness)这两个万分重要的属性当然是必不可少的


?


?


二、重要属性与方法

对于基础的数据结构和方法,可以提前准备好,包括但不限于:


光照方向?:lightDir法线方向?:normal视角方向?:viewDir半角方向??:halfDir切线与副切线::tangentDir &?bitangentDir粗糙度?roughness光照衰减 atten

struct appdata
{
float4 vertex: POSITION;
float3 normal: NORMAL; //法线
float4 tangent: TANGENT; //切线
float2 uv: TEXCOORD0; //uv坐标
float2 lightMapUV: TEXCOORD1; //光照贴图uv坐标
float2 dynLightMapUV: TEXCOORD2; //动态光照贴图uv坐标
};

struct v2f
{
float4 pos: SV_POSITION;
float2 uv: TEXCOORD0; //uv坐标
float2 lightMapUV: TEXCOORD1; //光照贴图uv坐标
float2 dynLightMapUV: TEXCOORD2; //动态光照贴图uv坐标

float3 normalDir: TEXCOORD3; //法线
float3 posWorld: TEXCOORD4; //当前像素所在世界空间坐标
float3 tangentDir: TEXCOORD5; //主切线
float3 bitangentDir: TEXCOORD6; //副切线
LIGHTING_COORDS(7, 8) //Unity自带阴影
UNITY_FOG_COORDS(9) //Unity内置雾效
};

v2f vert(appdata v)
{
v2f o;
o.uv = v.uv;
o.lightMapUV = v.lightMapUV;
o.dynLightMapUV = v.dynLightMapUV;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.tangentDir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz);
o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);

o.pos = UnityObjectToClipPos(v.vertex);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
UNITY_TRANSFER_FOG(o, o.pos);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}

fixed4 frag(v2f i): SV_Target
{
//法线方向
float3 normal = normalize(i.normalDir);
//光源方向,_WorldSpaceLightPos0.xyz为当前Pass中主光源方向,_WorldSpaceLightPos0.w为1表示当前光源为点光源或聚光灯,为0表示当前光源为*行光
float3 lightDir = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz, _WorldSpaceLightPos0.w));
//光源反射向量
float3 lightReflectDir = reflect(-lightDir, normal);
//视角方向
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
//视角反射方向
float3 viewReflectDir = normalize(reflect(-viewDir, normal));
//视线与光照半角方向
float3 halfDir = normalize(viewDir + lightDir);

float NdotL = max(0.0, dot(normal, lightDir));
float NdotH = max(0.0, dot(normal, halfDir));
float NdotV = max(0.0, dot(normal, viewDir));
float VdotH = max(0.0, dot(viewDir, halfDir));
float LdotH = max(0.0, dot(lightDir, halfDir));
float LdotV = max(0.0, dot(lightDir, viewDir));
float RdotV = max(0.0, dot(lightReflectDir, viewDir));

//得到光照衰减
float attenuation = LIGHT_ATTENUATION(i);
//光照衰减颜色
float3 attenColor = attenuation * _LightColor0.rgb;
//粗糙度
float roughness = 1 - (_Glossiness * _Glossiness);
roughness = roughness * roughness;

float3 diffColor = _Color.rgb * (1 - _Metallic) ;
float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);

fixed4 col = fixed4(diffColor + specColor, 1.0);
return col;
}

使用了输入光滑度(Smoothness)的*方来重新计算粗糙度(roughness)可以得到一个不错的效果,而对于金属度(Metalness),它会同时影响漫反射和镜面反射的颜色


对于非金属,它拥有绝对的漫反射以及镜面反射,它们全部来源于纹理输入对于金属,它几乎没有漫反射,因此漫反射项颜色往往用于驱动镜面反射

?


?


三、完整的反射方程

完整的?Cook-Torrance BRDF 模型的 PBR 方程如下:



在计算菲涅尔系数时,通常要考虑金属和非金属(电介质),对于金属或导体(Conductor)表面,基础反射率??的值需要进行混合,对应的伪代码就是:


vec3 F0 = vec3(0.04);
F0 = mix(F0, surfaceColor.rgb, metalness);

代入??求出对应的菲涅尔系数理应就是??项,但由于金属几乎是不产生漫反射的,所以在计算漫反射项时减去镜面反射的比例后,还要再乘以电介质的比例,这也可以算到??项中,得到?


对于镜面反射项系数?,由于 ?中菲涅尔项? 本身就已经体现了镜面反射的比例,所以这里可以直接去掉,好了,这样去掉 ?和 ?项的 PBR 反射方程就是:




UnityStandard 在计算 BRDF 时将整体乘上了一个?,这也是为什么很多时候自己实现的 BRDF 会显得过暗的原因



本章完整代码如下:


Shader "Jaihk662/PBR"
{
Properties
{
_Color("MainColor", Color) = (1, 1, 1, 1)
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_Glossiness("Smoothness", Range(0, 1)) = 1 //光滑度
_Metallic("Metalness", Range(0, 1)) = 0 //金属度
_Ior("Ior", Range(1, 4)) = 1.5 //折射指数,用于计算基础反射率
_LUT("LUT", 2D) = "white" {}
}

CGINCLUDE
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"
#include "UnityStandardBRDF.cginc"

float4 _Color;
float4 _SpecularColor;
float _Glossiness;
float _Metallic;
float _Ior;
sampler2D _LUT;

#define PI 3.1415926535
#define CalculationKAverage(p) pow(p, 2) * 0.797884560802865
#define CalculationKDirect(p) pow(p + 1, 2) / 8
#define CalculationKIBL(p) pow(p, 2) / 8
#define MixFunction(i, j, x) j * x + i * (1.0 - x)
//--------------------------------------------------Distribution Begin--------------------------------------------------
//Beckman NDF(D项)
float BeckmannNormalDistribution(float roughness, float NdotH)
{
float roughnessSqr = roughness * roughness;
float NdotHSqr = NdotH * NdotH;
return max(0.000001, (1.0 / (PI * roughnessSqr * NdotHSqr * NdotHSqr)) * exp((NdotHSqr - 1) / (roughnessSqr * NdotHSqr)));
}

//GGX NDF(D项)
float GGXNormalDistribution(float roughness, float NdotH)
{
float roughnessSqr = roughness * roughness;
float NdotHSqr = NdotH * NdotH;
float TanNdotHSqr = (1 - NdotHSqr) / NdotHSqr;
return (1.0 / PI) * sqrt(roughness / (NdotHSqr * (roughnessSqr + TanNdotHSqr)));
}
//--------------------------------------------------Distribution End--------------------------------------------------

//--------------------------------------------------Geometric Begin--------------------------------------------------
//Schlick-Beckman Geometric(G项)
float SchlickBeckmanGeometricShadowingFunction(float NdotL, float NdotV, float roughness)
{
float k = CalculationKDirect(roughness);
float SmithL = NdotL / (NdotL * (1 - k) + k);
float SmithV = NdotV / (NdotV * (1 - k) + k);
float Gs = SmithL * SmithV;
return Gs;
}
//--------------------------------------------------Geometric End--------------------------------------------------

//--------------------------------------------------Fresnel Begin--------------------------------------------------
float SchlickFresnel(float i)
{
float x = clamp(1.0 - i, 0.0, 1.0);
float x2 = x * x;
return x2 * x2 * x;
}
//Schlick-Fresnel Fresnel(F项)
float3 SchlickFresnelFunction(float3 F0, float LdotH)
{
return F0 + (1 - F0) * SchlickFresnel(LdotH);
}
float SchlickFresnelFunctionSingle(float F0, float LdotH)
{
return F0 + (1 - F0) * SchlickFresnel(LdotH);
}

//Spherical-Gaussian-Fresnel Fresnel拟合(F项)
float SphericalGaussianFresnelFunction(float3 F0, float LdotH)
{
float power = ((-5.55473 * LdotH) - 6.98316) * LdotH;
return F0 + (1 - F0) * pow(2, power);
}
//利用折射指数(Indices of Refraction, IOR)直接计算金属材质的F0
float SchlickIORFresnelFunction(float ior, float LdotH)
{
float F0 = pow(ior - 1, 2)/pow(ior + 1, 2);
return SchlickFresnelFunctionSingle(F0, LdotH);
}
//利用插值计算金属材质的F0,其中unity_ColorSpaceDielectricSpec.rgb为所有非金属基础反射率的*均值,约为0.04
float SchlickLerpFresnelFunction(float LdotH)
{
float F0 = lerp(unity_ColorSpaceDielectricSpec.rgb, _Color, _Metallic);
return SchlickFresnelFunctionSingle(F0, LdotH);
}
//DisneyDiffuseFresnel 漫反射F项
float DisneyDiffuseFresnel(float NdotL, float NdotV, float LdotH, float roughness)
{
float FresnelLight = SchlickFresnel(NdotL);
float FresnelView = SchlickFresnel(NdotV);
float FresnelDiffuse90 = 0.5 + 2.0 * LdotH * LdotH * roughness;
return MixFunction(1, FresnelDiffuse90, FresnelLight) * MixFunction(1, FresnelDiffuse90, FresnelView);
}
//--------------------------------------------------Fresnel End--------------------------------------------------
ENDCG

SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 200

Pass
{
CGPROGRAM
struct appdata
{
float4 vertex: POSITION;
float3 normal: NORMAL; //法线
float4 tangent: TANGENT; //切线
float2 uv: TEXCOORD0; //uv坐标
float2 lightMapUV: TEXCOORD1; //光照贴图uv坐标
float2 dynLightMapUV: TEXCOORD2; //动态光照贴图uv坐标
};

struct v2f
{
float4 pos: SV_POSITION;
float2 uv: TEXCOORD0; //uv坐标
float2 lightMapUV: TEXCOORD1; //光照贴图uv坐标
float2 dynLightMapUV: TEXCOORD2; //动态光照贴图uv坐标

float3 normalDir: TEXCOORD3; //法线
float3 posWorld: TEXCOORD4; //当前像素所在世界空间坐标
float3 tangentDir: TEXCOORD5; //主切线
float3 bitangentDir: TEXCOORD6; //副切线
LIGHTING_COORDS(7, 8) //Unity自带阴影
UNITY_FOG_COORDS(9) //Unity内置雾效
};

v2f vert(appdata v)
{
v2f o;
o.uv = v.uv;
o.lightMapUV = v.lightMapUV;
o.dynLightMapUV = v.dynLightMapUV;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.tangentDir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz);
o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);

o.pos = UnityObjectToClipPos(v.vertex);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
UNITY_TRANSFER_FOG(o, o.pos);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}

fixed4 frag(v2f i): SV_Target
{
//法线方向
float3 normal = normalize(i.normalDir);
//光源方向,_WorldSpaceLightPos0.xyz为当前Pass中主光源方向,_WorldSpaceLightPos0.w为1表示当前光源为点光源或聚光灯,为0表示当前光源为*行光
float3 lightDir = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz, _WorldSpaceLightPos0.w));
//光源反射向量
float3 lightReflectDir = reflect(-lightDir, normal);
//视角方向
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
//视角反射方向
float3 viewReflectDir = normalize(reflect(-viewDir, normal));
//视线与光照半角方向
float3 halfDir = normalize(viewDir + lightDir);

float NdotL = max(0.00001, dot(normal, lightDir));
float NdotH = max(0.00001, dot(normal, halfDir));
float NdotV = max(0.00001, dot(normal, viewDir));
float VdotH = max(0.00001, dot(viewDir, halfDir));
float LdotH = max(0.00001, dot(lightDir, halfDir));
float LdotV = max(0.00001, dot(lightDir, viewDir));
float RdotV = max(0.00001, dot(lightReflectDir, viewDir));

//得到光照衰减
float attenuation = LIGHT_ATTENUATION(i);
//光照衰减颜色
float3 attenColor = attenuation * _LightColor0.rgb;
//粗糙度
float roughness = 1 - (_Glossiness * _Glossiness);
roughness = roughness * roughness;

float Fd = DisneyDiffuseFresnel(NdotL, NdotV, LdotH, roughness);
float3 diffColor = _Color.rgb * (1 - _Metallic) * Fd;
float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);

float3 D = specColor;
float G = 1;
float3 F = specColor;
D *= GGXNormalDistribution(roughness, NdotH);
G *= SchlickBeckmanGeometricShadowingFunction(NdotL, NdotV, roughness);
F *= SchlickFresnelFunction(specColor, LdotH);

float3 specularity = (D * G * F) / (4 * (NdotL * NdotV)) * PI;
float3 directLightCol = (diffColor + specularity) * NdotL * attenColor;
float4 finalCol = float4(directLightCol, 1);

UNITY_APPLY_FOG(i.fogCoord, finalCol);
fixed4 col = finalCol;
return col;
}
ENDCG
}
}
FallBack "Specular"
}

你会发现金属度高的时候材质显得过黑,如果是这样那就对了,因为目前并没有考虑间接光照


?


?


四、法线分布函数(Normal Distribution Function, NDF)

依赖完整的 Cook-Torrance BRDF 模型,反射方程??? 中的??项就是法线分布函数(Normal Distribution Function)


其中最经典的莫过于之前的 Blinn-Phong 经验模型:,这个已经实现过不少次了,所以这次考虑使用一些更高端的模型


4.1?Beckman NDF

定义??为粗糙度 roughness,有??


Beckman NDF 额外的考虑了粗糙度值


CGINCLUDE
#define PI 3.1415926535
//Beckman NDF(D项)
float BeckmannNormalDistribution(float roughness, float NdotH)
{
float roughnessSqr = roughness * roughness;
float NdotHSqr = NdotH * NdotH;
return max(0.000001, (1.0 / (PI * roughnessSqr * NdotHSqr * NdotHSqr)) * exp((NdotHSqr - 1) / (roughnessSqr * NdotHSqr)));
}
ENDCG


4.2?GGX NDF

定义??为粗糙度 roughness,,有?


最常用的经验公式,这也是 UnityStandard 中使用的标准各向同性分布函数


//GGX NDF(D项)
float GGXNormalDistribution(float roughness, float NdotH)
{
float roughnessSqr = roughness * roughness;
float NdotHSqr = NdotH * NdotH;
float TanNdotHSqr = (1 - NdotHSqr) / NdotHSqr;
return (1.0 / PI) * sqrt(roughness / (NdotHSqr * (roughnessSqr + TanNdotHSqr)));
}


4.3 未完待续……

更多的经验方程可参考:https://www.jordanstevenstechart.com/physically-based-rendering


?


?


五、阴影遮蔽函数(Geometric Shadowing Function, GSF)

依赖完整的 Cook-Torrance BRDF 模型,反射方程??? 中的 G?项就是阴影遮蔽函数(Geometric Shadowing Function)


如果不想要计算这一项,那么可以默认值为1,所有的阴影遮蔽函数值域都为?


5.1?Schlick-Beckman?GSF

定义??为粗糙度 roughness,,有??


当然这里的? ?可以说是一个折中,对于直接光和间接光,还可以针对性的使用下面两个公式


针对光照时:针对 IBL 的重映射(Remapping):

其实本质上,G 项为入射光遮蔽和出射光遮蔽两者的乘积,也就是?


#define CalculationKAverage(p) pow(p, 2) * 0.797884560802865;
#define CalculationKDirect(p) pow(p + 1, 2) / 8;
#define CalculationKIBL(p) pow(p, 2) / 8;
//Schlick-Beckman Geometric(G项)
float SchlickBeckmanGeometricShadowingFunction(float NdotL, float NdotV, float roughness)
{
float k = CalculationKDirect(roughness);
float SmithL = (NdotL) / (NdotL * (1 - k) + k);
float SmithV = (NdotV) / (NdotV * (1 - k) + k);
float Gs = (SmithL * SmithV);
return Gs;
}

5.2 未完待续……

更多的经验方程可参考:https://www.jordanstevenstechart.com/physically-based-rendering


?


?


六、菲涅尔项(Fresnel Function)

依赖完整的 Cook-Torrance BRDF 模型,反射方程??? 中的 F?项就是菲涅尔项(Fresnel Function)


不必多说,大名鼎鼎的?Fresnel-Schlick *似,它几乎是最常用的经验方程:


6.1?Fresnel-Schlick *似

Fresnel-Schlick 还有另一种拟*姹 Spherical-Gaussian Fresnel:,它的效率要高一些,这在 UE4 中被使用



float SchlickFresnel(float i)
{
float x = clamp(1.0 - i, 0.0, 1.0);
float x2 = x * x;
return x2 * x2 * x;
}
//Schlick-Fresnel Fresnel(F项)
float3 SchlickFresnelFunction(float3 F0, float LdotH)
{
return F0 + (1 - F0) * SchlickFresnel(LdotH);
}
//Spherical-Gaussian-Fresnel Fresnel拟合(F项)
float SphericalGaussianFresnelFunction(float3 F0, float LdotH)
{
float power = ((-5.55473 * LdotH) - 6.98316) * LdotH;
return F0 + (1 - F0) * pow(2, power);
}

对于金属材质的?Fresnel 计算,需要额外基础反射率,有三个方案:


    通过表面颜色(一般是反照率 Albedo)和非金属基础反射率的*均值 0.04 插值得到利用折射指数(Indices of Refraction, IOR)直接计算,其中 IOR 由输入指定直接将计算得到的高光颜色(specColor)作为基础反射率

对于①②的代码如下:


//利用折射指数(Indices of Refraction, IOR)直接计算金属材质的F0
float SchlickIORFresnelFunction(float ior, float LdotH)
{
float F0 = pow(ior - 1, 2)/pow(ior + 1, 2);
return SchlickFresnelFunctionSingle(F0, LdotH);
}
//利用插值计算金属材质的F0,其中unity_ColorSpaceDielectricSpec.rgb为所有非金属基础反射率的*均值,约为0.04
float SchlickLerpFresnelFunction(float LdotH)
{
float F0 = lerp(unity_ColorSpaceDielectricSpec.rgb, _Color, _Metallic);
return SchlickFresnelFunctionSingle(F0, LdotH);
}
float SchlickFresnelFunctionSingle(float F0, float LdotH)
{
return F0 + (1 - F0) * SchlickFresnel(LdotH);
}

6.2 Disney BRDF Fresnel

在计算漫反射项??时,?项的计算也可参考?Disney BRDF:


,其中??

此时?,这也是 UnityStandard 的写法


#define MixFunction(i, j, x) j * x + i * (1.0 - x)
float DisneyDiffuseFresnel(float NdotL, float NdotV, float LdotH, float roughness)
{
float FresnelLight = SchlickFresnel(NdotL);
float FresnelView = SchlickFresnel(NdotV);
float FresnelDiffuse90 = 0.5 + 2.0 * LdotH * LdotH * roughness;
return MixFunction(1, FresnelDiffuse90, FresnelLight) * MixFunction(1, FresnelDiffuse90, FresnelView);
}

?


?


参考文章:


https://www.yuque.com/dachengzi-shgxu/manual/uw9c34https://www.jordanstevenstechart.com/physically-based-renderinghttps://zhuanlan.zhihu.com/p/96975561https://zhuanlan.zhihu.com/p/158025828https://blog.csdn.net/Jaihk662/article/details/110644023https://zhuanlan.zhihu.com/p/68025039

?

相关推荐

最新更新

猜你喜欢