Skip to content

Latest commit

 

History

History

41_shadow_mapping

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

阴影映射

深度贴图

步骤1

创建帧缓冲

unsigned int depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);

步骤2

创建2D纹理供帧缓冲的深度缓冲使用

const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
unsigned int depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

步骤3

把生成的纹理作为帧缓冲的深度缓冲

glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

步骤4

渲染循环中

// 1.渲染深度贴图

// 2.使用深度贴图
glm::mat4 model = glm::mat4(1.0f);
// ++++++++++++++++++++++++++++++++++++++++++++++++
glm::mat4 lightProjection, lightView;
glm::mat4 lightSpaceMatrix;
float near_plane = 1.0f, far_plane = 7.5f;
lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
lightView = glm::lookAt(lightPosition, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
lightSpaceMatrix = lightProjection * lightView;
simpleShadowShader.use();
simpleShadowShader.setMat4("lightSpaceMatrix", lightSpaceMatrix);

glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
// 绘制场景
glBindTexture(GL_TEXTURE_2D, woodMap);

simpleShadowShader.setMat4("model", model);
drawMesh(floorGeometry);

glBindTexture(GL_TEXTURE_2D, brickMap);
model = glm::translate(model, glm::vec3(0.0, 0.5, 0.0));
simpleShadowShader.setMat4("model", model);
drawMesh(boxGeometry);

glBindFramebuffer(GL_FRAMEBUFFER, 0);
// ++++++++++++++++++++++++++++++++++++++++++++++++

glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT, 0.1f, 100.0f);

// 显示深度贴图
quadShader.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthMap);
quadShader.setMat4("view", view);

model = glm::mat4(1.0f);
quadShader.setFloat("near_plane", near_plane);
quadShader.setFloat("far_plane", far_plane);
quadShader.setMat4("model", model);
quadShader.setMat4("projection", projection);
drawMesh(quadGeometry);

image-20211125154712540

渲染阴影

image-20211125155059603

image-20211125155059603

阴影失真

非阴影区域有交替黑线出现,这叫做阴影失真

image-20211125160008256

因为阴影贴图受限于分辨率,在距离光源比较远的情况下,多个片段可能从深度贴图的同一个值中去采样,图片每个斜坡代表深度贴图一个单独的纹理像素,可以看到,多个片段从同一个深度值进行采样,当光源以一个角度朝向表面的时候会重修按问题,这种情况下深度贴图也是从一个角度下进行渲染,多个片段就会从同一个斜坡的深度纹理像素中采样,有些在地面上面,有些在地板下面,所以得到的阴影就会有差异。

因为有些片段被认为是在阴影之中,有些不在,因此产生了条纹样式。可以通过 阴影偏移的技巧来解决这个问题,可以简单的对表面的深度(或者深度贴图)应用一个偏移量,这样片段就不会被错误的认为在表面之下了。

float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
float shadow = currentDepth - bias > closestDepth  ? 1.0 : 0.0;

image-20211125161142647

悬浮

image-20211125161305371

使用阴影偏移一个缺点是悬浮,如果值很大则会出现阴影相对与实际位置的偏移,使物体看起来轻轻悬浮在表面之上

image-20211125161531392

解决方法:渲染深度贴图时,剔除正面

采样过多

image-20211125162748383

光的视锥不可见区域一律被认为时处于阴影之中,不过它真的处于阴影之中,出现这个原因是因为超过光的视锥的投影坐标比1.0大,这样深度纹理就会超出它默认的0到1的范围。

解决办法

将深度贴图的环绕方式设置为GL_CLAMP_TO_BORDER,然后给一个边框颜色,让超过的部分都显示同一个颜色

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
GLfloat borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

当一个点比光的远平面还要远时,它的投影坐标的z坐标大于1.0,那么此时GL_CLAMP_TO_BORDER环绕方式不起作用,因为我们把坐标的z元素和深度贴图的值进行了对比,大于1时返回true,可以手动去强制将其设置为0,那么此是超过的边缘就会是border 的颜色。

if(projCoords.z > 1.0) {
    shadow = 0.0;
}

PCF柔和阴影

深度贴图有一个固定的分辨率,多个片段对应于一个纹理像素,结果就是多个片段会从深度贴图的同一个深度值进行采样,这几个片段便得到的是同一个阴影,因此会产生锯齿边。

解决方法

  • 增加深度贴图分辨率

  • PCF - percentage closer filtering

    核心思想是从深度贴图中多次采样,每一次采样的纹理坐标都稍有不同,每个独立样本可能在或不在阴影中,将所有结果平均化得到柔和阴影

// PCF
float shadow = 0.0;
vec2 texSize = 1.0 / textureSize(shadowMap, 0);
for(int i = -1; i <= 1; i++) {
    for(int j = -1; j <= 1; j++) {
        float pcfDepth = texture(shadowMap, projCoords.xy + vec2(i, j) * texSize).r;
        shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
    }
}
shadow /= 9.0;

image-20211125171921599

参考

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/03%20Shadows/01%20Shadow%20Mapping/#_1