|
一、PBR理论
1.1基础理论
PBR模型是基于物理渲染的模型,因为其公式是基于对真实世界的模拟,所以PBR模型对材质的表现能力很强,可以模拟几乎所以现实中存在的材质,在游戏中能很有效的提高材质的区分程度。
目前主流的PBR模型有两种,一种是PBR-SG(specular-glossiness)模型,一种是PBR-MR(metallic-roughness)模型,这两种工作流如下所示
PBR-SG模型因为参数过于灵活,经常会调节出不理想的效果,所以目前大多采用PBR-MR工作流。
1.2公式
PBR的核心公式如下,其由两部分组成:漫反射部分和高光部分,整个式子就是对各个方向的光照产生的这两部分效果做积分。
BRDF方程
其含义如下
其中ks=F,KD = (1-KS)*(1-metallic)。
1.2.1 直接光
直接光的漫反射部分就是兰伯特模型,只不过多除以了pi,这是为了保证能量守恒
直接光的镜面反射部分的核心就是DGF这三个项的计算。
1.D项是Normal distribution function,他描述了微表面的法线n和half vector之间的趋同性,公式如下
随着roughness的改变,其效果如下
2.G项Geometry function,如下所示,其用来表示微表面对于反射光线的遮挡比例。表面越粗糙,微表面对镜面反射的遮挡比例也就会越高。
其公式如下
其中k的计算再直接光照和间接光找中不同
随着粗糙度的变化,效果会有如下的变化
3.F项即Fresnel function,用来模拟现实中的菲涅尔现象。
其公式如下所示
Fresnel-Schlick公式,是对Fresnel的近似公式
它的效果如下所示
其中F0表示从0角度观看物体表面时的反射率
1.2.2间接光
1.间接光的漫反射
间接光的漫反射就是对光照探针进行采样。
2.间接光的高光反射
间接光的高光反射基于IBL,较为复杂。
这两个直接使用雪峰的代码,以后再深究。。。
二、代码
MyURP.shader
Shader "Custom/10_MyURP"
{
Properties
{
_BaseColor ("BaseColor", Color) = (1,1,1,1) //颜色
_BaseMap ("Albedo", 2D) = "white" {} //基础颜色(反照率)
_MetallicMap("MetallicMap", 2D) = "white" {} //金属图,r通道存储金属度,a通道存储光滑度
_MetallicStrength ("MetallicStrength", Range(0,1)) = 0 //金属强度Metallic strength
_Smoothness("Smoothness",Range(0,1)) = 0.5 //光滑度
_NormalMap("NormalMap", 2D) = "white"{} //法线贴图
_OcclusionMap("OcclusionMap",2D) = "white"{} //环境光遮蔽贴图, g通道
}
SubShader
{
Tags { "RenderType"="Opaque" }
HLSLINCLUDE
#include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
sampler2D _BaseMap, _MetallicMap, _NormalMap, _OcclusionMap;
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
float4 _BaseMap_ST, _MetallicMap_ST, _NormalMap_ST, _OcclusionMap_ST;
float _Smoothness, _MetallicStrength;
CBUFFER_END
struct Attributes
{
float3 positionOS: POSITION;
half3 normalOS: NORMAL;
half4 tangentOS: TANGENT;
float2 texcoord: TEXCOORD0;
};
struct Varyings
{
float2 uv: TEXCOORD0;
float3 positionWS: TEXCOORD1;
half3 normalWS: TEXCOORD2;
half3 tangentWS: TEXCOORD3;
half3 bitangentWS: TEXCOORD4;
float4 positionCS: SV_POSITION;
};
ENDHLSL
Pass
{
Tags
{
"LightMode"="UniversalForward"
}
HLSLPROGRAM
#include "MyURPFunction.hlsl"
#pragma vertex vert
#pragma fragment frag
Varyings vert(Attributes input)
{
Varyings output;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS);
VertexNormalInputs vertexNormalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS);
output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);
output.positionCS = vertexInput.positionCS;
output.positionWS = vertexInput.positionWS;
output.normalWS = vertexNormalInputs.normalWS;
output.tangentWS = vertexNormalInputs.tangentWS;
output.bitangentWS = vertexNormalInputs.bitangentWS;
return output;
}
half4 frag(Varyings input): SV_TARGET
{
half4 output = (1,1,1,1);
//properties
half3 albedo = tex2D(_BaseMap,input.uv).rgb * _BaseColor.rgb;
half2 metallicGloss = tex2D(_MetallicMap,input.uv).ra;
half metallic = metallicGloss.x * _MetallicStrength;
//half roughness = 1 - metallicGloss.y * _Smoothness;
half smoothness = metallicGloss.y;
half roughness = 1-smoothness;
roughness = roughness*roughness;
half occlusion = tex2D(_OcclusionMap, input.uv).g;
//get normal map in WS
float4 normalTex = tex2D(_NormalMap, input.uv);
float3 normalTS = UnpackNormalScale(normalTex,1); //tangent space normal
normalTS.z = sqrt(1-dot(normalTS.xy, normalTS.xy));
float3x3 T2W = {input.tangentWS, input.bitangentWS, normalize(input.normalWS)};
T2W = transpose(T2W);
float3 normalWS = NormalizeNormalPerPixel(mul(T2W, normalTS));
//light
Light mainLight = GetMainLight();
//get input
//float3 normalWS = normalize(input.normalWS);
float3 positionWS = input.positionWS;
float3 viewDirWS = SafeNormalize(GetCameraPositionWS()-positionWS);
float3 halfDir = normalize(viewDirWS+mainLight.direction);
float NdotH = max(saturate(dot(normalWS, halfDir)),0.000001);
float NdotL = max(saturate(dot(normalWS, mainLight.direction)),0.000001);
float NdotV = max(saturate(dot(normalWS, viewDirWS)),0.000001);
float HdotL = max(saturate(dot(halfDir, mainLight.direction)),0.000001);
float3 F0 = lerp(0.04, albedo, metallic);
///////////////////////////
// direct light //
//////////////////////////
//specular section
float D = D_Function(NdotH, roughness);
//return D;
float G = G_Function(NdotL, NdotV, roughness);
//return G;
float3 F = F_Function(HdotL, F0);
//return float4(F,1);
float3 BRDFSpeSection = (D*G*F)/(4*NdotL*NdotV);
float3 DirectSpeColor = BRDFSpeSection*mainLight.color*NdotL*PI;
//return float4(DirectSpeColor, 1);
//diffuse section
float3 KS = F;
float3 KD = (1-KS)*(1-metallic);
float3 directDiffColor = KD*albedo*mainLight.color*NdotL;
float3 directColor = DirectSpeColor+directDiffColor;
///////////////////////////
// indirect light //
//////////////////////////
//indirect diffuse
float3 SHcolor = SH_IndirectionDiff(normalWS)*occlusion;
float3 IndirKS=IndirF_Function(NdotV,F0,roughness);
float3 IndirKD = (1-IndirKS)*(1-metallic);
float3 IndirDiffColor=SHcolor*IndirKD*albedo;
//return float4(IndirDiffColor,1);
//indirect specular
float3 IndirSpeCubeColor = IndirSpeCube(normalWS,viewDirWS,roughness,occlusion);
//return float4(IndirSpeCubeColor,1);
float3 IndirSpeCubeFactor = IndirSpeFactor(roughness,smoothness,BRDFSpeSection,F0,NdotV);
float3 IndirSpeColor = IndirSpeCubeColor*IndirSpeCubeFactor;
//return float4(IndirSpeColor,1);
float3 IndirColor = IndirSpeColor+IndirDiffColor;
//return float4(IndirColor,1);
float3 color = IndirColor+directColor;
return float4(color, 1);
}
ENDHLSL
}
}
}
MyURPFunction.hlsl
#pragma once
float D_Function(float NdotH, float roughness)
{
float a = roughness;
float a2 = a * a;
float NdotH2=NdotH*NdotH;
float nominator = a2;
float denominator = NdotH2*(a2-1)+1;
denominator = denominator*denominator*PI;
return nominator/denominator;
}
float G_Function(float NdotL, float NdotV, float roughness)
{
//float k=roughness*roughness/2; //for indirect light(IBL)
float k = pow(1 + roughness, 2)/8; //for direct light
//float denominator = (NdotL*(1-k)+k)*(NdotV*(1-k)+k);
float denominator = (NdotL/lerp(NdotL,1,k))*(NdotV/lerp(NdotV,1,k));
return denominator;
}
real3 F_Function(float HdotL, float3 F0)
{
float Fre = exp2((-5.55473*HdotL-6.98316)*HdotL);
return lerp(Fre,1,F0);
}
real3 IndirF_Function(float NdotV,float3 F0,float roughness)
{
float Fre=exp2((-5.55473*NdotV-6.98316)*NdotV);
return F0+Fre*saturate(1-roughness-F0);
}
//间接光漫反射 球谐函数 光照探针
real3 SH_IndirectionDiff(float3 normalWS)
{
real4 SHCoefficients[7];
SHCoefficients[0]=unity_SHAr;
SHCoefficients[1]=unity_SHAg;
SHCoefficients[2]=unity_SHAb;
SHCoefficients[3]=unity_SHBr;
SHCoefficients[4]=unity_SHBg;
SHCoefficients[5]=unity_SHBb;
SHCoefficients[6]=unity_SHC;
float3 Color=SampleSH9(SHCoefficients,normalWS);
return max(0,Color);
}
//间接光高光 反射探针
real3 IndirSpeCube(float3 normalWS,float3 viewWS,float roughness,float AO)
{
float3 reflectDirWS=reflect(-viewWS,normalWS);
roughness=roughness*(1.7-0.7*roughness);//Unity内部不是线性 调整下拟合曲线求近似
float MidLevel=roughness*6;//把粗糙度remap到0-6 7个阶级 然后进行lod采样
float4 speColor=SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0,reflectDirWS,MidLevel);//根据不同的等级进行采样
#if !defined(UNITY_USE_NATIVE_HDR)
return DecodeHDREnvironment(speColor,unity_SpecCube0_HDR)*AO;//用DecodeHDREnvironment将颜色从HDR编码下解码。可以看到采样出的rgbm是一个4通道的值,最后一个m存的是一个参数,解码时将前三个通道表示的颜色乘上xM^y,x和y都是由环境贴图定义的系数,存储在unity_SpecCube0_HDR这个结构中。
#else
return speColor.xyz*AO;
#endif
}
//间接高光 曲线拟合 放弃LUT采样而使用曲线拟合
real3 IndirSpeFactor(float roughness,float smoothness,float3 BRDFspe,float3 F0,float NdotV)
{
#ifdef UNITY_COLORSPACE_GAMMA
float SurReduction=1-0.28*roughness,roughness;
#else
float SurReduction=1/(roughness*roughness+1);
#endif
#if defined(SHADER_API_GLES)//Lighting.hlsl 261行
float Reflectivity=BRDFspe.x;
#else
float Reflectivity=max(max(BRDFspe.x,BRDFspe.y),BRDFspe.z);
#endif
half GrazingTSection=saturate(Reflectivity+smoothness);
float Fre=Pow4(1-NdotV);//lighting.hlsl第501行
//float Fre=exp2((-5.55473*NdotV-6.98316)*NdotV);//lighting.hlsl第501行 它是4次方 我是5次方
return lerp(F0,GrazingTSection,Fre)*SurReduction;
}三、结果
左边是自己实现的PBR效果,右边是unity urp的lit shader的效果,可以发现自己实现的PBR效果材质明显比unity系统中自带的要”光滑“,具体的原因寻找了很久也没找到。所以以后如果基于PBR的效果实现,最好使用unity urp封装好的BRDF函数,这样子效果会更好一些。
另外,如果把lit.shader的smoothness设置为1,调整下角度可以发现效果很相近,所以猜测是要把smoothness参数与metallic map的alpha通道值结合起来,不过自己并没找到理想的结合方法。
五、主要参考
宋开心:如何在Unity中造一个PBR Shader轮子
URP管线的自学HLSL之路 第三十七篇 造一个PBR的轮子
https://learnopengl.com/PBR/Theory
The PBR Guide - Part 1 on Substance 3D Tutorials |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|