阴影贴图

将深度信息渲染到纹理

image-20230106163202431

​ 首先从光源的角度来看,光源是位于左上角并且指向立方体。看图中A,B,C这3个点。当B被渲染时,它的深度值进入深度缓冲区,因为在B和光源之间没有任何东西,默认它是那条线上离光源最近的点。然而当A和C被渲染的时候,它们在深度缓冲区的同一个点上“竞争”。两个点都在同一条来自光源的直线上,所以在透视投影后,光栅器发现这两个点需要去往屏幕上的同一个像素,则C点的深度值被写入了深度缓存中。

step1:创建纹理作为阴影图

1
2
3
4
5
6
7
8
9
10
11
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);
//GL_DEPTH_COMPONENT:每个纹素放一个单精度浮点数用于存放已经标准化后的深度值 0:暂时不提供数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
//过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);

step2:创建配置帧缓冲

1
2
3
4
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
//GL_DEPTH_ATTACHMENT:附着在上面的纹理收到深度测试的结果,0:一个层级
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);

step3:相关设置

​ 禁止向颜色缓存中写入,只输出深度,默认情况下,颜色缓存会被绑定在GL_COLOR_ATTACHMENT0上,此使的帧缓冲不包含纹理缓冲区,最后确认帧缓冲的状态完整。

1
2
3
4
5
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
GLuint error = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
return false;

step4:在缓冲绘制图像

本次绘制不要片段着色器,但一定要开启深度测试(此处以点光源的mvp为例)

1
2
3
4
5
6
7
8
9
10
11
12
//将图像绘制进帧缓冲而不是显示在屏幕
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
//从光源的角度观察,计算mvp
glm::vec3 lightInvDir = glm::vec3(0.5f, 2, -2);
glm::vec3 lightPos(5, 20, 20);
glm::mat4 depthP = glm::perspective<float>(45.0f, 1.0f, 2.0f, 50.0f);
glm::mat4 depthV = glm::lookAt(lightPos, lightPos-lightInvDir, glm::vec3(0,1,0));
glm::mat4 depthM = glm::mat4(1.0);
glm::mat4 depthMVP = depthP * depthV * depthM;
//正常绘制图像
//回到默认帧缓冲,渲染到屏幕
glBindFramebuffer(GL_FRAMEBUFFER, 0);

绘制物体

step5:在屏幕绘制图像

1
2
3
4
5
6
7
8
9
10
11
12
//将帧缓冲纹理作为正常纹理渲染
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthTexture);
glUniform1i(TextureID, 0);
//便于从齐次坐标[-1,1]变换到纹理坐标[0,1]
glm::mat4 biasMatrix(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);
//除了传入相机视角的MVP矩阵,还要传入光源视角的MVP矩阵
glm::mat4 depthBiasMVP = biasMatrix * depthMVP;

step6:着色器文件(与上一次绘制不同的着色器)

​ 其次,从摄像机的角度于每一个像素,从深度缓冲区中取出相应的深度值,同时也计算这个像素到光源的距离。如果这两个深度值不同,意味着从光源看这个像素时有其他像素遮挡了它,这种情况下在颜色计算中要增加阴影因子来模仿阴影效果。

(1)顶点着色器

1
2
ShadowCoord = DepthBiasMVP * vec4(position,1);//光源视角
gl_Position = MVP * vec4(position,1);//相机视角

​ 由于片段着色器将接收到的裁剪空间下的坐标看做一个标准的顶点属性,光栅化程序不会对其进行透视分割(只有传到gl_position变量中的顶点才会自动执行透视分割)。将这个向量除以其W分量手动透视分割;

(2)片段着色器

从阴影贴图中获取深度数据的,将上面阴影贴图的深度值和当前像素的深度值进行比较,如果阴影贴图的深度值小,也就是阴影离相机近,那么就返回0.2作为阴影参数,反之就返回1.0表示没有阴影。bias是修复参数,仅用这一个参数还是会出现粗糙的阴影,还需要更多参数调整。https://blog.csdn.net/linjf520/article/details/105380551/

float bias = 0.005;
float visibility=1.0;
if ( texture2D( sampler, (ShadowCoord.xy/ShadowCoord.w) ).x  <  (ShadowCoord.z-bias)/ShadowCoord.w ){
    visibility=0.2;
}

将阴影参数传进来并调整漫射光和镜面反射光的颜色值,环境光就不受阴影影响。

1
2
3
4
gl_FragColor.rgb = 
MaterialAmbientColor +
visibility* MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) +
visibility*MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance);

image-20230125133010521

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码

请我喝杯咖啡吧~

支付宝
微信