基于物理的渲染

1.Microfacet微面元


在所有的微面元中,仅仅只有红色的面元即h的方向才能被v看到,h被称为半矢量\(\mathrm{h}=\frac{\mathrm{l}+\mathrm{v}}{\left | \mathrm{l}+\mathrm{v} \right |}\),D和G都是影响h这个参数

\(f (\mathrm{l},\mathrm{v})=f_d(\mathrm{l},\mathrm{v})+\frac{F(\mathrm{v},\mathrm{h})D(\mathrm{h})G(\mathrm{l},\mathrm{v},\mathrm{h})}{4(\mathrm{n}\cdot \mathrm{l})(\mathrm{n}\cdot \mathrm{v})} \)

2.菲涅尔F

菲涅尔表示入射和折射的比例,这个数值和材质本身有关系也和光线入射角有关系

\( L_R=R_F(\theta_i)L_l \)

菲涅尔的计算公式[Schick,1994]

\( R_F(\theta_i)\approx R_F(0^{\circ})+(1-R_F(0^{\circ}))(1-\cos\theta_i)^5 \)

\(R_F(0^{\circ})\)表示入射光垂直于表面时菲涅尔的反射率的值,下图展示了菲涅尔在金属和非金属下不同角度的数值

3.法线分布项D

D就是法线的分布,简单的说就是h偏离法线n的程度,主要是由粗糙度决定,直接只用GGX的分布

NDF(Normal Distribution Function) GGX

\( \alpha = Roughness^2 \)

\( D(h)=\frac{\alpha ^2}{\pi ((\mathrm{n} \cdot \mathrm{h})^2(\alpha ^2-1)+1)^2} \)

4.遮挡项G

G称为双向阴影遮挡函数,也就是v会被其他的微面元遮挡,G主要受粗糙度影响,是用Smith GGX分布

Geometry Factor Smith GGX

\( \alpha = Roughness^2 \)

\( G_{GGX}(\mathrm{k})=\frac{2(\mathrm{n} \cdot \mathrm{k})}{(\mathrm{n} \cdot \mathrm{k}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{k})^2 }} \)

\( G(\mathrm{l},\mathrm{v},\mathrm{h})=G_1(\mathrm{l})G_1(\mathrm{v}) \)

5.漫反射Diffuse

Lambert模型

\( f_d(\mathrm{l},\mathrm{v}) = \frac{c_\mathrm{diff}}{\pi} \)

6.UE4材质模型

6.1 Basecolor

Basecolor就是材质的基础颜色,也就是材质的漫反射颜色,即漫反射公式中的\(c_{diff}\),漫反射颜色的本质是,光线在照射材质后,光线进入材质内部经过复杂散射后出来的光,这个散射后的光丧失了方向。与之对应的是高光,高光直接在材质上反射,映射的直接是光的颜色

6.2 Roughness

粗糙度是影响D和G的重要参数

6.3 Metallic

金属是个很特殊的参数,影响的是漫反射diff和菲涅尔项F,当材质为纯金属的时候原来漫反射为0(可理解为纯金属的时候进入材质的的光和金属发生光电反应,全部以热能的形式消耗掉),而此时\(F_0\)就是diff。

非金属的情况,当完全是非金属的时候,漫反射就是diff,此时\(F_0\)就是下面的Specular

6.4 Specular

由金属的参数性质可知,Specular只有在非金属的情况才生效,根据非金属的菲涅尔的图标可以看出\(F_0\)的范围大致在0.03到0.08之间

根据6.3和6.4可得出

\( diffuseColor = baseColor*(1-metallic) \)

\( specularColor = mix(0.08*specular,baseColor,metallic) \)

7.精确光源

\( f=f_d+f_r \)

\( =\frac{c_{diff}}{\pi } + \frac{FDG}{4 (n\cdot l) (n \cdot v)} \)

\( =\frac{c_{diff}}{\pi } + (F_0+(1-F_0)(1-v\cdot h)^5)(\frac{\alpha ^2}{\pi ((\mathrm{n} \cdot \mathrm{h})^2(\alpha ^2-1)+1)^2})((\frac{2(\mathrm{n} \cdot \mathrm{l})}{(\mathrm{n} \cdot \mathrm{l}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{l})^2 }})(\frac{2(\mathrm{n} \cdot \mathrm{v})}{(\mathrm{n} \cdot \mathrm{v}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{v})^2 }}))\frac{1}{4 (n\cdot l) (n \cdot v)} \)

\( =\frac{c_{diff}}{\pi } + (F_0+(1-F_0)(1-v\cdot h)^5)(\frac{\alpha ^2}{\pi ((\mathrm{n} \cdot \mathrm{h})^2(\alpha ^2-1)+1)^2})((\frac{1}{(\mathrm{n} \cdot \mathrm{l}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{l})^2 }})(\frac{1}{(\mathrm{n} \cdot \mathrm{v}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{v})^2 }})) \)

\( L_r=f*L_i*\cos\theta =f*\frac{E}{r^2}*(n \cdot l) \)

precision highp float;

varying vec3        vNormal;//法线
varying vec3        vPosition;//位置

uniform vec3        uLightPosition;//点灯光位置
uniform vec3        uLightColor;//点灯光颜色
uniform float       uLightRadius;//点灯光半径
uniform vec3        uCamPosition;//摄像机位置

uniform vec3        uBaseColor;//基础色
uniform float       uRoughness;//粗糙度
uniform float       uMetallic;//金属
uniform float       uSpecular;//高光

#define PI          3.14159265359
#define invPI       0.3183098861837697
#define invTWO_PI   0.15915494309
#define saturate(x) clamp(x, 0.0, 1.0)

vec3 Diffuse_Lambert( vec3 DiffuseColor )
{
    return DiffuseColor * (1.0 / PI);
}

float D_GGX( float Roughness, float NoH )
{
    float m = Roughness * Roughness;
    float m2 = m * m;
    float d = ( NoH * m2 - NoH ) * NoH + 1.0;    // 2 mad
    return m2 / ( PI*d*d );                     // 4 mul, 1 rcp
}

float Vis_Smith( float Roughness, float NoV, float NoL )
{
    float a = Roughness * Roughness ;
    float a2 = a*a;

    float Vis_SmithV = NoV + sqrt( NoV * (NoV - NoV * a2) + a2 );
    float Vis_SmithL = NoL + sqrt( NoL * (NoL - NoL * a2) + a2 );
    return 1.0 / ( Vis_SmithV * Vis_SmithL );
}

vec3 F_Schlick( vec3 SpecularColor, float VoH )
{
    float Fc = pow( 1.0 - VoH, 5.0 );
    return Fc + (1.0 - Fc) * SpecularColor;
}

float getAttenuation( vec3 lightPosition, vec3 vertexPosition, float lightRadius )
{
    float r                = lightRadius;
    vec3 L                 = lightPosition - vertexPosition;
    float dist             = length(L);
    float d                = max( dist - r, 0.0 );
    L                      /= dist;
    float denom            = d / r + 1.0;
    float attenuation      = 1.0 / (denom*denom);
    float cutoff           = 0.0052;
    attenuation            = (attenuation - cutoff) / (1.0 - cutoff);
    attenuation            = max(attenuation, 0.0);
    
    return attenuation;
}


void main(void){
    vec3 N               = normalize( vNormal );
    vec3 V               = normalize( uCamPosition - vPosition );
    vec3 L               = normalize( uLightPosition - vPosition );
    vec3 H               = normalize(V + L);

    float NoL            = saturate( dot( N, L ) );
    float NoV            = saturate( dot( N, V ) );
    float VoH            = saturate( dot( V, H ) );
    float NoH            = saturate( dot( N, H ) );

    vec3 diffuseColor    = uBaseColor - uBaseColor * uMetallic;
    vec3 specularColor   = mix( vec3( 0.08 * uSpecular ), uBaseColor, uMetallic );

    float attenuation    = getAttenuation( uLightPosition, vPosition, uLightRadius );
    float D              = D_GGX(uRoughness,NoH);
    float Vis            = Vis_Smith(uRoughness,NoV,NoL);
    vec3  F              = F_Schlick(specularColor,VoH);

    vec3 diffusePoint   = Diffuse_Lambert(diffuseColor);
    vec3 specularPoint  = D * Vis * F;
    vec3 colorPoint     = uLightColor * ( diffusePoint + specularPoint ) * NoL * attenuation;

    vec4 infoUv = vec4(colorPoint * 1.0 ,1.0);
    gl_FragColor = infoUv;

}

8.环境光源

8.0 蒙特卡洛方法

对于一个连续函数f,它的积分公式

\( F=\int_{a}^{b}f(x)dx \)

对应f的蒙特卡洛积分公式

\( F^N=\frac{1}{N}\sum_{i=1}^{N}\frac{f(X_i)}{pdf(X_i)} \)

8.1 基本公式

\( \int _\Omega L_i(l)f(l,v)\cos\theta _i \mathrm{d}l \approx \frac{1}{N}\sum_{k=1}^{N}\frac{L_i(l_k)f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)} \)

\( pdf=\frac{D(n \cdot h)}{4(v \cdot h)} \)

float3 ImportanceSampleGGX( float2 Xi, float Roughness , float3 N )
{
    float a = Roughness * Roughness;
    float Phi = 2 * PI * Xi.x;
    float CosTheta = sqrt( (1 - Xi.y) / ( 1 + (a*a - 1) * Xi.y ) );
    float SinTheta = sqrt( 1 - CosTheta * CosTheta );
    float3 H;
    H.x = SinTheta * cos( Phi );
    H.y = SinTheta * sin( Phi );
    H.z = CosTheta;
    float3 UpVector = abs(N.z) < 0.999 ? float3(0,0,1) : float3(1,0,0);
    float3 TangentX = normalize( cross( UpVector , N ) );
    float3 TangentY = cross( N, TangentX );
    // Tangent to world space
    return TangentX * H.x + TangentY * H.y + N * H.z;
}

float3 SpecularIBL( float3 SpecularColor , float Roughness , float3 N, float3 V )
{
    float3 SpecularLighting = 0;
    const uint NumSamples = 1024;
    for( uint i = 0; i < NumSamples; i++ )
   {
        float2 Xi = Hammersley( i, NumSamples );
        float3 H = ImportanceSampleGGX( Xi, Roughness , N );
        float3 L = 2 * dot( V, H ) * H - V;
        float NoV = saturate( dot( N, V ) );
        float NoL = saturate( dot( N, L ) );
        float NoH = saturate( dot( N, H ) );
        float VoH = saturate( dot( V, H ) );
        if( NoL > 0 )
        {
            float3 SampleColor = EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb;
            float G = G_Smith( Roughness , NoV, NoL );
            float Fc = pow( 1 - VoH, 5 );
            float3 F = (1 - Fc) * SpecularColor + Fc;
            // Incident light = SampleColor * NoL
            // Microfacet specular = D*G*F / (4*NoL*NoV)
            // pdf = D * NoH / (4 * VoH)
            SpecularLighting += SampleColor * F * G * VoH / (NoH * NoV);
        }
    }
    return SpecularLighting / NumSamples;
}

8.2 展开优化

\( \frac{1}{N}\sum_{k=1}^{N}\frac{L_i(l_k)f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)} \approx \left ( \frac{1}{N}\sum_{k=1}^{N}L_i(l_k) \right )\left ( \frac{1}{N}\sum_{k=1}^{N} \frac{f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)}\right ) \)

8.3 前部优化 Pre-Filtered Environment Map

对于前半部分,做如下处理,假定 n=v=r

\( \frac{1}{N}\sum_{k=1}^{N}L_i(l_k) \approx \mathrm{Cubemap. Sample (r, mip) } \)

float3 PrefilterEnvMap( float Roughness , float3 R )
{
    float3 N = R;
    float3 V = R;
    float3 PrefilteredColor = 0;
    const uint NumSamples = 1024;
    for( uint i = 0; i < NumSamples; i++ )
    {
        float2 Xi = Hammersley( i, NumSamples );
        float3 H = ImportanceSampleGGX( Xi, Roughness , N );
        float3 L = 2 * dot( V, H ) * H - V;
        float NoL = saturate( dot( N, L ) );
        if( NoL > 0 )
        {
            PrefilteredColor += EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb * NoL;
            TotalWeight += NoL;
        }
    }
    return PrefilteredColor / TotalWeight;
}

8.3 后部优化

8.3.1 基本做法

\( \frac{1}{N}\sum_{k=1}^{N} \frac{f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)} = \frac{1}{N}\sum_{k=1}^{N} S \)

分解S

\( S = \frac{DFV \cos\theta }{pdf}\)

对F进行分解处理

\( F = F_0 +(1-F_0)(1-\cos\theta)^5 = F_0(1-(1-\cos\theta)^5) + (1-\cos\theta)^5 \)

\( = F_0(1-(1-v\cdot h)^5) + (1-v\cdot h)^5 = F_0(1-F_c) + F_c \)

其中\( F_c = (1-v\cdot h)^5 \)

处理pdf

\( pdf = \frac{D(n \cdot h)}{4(v \cdot h)}\)

对S最终分解处理

\( S= \frac{4DFV(n \cdot l)(v \cdot h)}{D(n \cdot h)} = F \frac{4V(n \cdot l)(v \cdot h)}{(n \cdot h)}\)

\( = F_0(1-F_c)\frac{4V(n \cdot l)(v \cdot h)}{(n \cdot h)} + F_c\frac{4V(n \cdot l)(v \cdot h)}{(n \cdot h)} \)

此时给定\(NOV\)和\(Roughness\),\(Roughness\)在重要性采样中确定\(h\),那么\(n、v、h\)都有即可推算出\(l\),那么上述式子的结果就只有和\(NOV、Roughness、F_0\)相关

通过数学技巧可以写成\(F_0*Scale+Offset,Scale和Offset\)这种线性的和Roughness、NoV相关,可以通过查表或者LUT的方式快速计算

float Vis_SmithJointApprox(float Roughness, float NoV, float NoL) {
	float a = Roughness * Roughness;
	float Vis_SmithV = NoL * (NoV * (1.0 - a) + a);
	float Vis_SmithL = NoV * (NoL * (1.0 - a) + a);
	return 0.5 / (Vis_SmithV + Vis_SmithL);
}
Vector2 IntegrateBRDF(Vector2 Random, float Roughness, float NoV){
	Vector3 V;
	V.x = sqrt(1.0 - NoV * NoV);// sin;
	V.y = 0;
	V.z = NoV;// # cos

	float A = 0;
	float B = 0;

	int NumSamples = 128;
	for (int i = 0;i < NumSamples;i++){
		Vector2 E = Hammersley(i, NumSamples, Random);
		Vector3 H = ImportanceSampleGGX(E, Roughness);
		Vector3 L = H * (2 * V.dot(H)) - V;

		float NoL = saturate(L.z);
		float NoH = saturate(H.z);
		float VoH = saturate(V.dot(H));

		if (NoL > 0){ 
			float Vis = Vis_SmithJointApprox(Roughness, NoV, NoL);
			//#Incident light = NoL
			//#pdf = D * NoH / (4 * VoH)
			//#NoL * Vis / pdf
			float NoL_Vis_PDF = NoL * Vis * (4 * VoH / NoH);
			float Fc = pow(1 - VoH, 5);
			A += (1 - Fc) * NoL_Vis_PDF;
			B += Fc * NoL_Vis_PDF;
		}
	}
	return Vector2(A / NumSamples, B / NumSamples);
}

最终生成的图片,运行时直接采样图片

8.3.2 再次优化

LUT的曲线本身是平滑的,所以可以用数学的方式(泰勒公式)来拟合曲线,下面是UE4的拟合代码

Vector2 EnvBRDFApprox(float Roughness,float NoV){
	//# [ Lazarov 2013, "Getting More Physical in Call of Duty: Black Ops II" ]
	//# Adaptation to fit our G term.
	Vector4 c0 = Vector4(-1, -0.0275, -0.572, 0.022);
	Vector4 c1 = Vector4(1, 0.0425, 1.04, -0.04);
	Vector4 r = c0 * (Roughness) + c1;
	float a004 = min(r.x * r.x, pow(2, -9.28 * NoV)) * r.x + r.y;
	Vector2 AB = Vector2(-1.04, 1.04)*(a004) + Vector2(r.z, r.w);
	return  AB;
}

用上述的方式,拟合出来的图片可以和上图来对比

8.4 环境光的Diffuse

直接对原始的环境光图片按照consine分布采样,然后直接累加颜色值,然后重新生成Diffuse的环境光图片,使用的时候,只需要用点的normal去采样这个环境光图片,就是diffuse的光照结果,代码如下

Vector4 DiffuseIBL(Vector2 Random, Vector3 N, BitmapData &img, bool hdr){
	Vector4 DiffuseLighting;
	unsigned NumSamples = 4096 * 2;
	//for i in range(NumSamples) :
	for (unsigned int i = 0; i < NumSamples; i++){
		Vector2 E = Hammersley(i, NumSamples, Random);
		Vector3 C = CosineSampleHemisphere(E);
		Vector3 L = TangentToWorld(C, N);

		float NoL = saturate(N.dot(L));;
		//#print("NoL", NoL);
		if (NoL > 0){
			Vector2 uv2 = pos2uv(L);
			Vector4 result = img.getPixel32Int(uv2.x * img.width, uv2.y * img.height);
			//#lambert = DiffuseColor * NoL / PI
			//#pdf = NoL / PI
			float e = 1.0;
			if (hdr)
			{
				e = result.w - 128.0;
				e = pow(2, e);
			}
			
			DiffuseLighting.x += result.x * e;
			DiffuseLighting.y += result.y * e;
			DiffuseLighting.z += result.z * e;
			DiffuseLighting.w += 255;
		}
	}
	float Weight = 1.0 / NumSamples;
	DiffuseLighting = DiffuseLighting * (Weight);
	return DiffuseLighting;
}

至此就是UE4关于物理渲染的整个推导流程

Leave a Comment