首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

使用延迟渲染的阴影贴图(OpenGL)

延迟渲染(Deferred Rendering)是一种渲染技术,它将场景中的几何处理(例如,变换和光栅化)与光照和着色处理分离。这种技术可以有效地处理大量的光源,因为它只需要对屏幕上的每个像素执行一次光照计算,而不是对每个物体执行一次。

在使用OpenGL实现延迟渲染的阴影贴图时,你需要进行以下步骤:

1. 准备G-Buffer

G-Buffer(Geometry Buffer)是一个屏幕大小的纹理数组,用于存储场景中的几何信息,如位置、法线、颜色等。

代码语言:javascript
复制
GLuint gBuffer;
glGenFramebuffers(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);

GLuint gPosition, gNormal, gColorSpec;

// 位置纹理
glGenTextures(1, &gPosition);
glBindTexture(GL_TEXTURE_2D, gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, screenWidth, screenHeight, 0, GL_RGB, GL_FLOAT, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);

// 法线纹理
glGenTextures(1, &gNormal);
glBindTexture(GL_TEXTURE_2D, gNormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, screenWidth, screenHeight, 0, GL_RGB, GL_FLOAT, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);

// 颜色+镜面纹理
glGenTextures(1, &gColorSpec);
glBindTexture(GL_TEXTURE_2D, gColorSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screenWidth, screenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gColorSpec, 0);

// 告诉OpenGL我们将使用这些附件
GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(3, attachments);

// 创建并绑定深度缓冲
GLuint depthBuffer;
glGenRenderbuffers(1, &depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, screenWidth, screenHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer);

// 检查帧缓冲完整性
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);

2. 几何阶段

在这个阶段,你将场景中的每个物体渲染到G-Buffer中。

代码语言:javascript
复制
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);

// 渲染场景中的每个物体
for(auto& object : sceneObjects) {
    // 设置模型矩阵、视图矩阵、投影矩阵
    // ...

    // 绑定VAO并绘制物体
    glBindVertexArray(object.vao);
    glDrawElements(GL_TRIANGLES, object.indexCount, GL_UNSIGNED_INT, 0);
}

3. 光照阶段

在这个阶段,你将对屏幕上的每个像素执行光照计算。

代码语言:javascript
复制
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 设置视口
glViewport(0, 0, screenWidth, screenHeight);

// 使用着色器程序
shader.use();

// 绑定G-Buffer纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPosition);
shader.setInt("gPosition", 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
shader.setInt("gNormal", 1);

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gColorSpec);
shader.setInt("gColorSpec", 2);

// 渲染一个全屏四边形
renderQuad();

// 在片段着色器中进行光照计算
// ...

4. 阴影贴图

为了在延迟渲染中实现阴影贴图,你需要在光照阶段考虑光源的视角。这通常涉及到创建一个光源的深度贴图,并在片段着色器中使用它来计算阴影。

创建光源深度贴图

代码语言:javascript
复制
// 设置光源的视口和投影矩阵
// ...

// 渲染场景到光源深度贴图
glBindFramebuffer(GL_FRAMEBUFFER, lightDepthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
glCullFace(GL_FRONT);

for(auto& object : sceneObjects) {
    // 设置模型矩阵、光源视图矩阵、光源投影矩阵
    // ...

    // 绑定VAO并绘制物体
    glBindVertexArray(object.vao);
    glDrawElements(GL_TRIANGLES, object.indexCount, GL_UNSIGNED_INT, 0);
}

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glCullFace(GL_BACK);

在片段着色器中使用阴影贴图

代码语言:javascript
复制
// 片段着色器
#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gColorSpec;
uniform sampler2D shadowMap;

uniform vec3 viewPos;
uniform vec3 lightPos;
uniform mat4 lightSpaceMatrix;

float ShadowCalculation(vec4 fragPosLightSpace) {
    // 执行透视除法
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    projCoords = projCoords * 0.5 + 0.5;
    float closestDepth = texture(shadowMap, projCoords.xy).r;
    float currentDepth = projCoords.z;
    float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
    return shadow;
}

void main() {
    // 从G-Buffer中获取位置、法线和颜色
    vec3 FragPos = texture(gPosition, TexCoords).rgb;
    vec3 Normal = texture(gNormal, TexCoords).rgb;
    vec3 Diffuse = texture(gColorSpec, TexCoords).rgb;

    // 计算光照
    vec3 lighting = vec3(0.0);
    vec3 ambient = 0.1 * Diffuse;
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 lightDir = normalize(lightPos - FragPos);

    // 计算漫反射
    float diff = max(dot(Normal, lightDir), 0.0);
    vec3 diffuse = diff * Diffuse;

    // 计算镜面高光
    vec3 reflectDir = reflect(-lightDir, Normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = vec3(0.2) * spec;

    // 计算阴影
    vec4 fragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0);
    float shadow = ShadowCalculation(fragPosLightSpace);

    lighting = (ambient + (1.0 - shadow) * (diffuse + specular));

    FragColor = vec4(lighting, 1.0);
}

注意事项

  • 确保你的光源深度贴图和G-Buffer使用相同的分辨率和格式。
  • 在计算阴影时,考虑使用PCF(Percentage Closer Filtering)或其他平滑技术来减少阴影锯齿。
  • 如果你的场景中有大量的动态光源,可能需要优化光照阶段的性能。

通过以上步骤,你可以在OpenGL中实现一个基本的延迟渲染系统,并添加阴影贴图以增强场景的真实感。

页面内容是否对你有帮助?
有帮助
没帮助

相关·内容

  • Shader经验分享

    流水线 1.应用阶段:(CPU)输出渲染图元,粗粒度剔除等 比如完全不在相机范围内的需要剔除,文件系统的粒子系统实现就用到粗粒度剔除。 2.几何阶段:(GPU)把顶点坐标转换到屏幕空间,包含了模型空间 到世界空间 到观察空间(相机视角view) 到齐次裁剪空间(投影project2维空间,四维矩阵,通过-w<x<w判断是否在裁剪空间) 到归一化设备坐标NDC(四维矩阵通过齐次除法,齐次坐标的w除以xyz实现归一化) 到屏幕空间(通过屏幕宽高和归一化坐标计算)。 a.顶点着色器:坐标变换和逐顶点光照,将顶点空间转换到齐次裁剪空间。 b.曲面细分着色器:可选 c.几何着色器:可选 d.裁剪:通过齐次裁剪坐标的-w<x<w判断不在视野范围内的部分或者全部裁剪,归一化。 e.屏幕映射:把NDC坐标转换为屏幕坐标 3.光栅化阶段:(GPU)把几何阶段传来的数据来产生屏幕上的像素,计算每个图元覆盖了哪些像素,计算他们的颜色、 a.三角形设置:计算网格的三角形表达式 b.三角形遍历:检查每个像素是否被网格覆盖,被覆盖就生成一个片元。 c.片元着色器:对片元进行渲染操作 d.逐片元操作:模板测试,深度测试 混合等 e.屏幕图像 ------------------------------------------------------- 矩阵: M*A=A*M的转置(M是矩阵,A是向量,该公式不适合矩阵与矩阵) 坐标转换: o.pos = mul(UNITY_MATRIX_MVP, v.vertex);顶点位置模型空间到齐次空间 o.worldNormal = mul((float3x3)_Object2World,v.normal);//游戏中正常的法向量转换,转换后法向量可能不与原切线垂直,但是不影响游戏显示,而且大部分显示也是差不多的。一般用这个就行了。 o.worldNormal = mul(v.normal, (float3x3)_World2Object);顶点法向量从模型空间转换到世界空间的精确算法,公式是用_Object2World该矩阵的逆转置矩阵去转换法线。然后通过换算得到该行。 ------------------------------------------------------- API: UNITY_MATRIX_MVP 将顶点方向矢量从模型空间变换到裁剪空间 UNITY_MATRIX_MV 将顶点方向矢量从模型空间变换到观察空间 UNITY_MATRIX_V 将顶点方向矢量从世界空间变换到观察空间 UNITY_MATRIX_P 将顶点方向矢量从观察空间变换到裁剪空间 UNITY_MATRIX_VP 将顶点方向矢量从世界空间变换到裁剪空间 UNITY_MATRIX_T_MV UNITY_MATRIX_MV的转置矩阵 UNITY_MATRIX_IT_MV UNITY_MATRIX_MV的逆转置矩阵,用于将法线从模型空间转换到观察空间 _Object2World将顶点方向矢量从模型空间变换到世界空间,矩阵。 _World2Object将顶点方向矢量从世界空间变换到模型空间,矩阵。 模型空间到世界空间的矩阵简称M矩阵,世界空间到View空间的矩阵简称V矩阵,View到Project空间的矩阵简称P矩阵。 --------------------------------------------- _WorldSpaceCameraPos该摄像机在世界空间中的坐标 _ProjectionParams _ScreenParams _ZBufferParams unity_OrthoParams unity_Cameraprojection unity_CameraInvProjection unity_CameraWorldClipPlanes[6]摄像机在世界坐标下的6个裁剪面,分别是左右上下近远、 ---------------------------- 1.表面着色器 void surf (Input IN, inout SurfaceOutput o) {}表面着色器,unity特殊封装的着色器 Input IN:可以引用外部定义输入参数 inout SurfaceOutput o:输出参数 struct SurfaceOutput//普通光照 { half3 Albedo;//纹理,反射率,是漫反射的颜色值 half3 Normal;//法线坐标 half3 Emission;//自发光颜色 half Specular;//高光,镜面反射系数 half Gloss;//光泽度 half Alpha;//alpha通道 } 基于物理的光照模型:金属工作流Surfa

    04

    Threejs入门之十七:给物体添加阴影

    在前面的章节中,我们已经实现了将物体添加到场景中,并设置了灯光等效果,但是,这并不是很真实,在真实的世界中,被灯光照射的物体是有阴影的,这一节我们就来给物体添加阴影。 在Threejs中给物体添加阴影,需要注意以下几点 1.要选择具有投射阴影效果的材质 我们前面也提到过,基础网格材质MeshBasicMaterial是不受光照影响的,我们如果需要有阴影效果,就不能选择该材质 2.需要投射阴影的物体要设置castShadow属性 castShadow属性用于设置物体是否被渲染到阴影贴图中,默认为false,如果需要投影,则设置为true 3.接收阴影的物体要开启receiveShadow属性 receiveShadow属性用于设置材质是否接收阴影,默认为false,如果需要接收物体的投影,设置为true 4.灯光开启投射阴影castShadow属性 灯光也要设置castShadow为true,默认为false 5.渲染器设置允许在场景中使用阴影贴图 将渲染器的shadowMap.enabled属性设置为true,允许场景中使用阴影贴图 经过上面五步的设置,就可以开启物体的阴影效果了,具体实现代码如下

    01
    领券