Vulkan_PCSS软阴影

本文简述实现,具体步骤原理可查看闫大神高质量实时渲染。
在这里插入图片描述

一、PCSS简介

1.1 PCF

百分比渐近过滤(PCF)是一个简单,常见的进行阴影边缘反走的技术。它通过在片段周围进行采样,然后计算样本比片段的更接近光源的比例,使用这个比例对散射光和镜面光成分进行缩放,然后对片段进行着色。使用这一技术后,阴影边缘看上去进行了模糊一样。

但对于较大物体的内部的采样是一个较大的浪费,我们并无法只针对物体边界进行采样,所以在性能是具有很大的瓶颈。

1.2 PCSS

pcss是距离相关的, 它通过搜索阴影贴图上的附近区域来尝试找到所有可能的遮挡物。这些遮挡物与该位置的平均距离用于确定样本区域宽度。随着平均遮挡物越来越远离接收物并且更靠近光线,样本的表面区域的宽度增大。
在这里插入图片描述

如果找不到遮挡物,则该位置完全点亮,无需进一步处理。类似地,如果位置完全被遮挡,则处理可以结束。否则,继续对感兴趣的区域进行采样并计算光的近似贡献。为了节省处理成本,样本区域的宽度可用于改变采样的数量。 可以实现其他技术,例如,使用较低的采样率来获得不太重要的远距离软阴影。

具体实现步骤:

  1. 遮挡物平均深度计算;
  2. 半影区域计算;
  3. 适用性PCF采样;

其中需要注意的是:

  • 半影区域计算如下图三角形比例关系:

在这里插入图片描述

  • 遮挡物平均深度计算时的区域大小计算如下图(也可简化使用固定区域大小):

在这里插入图片描述

二、代码实现

本部分仅讲述Shader实现,C+代码具体参照之前阴影相关文章。

2.1 shadow map pass

简单取shadow map:

顶点着色器:

#version 330 core

in vec3 position;

uniform mat4 modelViewProjection;

void main()
{
	gl_Position = modelViewProjection * vec4(position, 1);
}

片元着色器:

#version 330 core

out float outDepth;

void main()
{
    outDepth = gl_FragCoord.z;
}

2.2 pcss pass

使用shadow map求PCSS

顶点着色器:

#version 330 core

in vec3 position; 
in vec3 normal;
in vec2 texcoords;

out vec2 vTexcoords; 
out vec3 vNormal;
out vec3 vViewDir;
out vec3 vWorldPosition;
out vec3 vCameraPosition;

uniform mat4 model; 
uniform mat4 view; 
uniform mat4 projection; 
uniform vec3 eyePosition;

void main()
{
    vTexcoords = texcoords;
	vNormal = (model * vec4(normal, 0)).xyz;
	vec4 worldPosition = model * vec4(position, 1.0f);
	vWorldPosition = worldPosition.xyz;
	vec4 cameraPosition = view * model * vec4(position, 1.0f);
	vCameraPosition = cameraPosition.xyz;
	vViewDir = normalize(eyePosition - vWorldPosition);
    gl_Position = projection * cameraPosition;
}

片元着色器中实现步骤基本与上述PCSS实现步骤一致,详见注释。

片元着色器:

#version 330 core

#define NEAR 0.1

#define HARD_SHADOWS 0
#define SOFT_SHADOWS 1

in vec2 vTexcoords;
in vec3 vNormal;
in vec3 vViewDir;
in vec3 vWorldPosition;
in vec3 vCameraPosition;

struct LightSource
{
	vec3 diffuseColor;
	float diffusePower;
	vec3 specularColor;
	float specularPower;
	vec3 position;
	int type;
	float size;

};

layout (std140) uniform LightSources
{
    LightSource lightSources[2];
};

uniform sampler2D shadowMap0;
uniform samplerCube shadowCubeMap0;
uniform mat4 shadowMapViewProjection0;

uniform mat4 invView;
uniform mat4 lightProjection;
uniform vec3 eyePosition;
uniform vec3 ambientColor = vec3(0.8,0.8,0.8);
uniform vec3 specularColor = vec3(1,1,1);
uniform float specularity = 0;
uniform float frustumSize = 1;
uniform sampler2D tex0;
uniform sampler1D distribution0;
uniform sampler1D distribution1;
uniform int numBlockerSearchSamples = 16;
uniform int numPCFSamples = 16;
uniform int displayMode = 0;
uniform int selectedLightSource = -1;

out vec3 outColor;

//随机采样数据
vec2 RandomDirection(sampler1D distribution, float u)
{
   return texture(distribution, u).xy * 2 - vec2(1);
}

//常规光照处理
vec3 LightContribution(vec3 diffuseColor)
{
	float NdotL=max(0, dot(vNormal, normalize(vViewDir - lightSources[0].position)));
	return diffuseColor * lightSources[0].diffuseColor * lightSources[0].diffusePower * NdotL  + 
			pow(NdotL, specularity) * specularColor * lightSources[0].specularColor * lightSources[0].specularPower;
}

//坐标点转相机空间
vec3 ShadowCoords(mat4 shadowMapViewProjection)
{
	vec4 projectedCoords = shadowMapViewProjection * vec4(vWorldPosition, 1);
	vec3 shadowCoords = projectedCoords.xyz / projectedCoords.w;
	shadowCoords = shadowCoords * 0.5 + 0.5;
	return shadowCoords;
}

//遮挡范围计算
float SearchWidth(float uvLightSize, float receiverDistance)
{
	return uvLightSize * (receiverDistance - NEAR) / eyePosition.z;
}

//遮挡物平均深度查询
float FindBlockerDistance_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
{
	int blockers = 0;
	float avgBlockerDistance = 0;
	float searchWidth = SearchWidth(uvLightSize, shadowCoords.z);
	for (int i = 0; i < numBlockerSearchSamples; i++)
	{
		float z = texture(shadowMap, shadowCoords.xy + RandomDirection(distribution0, i / float(numBlockerSearchSamples)) * searchWidth).r;
		if (z < (shadowCoords.z - 0.002f))
		{
			blockers++;
			avgBlockerDistance += z;
		}
	}
	if (blockers > 0)
		return avgBlockerDistance / blockers;
	else
		return -1;
}

//PCF
float PCF_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvRadius)
{
	float sum = 0;
	for (int i = 0; i < numPCFSamples; i++)
	{
		float z = texture(shadowMap, shadowCoords.xy + RandomDirection(distribution0, i / float(numPCFSamples)) * uvRadius).r;
		sum += (z < (shadowCoords.z - 0.002f)) ? 1 : 0;
	}
	return sum / numPCFSamples;
}

//ShadowMap
float ShadowMapping_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
{
	float z = texture(shadowMap, shadowCoords.xy).x;
	return (z < (shadowCoords.z - 0.002f)) ? 0 : 1;
}

//软阴影计算
float PCSS_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
{
	// 遮挡物平均深度计算
	float blockerDistance = FindBlockerDistance_DirectionalLight(shadowCoords, shadowMap, uvLightSize);
	if (blockerDistance == -1)
		return 1;		

	// 半影区域计算
	float penumbraWidth = ((shadowCoords.z - blockerDistance) / blockerDistance) * uvLightSize;

	// PCF
	float uvRadius = penumbraWidth * NEAR / shadowCoords.z;
	return 1 - PCF_DirectionalLight(shadowCoords, shadowMap, uvRadius);
}

void main()
{
	vec3 diffuseColor = texture(tex0, vTexcoords).rgb;
	switch (displayMode)
	{
		case HARD_SHADOWS:	//硬阴影
			outColor = LightContribution(diffuseColor) * ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
			break;
		case SOFT_SHADOWS:  //软阴影
			outColor = LightContribution(diffuseColor) * PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
			break;
		default:
			outColor = vec3(0.3,0.3,0.3);
	}
	outColor += ambientColor*diffuseColor;
}

运行可见如下效果(32采样范围,面光源默认大小0.5):
在这里插入图片描述

对比原有shadow map效果:

在这里插入图片描述

再放一张低PCF效果(4采样范围,面光源默认大小0.5):

在这里插入图片描述

大家可以从帧率上明显看出PCFF的耗时,后续可有时间的话继续使用VSSM方法来优化PCSS第一步blocker search 和第三部pcf采样的耗时。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值