渲染中实现阴影的各种方式
在渲染中实现阴影的方式有多种,具体取决于所使用的渲染引擎和技术。以下是一些常见的实现阴影的方法:
- 平面投影 (Planar Shadow):Planar Shadow 平面投影(假阴影),低开销的阴影实现方式 - JeasonBoy - 博客园 (cnblogs.com)
- 开销最低的方式,思路就是根据光的方向将模型拉伸/压缩后画个黑影在地上,缺点是只能投在平面上,不能投射到其他物体上,斜面上。
-
阴影贴图 (Shadow Mapping):
- 阴影贴图是最常见的实时阴影技术之一。其基本思想是从光源的视角渲染场景,生成一张深度贴图,然后在实际渲染过程中使用这张深度贴图来判断像素是否在阴影中。
-
万向阴影贴图 (Omnidirectional Shadow Mapping):点阴影 - LearnOpenGL CN (learnopengl-cn.github.io)
- 上面的阴影贴图只能绘制一个方向的阴影,这是改进版。比如一个点光源在中心,那么它周围的物体都能向各个方向投影,性能消耗也比较高。
-
阴影体积 (Shadow Volumes):
- 阴影体积是一种精确的阴影生成方法。它通过将物体的几何体向光源方向投影,形成一个体积,然后在渲染过程中检测哪些像素在这个体积内,从而确定阴影。
-
屏幕空间阴影 (Screen Space Shadows):
- 屏幕空间阴影技术在屏幕空间中计算阴影,而不是在世界空间中。这种方法可以减少计算量,适合实时渲染。
-
软阴影 (Soft Shadows):
- 软阴影模拟的是现实中的阴影边缘柔和效果。可以通过多次采样 (PCF, Percentage Closer Filtering) 或者其他方法 (如 Variance Shadow Mapping) 来实现。
-
环境光遮蔽 (Ambient Occlusion):
- 虽然环境光遮蔽并不是传统意义上的阴影,但它可以增加场景的深度感。屏幕空间环境光遮蔽 (SSAO) 是一种常见的实时环境光遮蔽技术。
-
光线追踪 (Ray Tracing):
- 光线追踪是一种物理上更准确的渲染技术,它通过追踪光线的路径来生成逼真的阴影。虽然计算量较大,但现代GPU的硬件加速使其在实时渲染中逐渐变得可行。
阴影贴图
阴影贴图(Shadow Mapping)是一种常用的实时渲染技术,用于生成和计算场景中的阴影。其基本原理是通过从光源的角度生成深度信息,然后在实际渲染过程中使用这些深度信息来判断哪些像素在阴影中。下面详细讲解阴影贴图的实现步骤和技术细节。
1. 从光源角度渲染深度贴图
首先,从光源的角度渲染整个场景,并生成一张深度贴图(Depth Map)。这张贴图记录了从光源到每个物体表面的深度值。
步骤:
-
设置光源视图和投影矩阵:
- 根据光源的位置和方向,设置光源的视图矩阵和投影矩阵(通常是正交投影用于方向光,透视投影用于点光源和聚光灯)。
glm::mat4 lightView = glm::lookAt(lightPos, lightTarget, upVector); glm::mat4 lightProjection = glm::ortho(left, right, bottom, top, near, far); glm::mat4 lightSpaceMatrix = lightProjection * lightView;
-
渲染深度信息:
- 使用光源的视图和投影矩阵渲染场景,但只记录深度值,而不渲染颜色。
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); glClear(GL_DEPTH_BUFFER_BIT); // Render scene to depth map renderScene(lightSpaceMatrix); glBindFramebuffer(GL_FRAMEBUFFER, 0);
2. 渲染实际场景并应用阴影贴图
在从相机视角渲染实际场景时,需要将光源的深度信息与当前像素的深度信息进行比较,以确定该像素是否在阴影中。
步骤:
-
计算当前像素在光源视图中的位置:
- 将当前像素的世界坐标转换到光源的视图空间。
vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPos, 1.0);
-
深度比较:
- 使用深度贴图中的深度值与当前像素的深度值进行比较。如果当前像素的深度大于深度贴图中的值,则该像素在阴影中。
float shadow = currentDepth > closestDepth + bias ? 1.0 : 0.0;
-
应用阴影因子:
- 使用阴影因子调整光照强度,生成阴影效果。
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular));
3. 处理阴影伪影
阴影贴图方法中常见的伪影包括锯齿状阴影和彼得潘效应(Peter Panning)。可以通过以下技术来处理这些问题:
-
深度偏差(Depth Bias):
- 在进行深度比较时加入一个小的偏差,防止自阴影(Self-Shadowing)问题。
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); float shadow = currentDepth > closestDepth + bias ? 1.0 : 0.0;
-
百分比更接近过滤(Percentage Closer Filtering, PCF):
- 对深度贴图进行多次采样,并平均这些采样结果来生成更平滑的阴影边缘。
float shadow = 0.0; vec2 texelSize = 1.0 / textureSize(shadowMap, 0); for(int x = -1; x <= 1; ++x) { for(int y = -1; y <= 1; ++y) { float pcfDepth = texture(shadowMap, fragPosLightSpace.xy + vec2(x, y) * texelSize).r; shadow += currentDepth > pcfDepth ? 1.0 : 0.0; } } shadow /= 9.0;
4. 代码示例
以下是一个简化的OpenGL代码示例,展示了阴影贴图的实现步骤:
顶点着色器(Vertex Shader)
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; uniform mat4 model; uniform mat4 view; uniform mat4 projection; uniform mat4 lightSpaceMatrix; out vec4 FragPosLightSpace; out vec3 FragPos; out vec3 Normal; void main() { FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0); gl_Position = projection * view * vec4(FragPos, 1.0); }
片段着色器(Fragment Shader)
#version 330 core out vec4 FragColor; in vec4 FragPosLightSpace; in vec3 FragPos; in vec3 Normal; uniform sampler2D shadowMap; uniform vec3 lightPos; uniform vec3 viewPos; float ShadowCalculation(vec4 fragPosLightSpace) { // Perform perspective divide vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; // Transform to [0,1] range projCoords = projCoords * 0.5 + 0.5; // Get closest depth value from light's perspective (using [0,1] range fragment coords) float closestDepth = texture(shadowMap, projCoords.xy).r; // Get depth of current fragment from light's perspective float currentDepth = projCoords.z; // Check whether current frag pos is in shadow float bias = max(0.05 * (1.0 - dot(Normal, lightDir)), 0.005); float shadow = currentDepth > closestDepth + bias ? 1.0 : 0.0; return shadow; } void main() { vec3 color = vec3(1.0); // Assume object color is white vec3 lightColor = vec3(1.0); vec3 ambient = 0.15 * lightColor; // Diffuse shading vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diff * lightColor; // Specular shading vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); vec3 specular = spec * lightColor; // Calculate shadow float shadow = ShadowCalculation(FragPosLightSpace); vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; FragColor = vec4(lighting, 1.0); }
通过上述步骤和技术细节,可以实现基本的阴影贴图,并在实际应用中进一步优化和调整,以获得更好的视觉效果。
软阴影
软阴影(Soft Shadows)是指阴影的边缘是平滑的,而不是硬边的。这种阴影更接近于现实生活中的阴影,因为在现实世界中,光源通常不是点光源,而是具有一定面积的面光源或体积光源,导致阴影边缘出现渐变。
软阴影的生成
生成软阴影的方法有很多,包括:
- 面积光源采样(Area Light Sampling): 通过对光源的多个点进行采样,生成阴影的柔和边缘。
- 阴影贴图的模糊(Shadow Map Blurring): 对阴影贴图进行模糊处理,使得阴影边缘变得柔和。
- PCF(Percentage-Closer Filtering): 对多个邻近深度值进行平均处理,可以平滑阴影边缘。
阴影贴图与软阴影
阴影贴图(Shadow Mapping)是生成阴影的基础技术,通过记录从光源视角下场景中每个像素到光源的距离,来判断像素是否在阴影中。通过添加一些额外的处理,阴影贴图技术也可以用来生成软阴影。
使用阴影贴图生成软阴影的方法
以下是几种常见的方法,用于在阴影贴图的基础上生成软阴影:
使用阴影贴图生成软阴影的方法
以下是几种常见的方法,用于在阴影贴图的基础上生成软阴影:
-
PCF(Percentage-Closer Filtering):
- 在计算阴影时,对当前像素周围的多个深度值进行采样,然后对这些深度值进行平均处理,以生成平滑的阴影边缘。
float shadow = 0.0; vec2 texelSize = 1.0 / textureSize(shadowMap, 0); for(int x = -1; x <= 1; ++x) { for(int y = -1; y <= 1; ++y) { float pcfDepth = texture(shadowMap, fragPosLightSpace.xy + vec2(x, y) * texelSize).r; shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; } } shadow /= 9.0;
-
VSM(Variance Shadow Maps):
- 使用方差阴影贴图技术,通过存储深度和深度的平方,使用方差来估计深度的分布,从而实现软阴影。
float ChebyshevUpperBound(float Moments[2], float t) { // One-tailed inequality valid if t > Moments[0] float p = t - Moments[0]; float variance = Moments[1] - (Moments[0] * Moments[0]); variance = max(variance, 0.00002); float d = p * p / variance; return variance / (variance + d); }
-
面积光源采样(Area Light Sampling):
- 对光源进行多个采样,模拟面积光源的效果,从而生成软阴影。这个方法比较昂贵,因为需要进行多次采样。
float shadow = 0.0; int samples = 16; vec3 random = vec3(rand(), rand(), rand()); for(int i = 0; i < samples; ++i) { vec3 offset = gridSamplingDisk(i, samples, random); float closestDepth = texture(shadowMap, fragPosLightSpace.xy + offset.xy).r; shadow += currentDepth - bias > closestDepth ? 1.0 : 0.0; } shadow /= float(samples);
总结
软阴影是指具有平滑边缘的阴影,能够提供更逼真的渲染效果。阴影贴图是生成阴影的一种基础技术,通过一些扩展和处理,可以在阴影贴图的基础上生成软阴影。常见的方法包括PCF、VSM和面积光源采样等。通过这些技术,可以实现不同程度和质量的软阴影效果,以满足不同的渲染需求。