Shadow Map(阴影贴图)跟Soft Shadows(软阴影)

在渲染中实现阴影的方式有多种,具体取决于所使用的渲染引擎和技术。以下是一些常见的实现阴影的方法:

  1. 阴影贴图 (Shadow Mapping):

    • 阴影贴图是最常见的实时阴影技术之一。其基本思想是从光源的视角渲染场景,生成一张深度贴图,然后在实际渲染过程中使用这张深度贴图来判断像素是否在阴影中。
  2. 阴影体积 (Shadow Volumes):

    • 阴影体积是一种精确的阴影生成方法。它通过将物体的几何体向光源方向投影,形成一个体积,然后在渲染过程中检测哪些像素在这个体积内,从而确定阴影。
  3. 屏幕空间阴影 (Screen Space Shadows):

    • 屏幕空间阴影技术在屏幕空间中计算阴影,而不是在世界空间中。这种方法可以减少计算量,适合实时渲染。
  4. 软阴影 (Soft Shadows):

    • 软阴影模拟的是现实中的阴影边缘柔和效果。可以通过多次采样 (PCF, Percentage Closer Filtering) 或者其他方法 (如 Variance Shadow Mapping) 来实现。
  5. 环境光遮蔽 (Ambient Occlusion):

    • 虽然环境光遮蔽并不是传统意义上的阴影,但它可以增加场景的深度感。屏幕空间环境光遮蔽 (SSAO) 是一种常见的实时环境光遮蔽技术。
  6. 光线追踪 (Ray Tracing):

    • 光线追踪是一种物理上更准确的渲染技术,它通过追踪光线的路径来生成逼真的阴影。虽然计算量较大,但现代GPU的硬件加速使其在实时渲染中逐渐变得可行。

 

阴影贴图

阴影贴图(Shadow Mapping)是一种常用的实时渲染技术,用于生成和计算场景中的阴影。其基本原理是通过从光源的角度生成深度信息,然后在实际渲染过程中使用这些深度信息来判断哪些像素在阴影中。下面详细讲解阴影贴图的实现步骤和技术细节。

1. 从光源角度渲染深度贴图

首先,从光源的角度渲染整个场景,并生成一张深度贴图(Depth Map)。这张贴图记录了从光源到每个物体表面的深度值。

步骤:

  1. 设置光源视图和投影矩阵:

    • 根据光源的位置和方向,设置光源的视图矩阵和投影矩阵(通常是正交投影用于方向光,透视投影用于点光源和聚光灯)。
    glm::mat4 lightView = glm::lookAt(lightPos, lightTarget, upVector);
    glm::mat4 lightProjection = glm::ortho(left, right, bottom, top, near, far);
    glm::mat4 lightSpaceMatrix = lightProjection * lightView;
  2. 渲染深度信息:

    • 使用光源的视图和投影矩阵渲染场景,但只记录深度值,而不渲染颜色。
    glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
    glClear(GL_DEPTH_BUFFER_BIT);
    // Render scene to depth map
    renderScene(lightSpaceMatrix);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

2. 渲染实际场景并应用阴影贴图

在从相机视角渲染实际场景时,需要将光源的深度信息与当前像素的深度信息进行比较,以确定该像素是否在阴影中。

步骤:

  1. 计算当前像素在光源视图中的位置:

    • 将当前像素的世界坐标转换到光源的视图空间。
    vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPos, 1.0);
  2. 深度比较:

    • 使用深度贴图中的深度值与当前像素的深度值进行比较。如果当前像素的深度大于深度贴图中的值,则该像素在阴影中。
    float shadow = currentDepth > closestDepth + bias ? 1.0 : 0.0;
  3. 应用阴影因子:

    • 使用阴影因子调整光照强度,生成阴影效果。
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular));

3. 处理阴影伪影

阴影贴图方法中常见的伪影包括锯齿状阴影和彼得潘效应(Peter Panning)。可以通过以下技术来处理这些问题:

  1. 深度偏差(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;
  2. 百分比更接近过滤(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)是指阴影的边缘是平滑的,而不是硬边的。这种阴影更接近于现实生活中的阴影,因为在现实世界中,光源通常不是点光源,而是具有一定面积的面光源或体积光源,导致阴影边缘出现渐变。

软阴影的生成

生成软阴影的方法有很多,包括:

  1. 面积光源采样(Area Light Sampling): 通过对光源的多个点进行采样,生成阴影的柔和边缘。
  2. 阴影贴图的模糊(Shadow Map Blurring): 对阴影贴图进行模糊处理,使得阴影边缘变得柔和。
  3. PCF(Percentage-Closer Filtering): 对多个邻近深度值进行平均处理,可以平滑阴影边缘。

阴影贴图与软阴影

阴影贴图(Shadow Mapping)是生成阴影的基础技术,通过记录从光源视角下场景中每个像素到光源的距离,来判断像素是否在阴影中。通过添加一些额外的处理,阴影贴图技术也可以用来生成软阴影。

使用阴影贴图生成软阴影的方法

以下是几种常见的方法,用于在阴影贴图的基础上生成软阴影:

使用阴影贴图生成软阴影的方法

以下是几种常见的方法,用于在阴影贴图的基础上生成软阴影:

  1. 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;
  2. 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);
    }
  3. 面积光源采样(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和面积光源采样等。通过这些技术,可以实现不同程度和质量的软阴影效果,以满足不同的渲染需求。

posted @ 2024-06-06 18:36  JeasonBoy  阅读(56)  评论(0编辑  收藏  举报