面试 JavaWeb Gitlab Morecoin typora VMware session paypal triggers rss vue前端 后台管理模板 河南普通话考试报名 大数据项目开发案例 mysql小数用什么类型 abaqus是什么软件 webform开发教程 nodejs后端开发 ubuntu显示隐藏文件夹 python转java js基本数据类型有哪些 完美解决cpu利用率低 linux查找文件内容 SketchUp python运行环境 java多态 java接口类 java基本语法 java表达式 java中的数据结构 java基本数据结构 java声明变量 java安装与配置 java流程 java接口规范 linux的安装 java游戏制作 微信超级好友 黑白照片一键变彩色 苹果手机老是自动重启
当前位置: 首页 > 学习教程  > 编程语言

OpenGL学习笔记 渲染阴影 阴影学习(二)

2020/7/24 11:18:21 文章标签: 测试文章如有侵权请发送至邮箱809451989@qq.com投诉后文章立即删除

渲染阴影

获得阴影的深度贴图

见阴影学习(一)

阴影映射

打开官方的实例源码使用了6个shader,前四个都在上个笔记中提到过。

剩下的两个shader的作用是正常的给予光照渲染,对每一个片段都判断是否在我们的阴影处,然后进行渲染,渲染的方式也很简单。

那么先看看最终效果

(优化后的)

在这里插入图片描述

(未优化的)

在这里插入图片描述

  1. 我们现在准备好了帧缓冲,里面也有了我们所需要的深度贴图,我们还需要正常的准备好我们的场景以及各种资源的绑定。将我们准备好的光源变换矩阵和深度贴图赋值给shader

   	shader.setVec3("viewPos", camera.Position);
	shader.setVec3("lightPos", lightPos);
    //光源变换矩阵的绑定
	shader.setMat4("lightSpaceMatrix", lightSpaceMatrix);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, woodTexture);
	glActiveTexture(GL_TEXTURE1);
    //深度贴图的绑定
	glBindTexture(GL_TEXTURE_2D, depthMap);
	renderScene(shader);
  1. 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;


out vec2 TexCoords;

//代码块输出
out VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
    vec4 FragPosLightSpace;//经过矩阵转换的点
} vs_out;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;//光照空间变换矩阵

void main()
{
    vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
    vs_out.Normal = transpose(inverse(mat3(model))) * aNormal;
    vs_out.TexCoords = aTexCoords;
    vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0);
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

只是在基础的输出上增加了一个光照空间变换矩阵的传递。

片段着色器

#version 330 core
out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
    vec4 FragPosLightSpace;
} fs_in;

uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;

uniform vec3 lightPos;
uniform vec3 viewPos;

//ShadowCalculation用来判断是否在阴影处。
float ShadowCalculation(vec4 fragPosLightSpace)
{
    // 执行透视除法
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    // 变换到[0,1]的范围
    projCoords = projCoords * 0.5 + 0.5;
    // 取得最近点的深度(使用[0,1]范围下的fragPosLight当坐标)
    float closestDepth = texture(shadowMap, projCoords.xy).r; 
    // 取得当前片段在光源视角下的深度
    float currentDepth = projCoords.z;
    // 检查当前片段是否在阴影中
    float shadow = currentDepth > closestDepth  ? 1.0 : 0.0;

    return shadow;
}
// 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 fragPosLight as coords)
//     float closestDepth = texture(shadowMap, projCoords.xy).r; 
//     // Get depth of current fragment from light's perspective
//     float currentDepth = projCoords.z;
//     // Calculate bias (based on depth map resolution and slope)
//     vec3 normal = normalize(fs_in.Normal);
//     vec3 lightDir = normalize(lightPos - fs_in.FragPos);
//     float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
//     // Check whether current frag pos is in shadow
//     // float shadow = currentDepth - bias > closestDepth  ? 1.0 : 0.0;
//     // 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, projCoords.xy + vec2(x, y) * texelSize).r; 
//             shadow += currentDepth - bias > pcfDepth  ? 1.0 : 0.0;        
//         }    
//     }
//     shadow /= 9.0;
    
//     // Keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
//     if(projCoords.z > 1.0)
//         shadow = 0.0;
        
//     return shadow;
// }

void main()
{           
    vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;//纹理颜色
    vec3 normal = normalize(fs_in.Normal);
    vec3 lightColor = vec3(0.3);
    // ambient
    vec3 ambient = 0.3 * color;
    // diffuse
    vec3 lightDir = normalize(lightPos - fs_in.FragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * lightColor;
    // specular
    vec3 viewDir = normalize(viewPos - fs_in.FragPos);
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);  
    spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
    vec3 specular = spec * lightColor;    
    // calculate shadow
    float shadow = ShadowCalculation(fs_in.FragPosLightSpace);  //判断点在不在纹理中                    
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;    
    
    FragColor = vec4(lighting, 1.0);
}

ShadowCalculation函数用来判断该片段是不是阴影处。(代码中展示的是未经优化的。)

Shadow Acne

出来的效果可以明显的看到

在这里插入图片描述

我们可以看到地板四边形渲染出很大一块交替黑线。这种阴影贴图的不真实感叫做***阴影失真(Shadow Acne)***

具体成因是因为阴影的分辨率限制。

在这里插入图片描述

凡是曲面法线跟光线不平行的且理论上应该被点亮的像素都可能会有这样的问题(出不出现问题取决于法线跟光线的夹角大小和深度贴图的分辨率)

假设因为深度贴图的分辨率太小,这四个像素点共用深度贴图中的同一个像素点来作为比较对象。
那么在计算这四个像素点的深度贴图时,就如下图这样:

在这里插入图片描述

假设取中心点坐标作为深度贴图的坐标,距离是10。
怎么理解呢,就是从光源出发经过这四个像素点的这么一条射线路径中,距离光源最近的点离光源的距离是10。
得到了深度贴图之后,就应该判定这四个像素点是否应该被点亮了。

在这里插入图片描述

比如:判断像素a是不是阴影,要根据像素a点的坐标来取出深度贴图的距离。显然,会得到10。然后计算像素点a跟光源之间的距离,从上图可以看出,是一个小于10的值,我就假设是9.8吧,和10一比较之后,判定a点跟光源之间没有障碍物遮挡,a点被点亮。同理,可以得到其他三个像素点的情况分别是b点阴影,c点阴影,d点被点亮。但是我们知道,理论上其实这四个点都是应该被点亮的,而不是两个黑两个亮。这就造成了阴影失真了。

https://www.zhihu.com/question/49090321/answer/114217482资料来源

解决方法

在这里插入图片描述

在shader代码中体现的是

float bias = 0.005;
float shadow = currentDepth - bias > closestDepth  ? 1.0 : 0.0;

减少了目标片段的深度,让由于失真片段的深度都在被判断成“不是阴影”的范围内。

Peter Panning

在当我们的偏移值过大的时候,会出现物体悬浮在表面。

我们可以使用一个叫技巧解决大部分的Peter panning问题:当渲染深度贴图时候使用正面剔除(front face culling)你也许记得在面剔除教程中OpenGL默认是背面剔除。我们要告诉OpenGL我们要剔除正面。

PCF

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SkcSrku0-1595558357646)(static/shadow_mapping_zoom.png)]

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

另一个(并不完整的)解决方案叫做PCF(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, projCoords.xy + vec2(x, y) * texelSize).r; 
        shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;        
    }    
}
shadow /= 9.0;

这个textureSize返回一个给定采样器纹理的0级mipmap的vec2类型的宽和高。用1除以它返回一个单独纹理像素的大小,我们用以对纹理坐标进行偏移,确保每个新样本,来自不同的深度值。这里我们采样得到9个值,它们在投影坐标的x和y值的周围,为阴影阻挡进行测试,并最终通过样本的总数目将结果平均化。


本文链接: http://www.dtmao.cc/news_show_50377.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?