阴影图(Shadow Map)是计算机图形学中常用的一种技术,用于在渲染场景时添加真实的光影效果。通过计算场景中每个点与光源的相对位置,阴影图能够模拟出光照产生的阴影,从而增强图像的真实感。本文将深入解析阴影图的计算原理,并提供核心公式,帮助读者轻松绘制精准的光影效果。

1. 阴影图的基本原理

阴影图的基本原理是利用空间中每个点相对于光源的位置关系来确定该点是否处于阴影中。具体来说,就是将场景中每个点的三维坐标转换为投影到二维屏幕上的坐标,然后根据这些坐标在一张二维纹理上计算阴影深度。

2. 阴影图计算步骤

2.1 确定光源位置和方向

首先,需要确定光源的位置和方向。光源可以是点光源、聚光源或方向光源。对于点光源和聚光源,还需要确定光源的半径或角度。

2.2 投影到屏幕空间

将场景中每个点的三维坐标投影到二维屏幕空间。这一步通常通过透视变换完成。

glm::vec2 projectToScreenSpace(const glm::vec3& worldPosition, const glm::vec3& cameraPosition, const glm::vec3& cameraUp, const glm::vec3& cameraForward) {
    glm::vec3 viewDirection = glm::normalize(cameraPosition - worldPosition);
    glm::vec3 right = glm::normalize(glm::cross(cameraUp, viewDirection));
    glm::vec3 up = glm::normalize(glm::cross(viewDirection, right));
    float aspectRatio = 16.0f / 9.0f;
    float zNear = 0.1f;
    float zFar = 1000.0f;
    float x = (right * worldPosition).x * aspectRatio;
    float y = (up * worldPosition).y;
    float z = (viewDirection * worldPosition).z;
    return glm::vec2(x / zFar * zFar + 0.5f * zFar, y / zFar * zFar + 0.5f * zFar);
}

2.3 计算阴影深度

对于每个屏幕空间中的点,计算其与光源的直线距离,并查找相应的阴影深度值。

float calculateShadowDepth(const glm::vec2& screenPosition, const glm::vec3& lightPosition, const glm::vec3& normal, const std::vector<float>& shadowMap) {
    float minDepth = shadowMap[static_cast<int>(screenPosition.x) * textureWidth + static_cast<int>(screenPosition.y)];
    for (int i = -1; i <= 1; ++i) {
        for (int j = -1; j <= 1; ++j) {
            glm::vec2 neighborPosition = screenPosition + glm::vec2(i, j) * shadowMapTextureSize / 2.0f;
            if (neighborPosition.x < 0.0f || neighborPosition.x >= textureWidth || neighborPosition.y < 0.0f || neighborPosition.y >= textureHeight) {
                continue;
            }
            float neighborDepth = shadowMap[static_cast<int>(neighborPosition.x) * textureWidth + static_cast<int>(neighborPosition.y)];
            float depthDifference = glm::dot(normal, lightPosition - worldPosition) / glm::length(lightPosition - worldPosition);
            float neighborDepthDifference = glm::dot(normal, neighborPosition - worldPosition) / glm::length(neighborPosition - worldPosition);
            if (glm::abs(neighborDepthDifference - depthDifference) < shadowBias) {
                minDepth = glm::min(minDepth, neighborDepth);
            }
        }
    }
    return minDepth;
}

2.4 应用阴影效果

最后,根据计算得到的阴影深度值,对场景中的点应用阴影效果。如果阴影深度大于物体表面的深度,则将该点设置为阴影。

3. 总结

通过以上步骤,我们可以计算出场景中每个点的阴影深度,并将其用于绘制阴影效果。阴影图是一种简单而有效的技术,可以帮助我们模拟真实的光影效果,提高图像的真实感。在实际应用中,我们可以根据需要调整阴影图的分辨率、光照模型和阴影偏移量等参数,以获得最佳的效果。