• PBR技术简介(四):直接光照的代码实现


    之前介绍了有关PBR技术的一些理论知识,今天来讲一下利用代码如何实现相应的光照算法。
    我们提到,我们最终要求解的其实就是这么一个积分:

    积分中kd的部分代表光照所产生的漫反射,ks的部分代表光照所产生的高光反射。如果充分考虑间接光照的效果(也就是从光源发射出光线后,不断碰撞反射,最终进入人眼),那这个积分事实上是极难求解的,但是我们可以先暂时不考虑间接光照,只考虑直接光照的部分。那么只需要在shader中,将所有的光源累加到该积分里就可以了,这个相对来说还是比较好做的。
    我们首先把Cook-Torrance BRDF相关的代码实现一下(用HLSL实现),基本上就是对着公式写:

    // 法向分布函数 N
    float DistributionGGX(float3 N, float3 H, float roughness)
    {
    	float pi = 3.14159265;
    	float a = roughness * roughness;
    	float a2 = a * a;
    	float NdotH = max(dot(N, H), 0.0);
    	float NdotH2 = NdotH * NdotH;
    
    	float num = a2;
    	float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    	denom = pi * denom * denom;
    
    	return num / denom;
    }
    
    float GeometrySchlickGGX(float NdotV, float roughness)
    {
    	float r = (roughness + 1.0);
    	float k = (r*r) / 8.0;
    
    	float num = NdotV;
    	float denom = NdotV * (1.0 - k) + k;
    
    	return num / denom;
    }
    
    //几何函数G
    float GeometrySmith(float3 N, float3 V, float3 L, float roughness)
    {
    	float NdotV = max(dot(N, V), 0.0);
    	float NdotL = max(dot(N, L), 0.0);
    	float ggx2 = GeometrySchlickGGX(NdotV, roughness);
    	float ggx1 = GeometrySchlickGGX(NdotL, roughness);
    
    	return ggx1 * ggx2;
    }
    
    //菲涅尔公式F
    float3 fresnelSchlick(float cosTheta, float3 F0)
    {
    	return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
    }
    

    然后具体的光照计算代码如下(以点光源为例,方向光原理):

    void ComputePointLight(Material mat, PointLight light, float3 pos, float3 N, float3 V, float3 F0,
    				   out float3 lo)
    {
    	float3 L = light.Position - pos;
    	float distance = length(L);
    	
    	// Range test.
    	if( distance > light.Range )
    		return;
    		
    	// Normalize the light vector.
    	L /= distance;
    	float3 H = normalize(V + L);
    	
    	float attenuation = 1.0 / (distance * distance);
    	float3 radiance = light.Diffuse.rgb * attenuation;
    
    	// cook-torrance brdf
    	float NDF = DistributionGGX(N, H, mat.roughness);
    	float G = GeometrySmith(N, V, L, mat.roughness);
    	float3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
    
    	float3 kS = F;
    	float3 kD = 1.0 - kS;
    	kD *= (1.0 - mat.metallic);
    
    	float3 numerator = NDF * G * F;
    	float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
    	float3 specular = numerator / max(denominator, 0.001);
    
    	float pi = 3.14159265;
    	float NdotL = max(dot(N, L), 0.0);
    	lo = (kD * mat.albedo / pi + specular) * radiance * NdotL;
    }
    

    上述代码中,V就是着色位置到相机的方向,N是法向,F0是前面提到的物体和菲涅尔效应有关的属性,lo是该光源在物体上最终产生的辐射。
    材质的定义如下:

    struct Material
    {
    	float3 albedo;
    	float  roughness;
    	float  metallic;
    };
    

    其中albedo可以认为是物体本身的颜色,roughness就是粗糙度,metallic则是金属度。
    PixelShader的代码如下所示:

    float4 CustomPS(VertexOut pin,
    	uniform int gPointLightCount,
    	uniform int gDirLightCount,
    	uniform bool gUseShadowMap,
    	uniform bool gUseSSAO) : SV_Target
    {
    
    	float3 color = float3(0.0f, 0.0f, 0.0f);
    
    	pin.NormalW = normalize(pin.NormalW);
    
    	float3 V = normalize(gEyePosW - pin.PosW);
    	
    	V = normalize(V);
    
    	// 根据金属度计算物体的F0
            float3 F0 = float3(0.04, 0.04, 0.04);
    	F0 = lerp(F0, gMaterial.albedo, gMaterial.metallic);
    
    	float3 L0 = float3(0.0, 0.0, 0.0);
    
    	float shadow = 1.0;
    	if (gUseShadowMap)
    		shadow = CalcShadowFactor(samShadow, gShadowMap, pin.ShadowPosH);
    
    	float ambient_weight = 1.0;
    	if (gUseSSAO)
    	{
    		pin.SSAOPosH /= pin.SSAOPosH.w;
    		ambient_weight = gSSAOMap.Sample(samLinear, pin.SSAOPosH.xy, 0.0f).r;
    	}
    
    	float3 ambient = gMaterial.albedo * 0.03 * ambient_weight;
    	
    	//
    	// Lighting.
    	//
    
    	if (gPointLightCount + gDirLightCount > 0)
    	{
    		[unroll]
    		for (int i = 0; i < gDirLightCount; ++i)
    		{
    			float3 lo = float3(0.0, 0.0, 0.0);
    			ComputeDirectionalLight(gMaterial, gDirLights[i], pin.NormalW, V, F0, lo);
    			color += shadow * lo;
    		}
    
    		[unroll]
    		for (int i = 0; i < gPointLightCount; i++)
    		{
    			float3 lo = float3(0.0, 0.0, 0.0);
    			ComputePointLight(gMaterial, gPointLights[i], pin.PosW, pin.NormalW, V, F0, lo);
    			color += shadow * lo;
    		}
    	}
    
    	color += ambient;
    
    	if (enableHDR)
    	{
    		float exposure = max(0.0, HDRexposure);
    		color = 1.0 - exp(color * exposure);
    	}
    
    	if (gammaCorrection)
    	{
    		float gamma_ratio = 1.0 / 2.2;
    		color = pow(color, gamma_ratio);	
    	}
    	return float4(color, 1.0);
    }
    

    基本计算过程就如上所示。最后展示一下不同粗糙度和金属度下渲染结果。
    图中共有25个球,从左到右其粗糙度越来越大,从上到下其金属度越来越大。可以看到,随着粗糙度的增加,高光汇聚的亮度区域会越来越小;而随着金属度的增加,漫反射的部分也会越来越少。

  • 相关阅读:
    Java加密作业
    作业
    思考动手
    方法作业
    课堂2数字输出
    字符型转整形
    课堂验证作业
    Eclipse @override报错解决
    用注解配置动态代理
    动态代理模式
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/13513244.html
Copyright © 2020-2023  润新知