OpenGL学习(十一):延迟渲染管线

OpenGL学习 专栏收录该内容
14 篇文章 13 订阅

前言

上一篇文章回顾:OpenGL学习(十)天空盒

上一次更新还是元旦了,因为最近一直忙于期末考试的预习,考完玩了几天,昨天才 run 回家,今天开始更新!

在这之前的博客,我们都是使用 “前向渲染” 来对生成的像素做处理,比如我们可以为每一个像素添加光照,阴影等效果。但是今天我们要引入一种更加高级的渲染方式 ---- 延迟渲染。

注:本文代码基于上一篇博客

延迟渲染简介

延迟渲染是各大计算机游戏引擎常用的一种较为现代的渲染策略,延迟渲染的出现最主要是为了解决了大量光源的光照计算,减少了片元着色器的计算量,使得渲染复杂的场景变得可能!

回顾我们的 OpenGL 的流水线,我们在对三角面片进行光栅化之后,马上生成像素,并且 call GPU 的一个线程,对该像素运行一次片段着色器。这并未考虑到物体之间的遮挡关系!如果此时有另一片三角面片也绘制在同样的地方,前一个三角型的像素就会被遮挡,浪费片段着色器计算力。示意图如下:

在这里插入图片描述

如果场景非常复杂,那么我们会有多重的覆盖关系,那么片元着色器将会被运行很多次。对于一些复杂计算,比如计算光照,阴影等开销大的算法,无效的片元开销是致命的。


延迟渲染的出现解决了这个问题。以光照计算为例,回想我们计算光照需要那些信息?

  • 当前像素的世界坐标
  • 当前像素的法线
  • 当前像素的颜色

那么我们将这些信息先输出到一些纹理上存储起来,随后绘制一个正方形当作我们的 “屏幕”,然后针对每个屏幕上的像素,从纹理中取出需要的颜色,法线,坐标等信息,再进行光照计算。这样无论场景有多么复杂,我们都只需要运行 w × h 次片元着色器,其中 w,h 为屏幕宽高,绝不浪费宝贵的光照计算。

下面是一个典型的延迟渲染管线的大概结构:
在这里插入图片描述

首先是 shadowMap 阶段,我们从光源方向进行渲染,获得阴影贴图。gbuffer 阶段我们正常地渲染,但是不输出颜色,反之我们输出必要的信息到纹理。在后处理阶段我们绘制光影特效,最终输出一帧。

注:
一般后处理着色器可以有很多个,因为要对一些像素做多次 pass,比如计算泛光或者 SSR 等特效
此外,针对多个后处理着色器的管线,我们可以创建一个后处理帧缓冲和多个颜色附件,然后按照顺序将必要的纹理 pass 下去,那么该管线就和 Minecraft 的 optifine 模组提供的管线相似了。


即然提到了 MC 就多嗦两句 p 话:
optifine 提供了 10 个后处理阶段的自定义着色器(composite0 ~ 9)和一个最终合成阶段着色器(final),final 着色器总是工作在后处理阶段之后,用来进行不同帧缓冲数据合成。此外,optifine允许用户任意地操作 8 个颜色纹理附件,但是最终 0 号颜色附件会被输出到屏幕。

变量创建与准备

因为 gbuffer 阶段的渲染需要一些纹理以缓存,我们定义:

  • gcolor 存储基础颜色
  • gdepth 存储像素的深度
  • gworldpos 存储像素的世界坐标
  • gnormal 存储像素的法向量

我们创建如下的全局变量,同时生成对应的着色器:

// 延迟渲染阶段
GLuint gbufferProgram;
GLuint gbufferFBO;  // gbuffer 阶段帧缓冲
GLuint gcolor;      // 基本颜色纹理
GLuint gdepth;      // 深度纹理
GLuint gworldpos;   // 世界坐标纹理
GLuint gnormal;     // 法线纹理

// 后处理阶段
GLuint composite0;

...

// 生成着色器程序对象
gbufferProgram = getShaderProgram("shaders/gbuffer.fsh", "shaders/gbuffer.vsh");
shadowProgram = getShaderProgram("shaders/shadow.fsh", "shaders/shadow.vsh");
debugProgram = getShaderProgram("shaders/debug.fsh", "shaders/debug.vsh");
skyboxProgram = getShaderProgram("shaders/skybox.fsh", "shaders/skybox.vsh");
composite0 = getShaderProgram("shaders/composite0.fsh", "shaders/composite0.vsh");

其中着色器和他们所属的阶段如下:

在这里插入图片描述

随后我们创建一个 gubffer 的帧缓冲,并且指定 3 个颜色附件和一个深度附件,用于存储必要的信息:

// 创建 gubffer 帧缓冲
glGenFramebuffers(1, &gbufferFBO);
glBindFramebuffer(GL_FRAMEBUFFER, gbufferFBO);

// 创建颜色纹理
glGenTextures(1, &gcolor);
glBindTexture(GL_TEXTURE_2D, gcolor);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_RGBA, 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_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 将颜色纹理绑定到 0 号颜色附件
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gcolor, 0);

// 创建法线纹理
glGenTextures(1, &gnormal);
glBindTexture(GL_TEXTURE_2D, gnormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_RGBA, 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_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 将法线纹理绑定到 1 号颜色附件
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gnormal, 0);

// 创建世界坐标纹理
glGenTextures(1, &gworldpos);
glBindTexture(GL_TEXTURE_2D, gworldpos);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_RGBA, 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_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 将世界坐标纹理绑定到 2 号颜色附件
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gworldpos, 0);

// 创建深度纹理
glGenTextures(1, &gdepth);
glBindTexture(GL_TEXTURE_2D, gdepth);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, windowWidth, windowHeight, 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_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 将深度纹理绑定到深度附件
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, gdepth, 0);

// 指定附件索引
GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(3, attachments);
glBindFramebuffer(GL_FRAMEBUFFER, 0);	// 解绑

注意到 glDrawBuffers 函数指定的附件绘制顺序,即是着色器中 gl_FragData 的索引,比如 gl_FragData[0] 对应的是 attachments[0] ,也就是我们的颜色纹理附件!执行如下的操作能向 0 号纹理附件中进行写入一块红色:

gl_FragData[0] = vec4(1, 0, 0, 1);

注:
其实没有必要生成世界坐标纹理,因为可以通过深度纹理和当前像素的屏幕坐标,重建其世界坐标。
Minecraft 的光影模组 optifine 就是这么操作的,但是缺点是必须知道 模型-视图矩阵的逆矩阵
这里我懒得算了,干脆直接缓存世界坐标来用了。。。

shadowMap 阶段

shadowMap 阶段和往常一样,我们正常进行绘制即可,因为最终我们需要的只是一张阴影贴图:

display 函数

...

// 从光源方向进行渲染
glUseProgram(shadowProgram);
glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, shadowMapResolution, shadowMapResolution);

// 光源看向世界坐标原点
shadowCamera.direction = glm::normalize(glm::vec3(0, 0, 0) - shadowCamera.position);
// 传视图矩阵
...
// 传投影矩阵
...

// 从光源方向进行绘制
for (auto m : models)
{
    m.draw(shadowProgram);
}

gbuffer 阶段

在 gbuffer 阶段,我们分两个着色器进行绘制。skybox 着色器负责绘制天空盒,而 gbuffer 着色器负责绘制一般物体。所以我们要进行两次 draw call,代码如下:

display 函数

...

// 绘制天空盒 -- 输出到 gbuffer 阶段的 3 张纹理中
glUseProgram(skyboxProgram);
glBindFramebuffer(GL_FRAMEBUFFER, gbufferFBO);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, windowWidth, windowHeight);

// 传视图,投影矩阵
...

// 传cubemap纹理
...

// 传递 zfar 和 znear 方便让天空盒的坐标置于最大视距
...

glDepthMask(GL_FALSE);
skybox.draw(skyboxProgram);
glDepthMask(GL_TRUE);

// ------------------------------------------------------------------------ // 

// 正常绘制 -- 输出到 gbuffer 阶段的 3 张纹理中
glUseProgram(gbufferProgram);

// 传视图矩阵
...
// 传投影矩阵
...

// 正常绘制
for (auto m : models)
{
    m.draw(gbufferProgram);
}

值得注意的是,片元着色器不再输出颜色了,反而是输出几何信息到我们的纹理缓存中,下面是 gbuffer.fsh 片元着色器的代码:

#version 330 core

...

void main()
{
    gl_FragData[0] = texture2D(texture, texcoord);  // 写入 gcolor
    gl_FragData[1] = vec4(normalize(normal), 0.0);  // 写入 gnormal
    gl_FragData[2] = vec4(worldPos, 1.0);           // 写入 gworldpos
}

对于 skybox 天空盒的绘制则比较特殊,因为天空盒不参与光照阴影的计算,于是我们在其世界坐标中给他一个很大的值,这里利用了透视投影摄像机的 far 参数,我们将天空的距离拉扯至两倍的远截面:

#version 330 core

...

uniform float far;

void main()
{
    gl_FragData[0] = textureCube(skybox, texcoord); // 写入 gcolor
    gl_FragData[1] = vec4(vec3(0), 0.0);            // 写入 gnormal
    gl_FragData[2] = vec4(texcoord*far*2, 1.0);     // 写入 gworldpos
}

后处理阶段

我们只需要绘制一个四边形,并且让他铺满屏幕即可。此外,我们传递 gbuffer 阶段绘制的必要的几何信息(就是那些纹理),代码如下:

display 函数

...

// debug着色器输出一个四方形以显示纹理中的数据
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);   // 需要取消深度测试以保证其覆盖在原画面上
glUseProgram(debugProgram);
glViewport(0, 0, windowWidth, windowHeight);

// 传递 zfar 和 znear 方便转线性深度
...

// 传 gcolor 纹理
...
// 传 gnormal 纹理
...
// 传 gworldpos 纹理
...
// 传 gdepth 纹理
...
// 传阴影深度纹理
...

// 绘制
screen.draw(debugProgram);
glEnable(GL_DEPTH_TEST);

随后我们在着色器中,根据屏幕坐标取 4 个纹理中不同的数据,并且打印在屏幕上,下面是 debug 着色器的代码:

#version 330 core

...

void main()
{   
    // 屏幕左下显示 gcolor
    if(0<=texcoord.x && texcoord.x<=0.5 && 0<=texcoord.y && texcoord.y<=0.5)
    {
        vec2 coord = vec2(texcoord.x*2, texcoord.y*2);
        fColor = vec4(texture2D(gcolor, coord).rgb, 1); 
    }

    // 屏幕右下显示 gnormal
    if(0.5<=texcoord.x && texcoord.x<=1 && 0<=texcoord.y && texcoord.y<=0.5)
    {
        vec2 coord = vec2(texcoord.x*2-1, texcoord.y*2);
        fColor = vec4(texture2D(gnormal, coord).rgb, 1); 
    }

    // 屏幕左上显示 gdepth
    if(0<=texcoord.x && texcoord.x<=0.5 && 0.5<=texcoord.y && texcoord.y<=1)
    {
        vec2 coord = vec2(texcoord.x*2, texcoord.y*2-1);
        float d = linearizeDepth(texture2D(gdepth, coord).r, near, far);
        //d = texture2D(shadowtex, coord).r;
        fColor = vec4(vec3(d*0.5+0.5), 1); 
    }

    // 屏幕右上显示 gworldpos
    if(0.5<=texcoord.x && texcoord.x<=1 && 0.5<=texcoord.y && texcoord.y<=1)
    {
        vec2 coord = vec2(texcoord.x*2-1, texcoord.y*2-1);
        fColor = vec4(texture2D(gworldpos, coord).rgb, 1); 
    }
}

效果如下:

在这里插入图片描述
可以看到 4 个纹理的数据都正常。现在我们开始进行光影的绘制,我们编写 composite0 着色器,片段着色器的内容如下:

#version 330 core

...

void main()
{   
    fColor.rgb = texture2D(gcolor, texcoord).rgb;
    vec3 worldPos = texture2D(gworldpos, texcoord).xyz;
    vec3 normal = texture2D(gnormal, texcoord).xyz;

    float isInShadow = shadowMapping(shadowtex, shadowVP, vec4(worldPos, 1.0));
    PhongStruct phong = phong(worldPos, cameraPos, lightPos, normal);

    // 如果在阴影中则只有环境光
    if(isInShadow==0) {
        fColor.rgb *= phong.ambient + phong.diffuse + phong.specular;
    } else if(isInShadow==1.0) {
        fColor.rgb *= phong.ambient;  // only ambient
    }
}

注意这里我们使用了一个小 trick,因为天空盒不属于阴影和光照计算的物体,于是我们通过判断其是否在光源摄像机的视野中,就可以知道它是否是天空盒。判断的代码如下:

// 阴影映射
float shadowMapping(sampler2D tex, mat4 shadowVP, vec4 worldPos) {
	// 转换到光源坐标
	vec4 lightPos = shadowVP * worldPos;
	lightPos = vec4(lightPos.xyz/lightPos.w, 1.0);
	lightPos = lightPos*0.5 + 0.5;

    // 超出阴影贴图视野 -- 返回一个特殊值
    if(lightPos.x<0 || lightPos.x>1 || lightPos.y<0 || lightPos.y>1 || lightPos.z<0 || lightPos.z>1) {
        return 2.0;
    }

	// 计算shadowmapping
	float closestDepth = texture2D(tex, lightPos.xy).r;	// shadowmap中最近点的深度
	float currentDepth = lightPos.z;	// 当前点的深度
	float isInShadow = (currentDepth>closestDepth+0.005) ? (1.0) : (0.0);

	return isInShadow;
}

然后我们和 debug 着色器类似,也是传纹理,传 uniform,然后 draw call,使用 comopiste0 着色器进行绘制。现在来看 c++ 的代码:

display 函数

...

// 后处理阶段: composite0 着色器进行渲染
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glUseProgram(composite0);
glViewport(0, 0, windowWidth, windowHeight);

// 传递 zfar 和 znear 方便转线性深度
...

// 传 gcolor 纹理
...
// 传 gnormal 纹理
...
// 传 gworldpos 纹理
...
// 传 gdepth 纹理
...
// 传阴影深度纹理
...

// 传递矩阵: 转换到光源坐标的变换矩阵
...

// 传递光源位置
...
// 传递相机位置
...

// 绘制
screen.draw(composite0);
glEnable(GL_DEPTH_TEST);

最终效果:

在这里插入图片描述

你可能会说这和之前的有啥区别啊?是的,图像没有任何区别,但是我们使用了截然不同的策略进行渲染。在后续的博客中,会基于延迟渲染,进行一些令人赛艇的特效的编写。。。

篇幅有限,本来还想进行多光源渲染的对比,无奈篇幅有限,下次一定!

完整代码

着色器

shadow

顶点着色器代码(.vsh文件):

#version 330 core

layout (location = 0) in vec3 vPosition;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(vPosition, 1.0);
}

片元着色器代码(.fsh文件):

#version 330 core

void main()
{             
    // gl_FragDepth = gl_FragCoord.z;
}

skybox

顶点着色器代码(.vsh文件):

#version 330 core

// 顶点着色器输入
layout (location = 0) in vec3 vPosition;

out vec3 texcoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(vPosition, 1.0);  
	texcoord = vPosition;	// 坐标作为cubeMap采样坐标
}

片元着色器代码(.fsh文件):

#version 330 core

in vec3 texcoord;

uniform samplerCube skybox;

// 透视投影近截面 / 远截面
uniform float near;
uniform float far;

void main()
{
    gl_FragData[0] = textureCube(skybox, texcoord); // 写入 gcolor
    gl_FragData[1] = vec4(vec3(0), 0.0);            // 写入 gnormal
    gl_FragData[2] = vec4(texcoord*far*2, 1.0);     // 写入 gworldpos
}

gbuffer

顶点着色器代码(.vsh文件):

#version 330 core

// 顶点着色器输入
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec2 vTexcoord;
layout (location = 2) in vec3 vNormal;

out vec3 worldPos;
out vec2 texcoord;
out vec3 normal;

uniform mat4 model;         // 模型变换矩阵
uniform mat4 view;          // 模型变换矩阵
uniform mat4 projection;    // 模型变换矩阵

void main()
{
    gl_Position = projection * view * model * vec4(vPosition, 1.0);

    // 传递到片段着色器
    texcoord = vTexcoord;   
    worldPos = (model * vec4(vPosition, 1.0)).xyz;
    normal = (model * vec4(vNormal, 0.0)).xyz;
}

片元着色器代码(.fsh文件):

#version 330 core

in vec3 worldPos;   // 当前片元的世界坐标
in vec2 texcoord;   // 纹理坐标
in vec3 normal;     // 法向量

uniform sampler2D texture;

void main()
{
    gl_FragData[0] = texture2D(texture, texcoord);  // 写入 gcolor
    gl_FragData[1] = vec4(normalize(normal), 0.0);  // 写入 gnormal
    gl_FragData[2] = vec4(worldPos, 1.0);           // 写入 gworldpos
}

debug

顶点着色器代码(.vsh文件):

#version 330 core

// 顶点着色器输入
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec2 vTexcoord;

// 传给片元着色器的变量
out vec2 texcoord;

void main() 
{
	gl_Position = vec4(vPosition, 1.0);
	texcoord = vTexcoord;
}

片元着色器代码(.fsh文件):

#version 330 core

in vec2 texcoord;
out vec4 fColor;

// 纹理数据
uniform sampler2D gcolor;
uniform sampler2D gnormal;
uniform sampler2D gdepth;
uniform sampler2D gworldpos;
uniform sampler2D shadowtex;	// shadow纹理

uniform float near;
uniform float far;

// 屏幕深度转线性深度
float linearizeDepth(float depth, float near, float far) {
    return (2.0 * near) / (far + near - depth * (far - near));
}

void main()
{   
    // 屏幕左下显示 gcolor
    if(0<=texcoord.x && texcoord.x<=0.5 && 0<=texcoord.y && texcoord.y<=0.5)
    {
        vec2 coord = vec2(texcoord.x*2, texcoord.y*2);
        fColor = vec4(texture2D(gcolor, coord).rgb, 1); 
    }

    // 屏幕右下显示 gnormal
    if(0.5<=texcoord.x && texcoord.x<=1 && 0<=texcoord.y && texcoord.y<=0.5)
    {
        vec2 coord = vec2(texcoord.x*2-1, texcoord.y*2);
        fColor = vec4(texture2D(gnormal, coord).rgb, 1); 
    }

    // 屏幕左上显示 gdepth
    if(0<=texcoord.x && texcoord.x<=0.5 && 0.5<=texcoord.y && texcoord.y<=1)
    {
        vec2 coord = vec2(texcoord.x*2, texcoord.y*2-1);
        float d = linearizeDepth(texture2D(gdepth, coord).r, near, far);
        //d = texture2D(shadowtex, coord).r;
        fColor = vec4(vec3(d*0.5+0.5), 1); 
    }

    // 屏幕右上显示 gworldpos
    if(0.5<=texcoord.x && texcoord.x<=1 && 0.5<=texcoord.y && texcoord.y<=1)
    {
        vec2 coord = vec2(texcoord.x*2-1, texcoord.y*2-1);
        fColor = vec4(texture2D(gworldpos, coord).rgb, 1); 
    }
}

conposite0

顶点着色器代码(.vsh文件):

#version 330 core

// 顶点着色器输入
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec2 vTexcoord;

// 传给片元着色器的变量
out vec2 texcoord;

void main() 
{
	gl_Position = vec4(vPosition, 1.0);
	texcoord = vTexcoord;
}

片元着色器代码(.fsh文件):

#version 330 core

in vec2 texcoord;
out vec4 fColor;

// 纹理数据
uniform sampler2D gcolor;
uniform sampler2D gnormal;
uniform sampler2D gdepth;
uniform sampler2D gworldpos;
uniform sampler2D shadowtex;	// shadow纹理

// 透视投影近截面 / 远截面
uniform float near;
uniform float far;

uniform mat4 shadowVP;  // 转换到光源坐标的变换矩阵

uniform vec3 lightPos;  // 光源位置
uniform vec3 cameraPos; // 相机位置

// 屏幕深度转线性深度
float linearizeDepth(float depth, float near, float far) {
    return (2.0 * near) / (far + near - depth * (far - near));
}

// 阴影映射
float shadowMapping(sampler2D tex, mat4 shadowVP, vec4 worldPos) {
	// 转换到光源坐标
	vec4 lightPos = shadowVP * worldPos;
	lightPos = vec4(lightPos.xyz/lightPos.w, 1.0);
	lightPos = lightPos*0.5 + 0.5;

    // 超出阴影贴图视野 -- 返回一个特殊值
    if(lightPos.x<0 || lightPos.x>1 || lightPos.y<0 || lightPos.y>1 || lightPos.z<0 || lightPos.z>1) {
        return 2.0;
    }

	// 计算shadowmapping
	float closestDepth = texture2D(tex, lightPos.xy).r;	// shadowmap中最近点的深度
	float currentDepth = lightPos.z;	// 当前点的深度
	float isInShadow = (currentDepth>closestDepth+0.005) ? (1.0) : (0.0);

	return isInShadow;
}

// phong 光照计算
struct PhongStruct
{
    float ambient;
    float diffuse;
    float specular;
};
PhongStruct phong(vec3 worldPos, vec3 cameraPos, vec3 lightPos, vec3 normal)
{
    vec3 N = normalize(normal);
    vec3 V = normalize(worldPos - cameraPos);
    vec3 L = normalize(worldPos - lightPos);
    vec3 R = reflect(L, N);

    PhongStruct phong;
    phong.ambient = 0.3;
    phong.diffuse = max(dot(N, -L), 0);
    phong.specular = pow(max(dot(-R, V), 0), 50.0) * 1.1;

    return phong;
}

void main()
{   
    fColor.rgb = texture2D(gcolor, texcoord).rgb;
    vec3 worldPos = texture2D(gworldpos, texcoord).xyz;
    vec3 normal = texture2D(gnormal, texcoord).xyz;

    float isInShadow = shadowMapping(shadowtex, shadowVP, vec4(worldPos, 1.0));
    PhongStruct phong = phong(worldPos, cameraPos, lightPos, normal);

    // 如果在阴影中则只有环境光
    if(isInShadow==0) {
        fColor.rgb *= phong.ambient + phong.diffuse + phong.specular;
    } else if(isInShadow==1.0) {
        fColor.rgb *= phong.ambient;  // only ambient
    }
}

c++

// std c++
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <map>
#include <sstream>
#include <iostream>

// glew glut
#include <GL/glew.h>
#include <GL/freeglut.h>

// glm
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// SOIL
#include <SOIL2/SOIL2.h>

// assimp
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

// --------------------- end of include --------------------- //

class Mesh
{
public:
    // OpenGL 对象
    GLuint vao, vbo, ebo;
    GLuint diffuseTexture;  // 漫反射纹理

    // 顶点属性
    std::vector<glm::vec3> vertexPosition;
    std::vector<glm::vec2> vertexTexcoord;
    std::vector<glm::vec3> vertexNormal;

    // glDrawElements 函数的绘制索引
    std::vector<int> index;

    Mesh() {}
    void bindData()
    {
        // 创建顶点数组对象
        glGenVertexArrays(1, &vao); // 分配1个顶点数组对象
        glBindVertexArray(vao);  	// 绑定顶点数组对象

        // 创建并初始化顶点缓存对象 这里填NULL 先不传数据
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER,
            vertexPosition.size() * sizeof(glm::vec3) +
            vertexTexcoord.size() * sizeof(glm::vec2) +
            vertexNormal.size() * sizeof(glm::vec3),
            NULL, GL_STATIC_DRAW);

        // 传位置
        GLuint offset_position = 0;
        GLuint size_position = vertexPosition.size() * sizeof(glm::vec3);
        glBufferSubData(GL_ARRAY_BUFFER, offset_position, size_position, vertexPosition.data());
        glEnableVertexAttribArray(0);   // 着色器中 (layout = 0) 表示顶点位置
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_position));
        // 传纹理坐标
        GLuint offset_texcoord = size_position;
        GLuint size_texcoord = vertexTexcoord.size() * sizeof(glm::vec2);
        glBufferSubData(GL_ARRAY_BUFFER, offset_texcoord, size_texcoord, vertexTexcoord.data());
        glEnableVertexAttribArray(1);   // 着色器中 (layout = 1) 表示纹理坐标
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_texcoord));
        // 传法线
        GLuint offset_normal = size_position + size_texcoord;
        GLuint size_normal = vertexNormal.size() * sizeof(glm::vec3);
        glBufferSubData(GL_ARRAY_BUFFER, offset_normal, size_normal, vertexNormal.data());
        glEnableVertexAttribArray(2);   // 着色器中 (layout = 2) 表示法线
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_normal));

        // 传索引到 ebo
        glGenBuffers(1, &ebo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, index.size() * sizeof(GLuint), index.data(), GL_STATIC_DRAW);

        glBindVertexArray(0);
    }
    void draw(GLuint program)
    {
        glBindVertexArray(vao);

        // 传纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, diffuseTexture);
        glUniform1i(glGetUniformLocation(program, "texture"), 0);

        // 绘制
        glDrawElements(GL_TRIANGLES, this->index.size(), GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
    }
};

class Model
{
public:
    std::vector<Mesh> meshes;
    std::map<std::string, GLuint> textureMap;
    glm::vec3 translate = glm::vec3(0, 0, 0), rotate = glm::vec3(0, 0, 0), scale = glm::vec3(1, 1, 1);
    Model() {}
    void load(std::string filepath)
    {
        Assimp::Importer import;
        const aiScene* scene = import.ReadFile(filepath, aiProcess_Triangulate | aiProcess_FlipUVs);
        // 异常处理
        if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
        {
            std::cout << "读取模型出现错误: " << import.GetErrorString() << std::endl;
            exit(-1);
        }
        // 模型文件相对路径
        std::string rootPath = filepath.substr(0, filepath.find_last_of('/'));

        // 循环生成 mesh
        for (int i = 0; i < scene->mNumMeshes; i++)
        {
            // 引用当前mesh
            meshes.push_back(Mesh());
            Mesh& mesh = meshes.back();

            // 获取 assimp 的读取到的 aimesh 对象
            aiMesh* aimesh = scene->mMeshes[i];

            // 我们将数据传递给我们自定义的mesh
            for (int j = 0; j < aimesh->mNumVertices; j++)
            {
                // 顶点
                glm::vec3 vvv;
                vvv.x = aimesh->mVertices[j].x;
                vvv.y = aimesh->mVertices[j].y;
                vvv.z = aimesh->mVertices[j].z;
                mesh.vertexPosition.push_back(vvv);

                // 法线
                vvv.x = aimesh->mNormals[j].x;
                vvv.y = aimesh->mNormals[j].y;
                vvv.z = aimesh->mNormals[j].z;
                mesh.vertexNormal.push_back(vvv);

                // 纹理坐标: 如果存在则加入。assimp 默认可以有多个纹理坐标 我们取第一个(0)即可
                glm::vec2 vv(0, 0);
                if (aimesh->mTextureCoords[0])
                {
                    vv.x = aimesh->mTextureCoords[0][j].x;
                    vv.y = aimesh->mTextureCoords[0][j].y;
                }
                mesh.vertexTexcoord.push_back(vv);
            }

            // 如果有材质,那么传递材质
            if (aimesh->mMaterialIndex >= 0)
            {
                // 获取当前 aimesh 的材质对象
                aiMaterial* material = scene->mMaterials[aimesh->mMaterialIndex];

                // 获取 diffuse 贴图文件路径名称 我们只取1张贴图 故填 0 即可
                aiString aistr;
                material->GetTexture(aiTextureType_DIFFUSE, 0, &aistr);
                std::string texpath = aistr.C_Str();
                texpath = rootPath + '/' + texpath;   // 取相对路径

                // 如果没生成过纹理,那么生成它
                if (textureMap.find(texpath) == textureMap.end())
                {
                    // 生成纹理
                    GLuint tex;
                    glGenTextures(1, &tex);
                    glBindTexture(GL_TEXTURE_2D, tex);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
                    int textureWidth, textureHeight;
                    unsigned char* image = SOIL_load_image(texpath.c_str(), &textureWidth, &textureHeight, 0, SOIL_LOAD_RGB);
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);   // 生成纹理
                    delete[] image;

                    textureMap[texpath] = tex;
                }

                // 传递纹理
                mesh.diffuseTexture = textureMap[texpath];
            }

            // 传递面片索引
            for (GLuint j = 0; j < aimesh->mNumFaces; j++)
            {
                aiFace face = aimesh->mFaces[j];
                for (GLuint k = 0; k < face.mNumIndices; k++)
                {
                    mesh.index.push_back(face.mIndices[k]);
                }
            }

            mesh.bindData();
        }
    }
    void draw(GLuint program)
    {
        // 传模型矩阵
        glm::mat4 unit(    // 单位矩阵
            glm::vec4(1, 0, 0, 0),
            glm::vec4(0, 1, 0, 0),
            glm::vec4(0, 0, 1, 0),
            glm::vec4(0, 0, 0, 1)
        );
        glm::mat4 scale = glm::scale(unit, this->scale);
        glm::mat4 translate = glm::translate(unit, this->translate);

        glm::mat4 rotate = unit;    // 旋转
        rotate = glm::rotate(rotate, glm::radians(this->rotate.x), glm::vec3(1, 0, 0));
        rotate = glm::rotate(rotate, glm::radians(this->rotate.y), glm::vec3(0, 1, 0));
        rotate = glm::rotate(rotate, glm::radians(this->rotate.z), glm::vec3(0, 0, 1));

        // 模型变换矩阵
        glm::mat4 model = translate * rotate * scale;
        GLuint mlocation = glGetUniformLocation(program, "model");    // 名为model的uniform变量的位置索引
        glUniformMatrix4fv(mlocation, 1, GL_FALSE, glm::value_ptr(model));   // 列优先矩阵

        for (int i = 0; i < meshes.size(); i++)
        {
            meshes[i].draw(program);
        }
    }
};

class Camera
{
public:
    // 相机参数
    glm::vec3 position = glm::vec3(0, 0, 0);    // 位置
    glm::vec3 direction = glm::vec3(0, 0, -1);  // 视线方向
    glm::vec3 up = glm::vec3(0, 1, 0);          // 上向量,固定(0,1,0)不变
    float pitch = 0.0f, roll = 0.0f, yaw = 0.0f;    // 欧拉角
    float fovy = 70.0f, aspect = 1.0, zNear = 0.01, zFar = 100; // 透视投影参数
    float left = -1.0, right = 1.0, top = 1.0, bottom = -1.0; // 正交投影参数
    Camera() {}
    // 视图变换矩阵
    glm::mat4 getViewMatrix(bool useEulerAngle = true)
    {
        if (useEulerAngle)  // 使用欧拉角更新相机朝向
        {
            direction.x = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
            direction.y = sin(glm::radians(pitch));
            direction.z = -cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 相机看向z轴负方向
        }
        return glm::lookAt(position, position + direction, up);
    }
    // 投影矩阵
    glm::mat4 getProjectionMatrix(bool usePerspective = true)
    {
        if (usePerspective) // 透视投影
        {
            return glm::perspective(glm::radians(fovy), aspect, zNear, zFar);
        }
        return glm::ortho(left, right, bottom, top, zNear, zFar);
    }
};

// ---------------------------- end of class definition ---------------------------- //

// 模型
std::vector<Model> models;  // 场景
Model screen;   // 渲染一个四方形做屏幕
Model skybox;   // 渲染一个立方体用于立方体贴图绘制天空盒

// 着色器程序对象
GLuint program;
GLuint debugProgram;    // 调试用
GLuint shadowProgram;   // 绘制阴影的着色器程序对象
GLuint skyboxProgram;   // 天空盒绘制

// 纹理
GLuint skyboxTexture;   // 天空盒
GLuint shadowTexture;   // 阴影纹理

// 相机
Camera camera;          // 正常渲染
Camera shadowCamera;    // 从光源方向渲染

// 光源与阴影参数
int shadowMapResolution = 1024;             // 阴影贴图分辨率
GLuint shadowMapFBO;                        // 从光源方向进行渲染的帧缓冲

// glut与交互相关变量
int windowWidth = 512;  // 窗口宽
int windowHeight = 512; // 窗口高
bool keyboardState[1024];   // 键盘状态数组 keyboardState[x]==true 表示按下x键

// 延迟渲染阶段
GLuint gbufferProgram;
GLuint gbufferFBO;  // gbuffer 阶段帧缓冲
GLuint gcolor;      // 基本颜色纹理
GLuint gdepth;      // 深度纹理
GLuint gworldpos;   // 世界坐标纹理
GLuint gnormal;     // 法线纹理

// 后处理阶段
GLuint composite0;

// --------------- end of global variable definition --------------- //

// 读取文件并且返回一个长字符串表示文件内容
std::string readShaderFile(std::string filepath)
{
    std::string res, line;
    std::ifstream fin(filepath);
    if (!fin.is_open())
    {
        std::cout << "文件 " << filepath << " 打开失败" << std::endl;
        exit(-1);
    }
    while (std::getline(fin, line))
    {
        res += line + '\n';
    }
    fin.close();
    return res;
}

// 获取着色器对象
GLuint getShaderProgram(std::string fshader, std::string vshader)
{
    // 读取shader源文件
    std::string vSource = readShaderFile(vshader);
    std::string fSource = readShaderFile(fshader);
    const char* vpointer = vSource.c_str();
    const char* fpointer = fSource.c_str();

    // 容错
    GLint success;
    GLchar infoLog[512];

    // 创建并编译顶点着色器
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, (const GLchar**)(&vpointer), NULL);
    glCompileShader(vertexShader);
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);   // 错误检测
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "顶点着色器 " + vshader + " 编译错误\n" << infoLog << std::endl;
        exit(-1);
    }

    // 创建并且编译片段着色器
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, (const GLchar**)(&fpointer), NULL);
    glCompileShader(fragmentShader);
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);   // 错误检测
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "片段着色器 " + fshader + " 编译错误\n" << infoLog << std::endl;
        exit(-1);
    }

    // 链接两个着色器到program对象
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    // 删除着色器对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    return shaderProgram;
}

// 鼠标滚轮函数
void mouseWheel(int wheel, int direction, int x, int y)
{
    // zFar += 1 * direction * 0.1;
    glutPostRedisplay();    // 重绘
}

// 鼠标运动函数
void mouse(int x, int y)
{
    // 调整旋转
    camera.yaw += 35 * (x - float(windowWidth) / 2.0) / windowWidth;
    camera.yaw = glm::mod(camera.yaw + 180.0f, 360.0f) - 180.0f;    // 取模范围 -180 ~ 180

    camera.pitch += -35 * (y - float(windowHeight) / 2.0) / windowHeight;
    camera.pitch = glm::clamp(camera.pitch, -89.0f, 89.0f);

    glutWarpPointer(windowWidth / 2.0, windowHeight / 2.0);
    glutPostRedisplay();    // 重绘
}

// 键盘回调函数
void keyboardDown(unsigned char key, int x, int y)
{
    keyboardState[key] = true;
}
void keyboardDownSpecial(int key, int x, int y)
{
    keyboardState[key] = true;
}
void keyboardUp(unsigned char key, int x, int y)
{
    keyboardState[key] = false;
}
void keyboardUpSpecial(int key, int x, int y)
{
    keyboardState[key] = false;
}
// 根据键盘状态判断移动
void move()
{
    float cameraSpeed = 0.035f;
    // 相机控制
    if (keyboardState['w']) camera.position += cameraSpeed * camera.direction;
    if (keyboardState['s']) camera.position -= cameraSpeed * camera.direction;
    if (keyboardState['a']) camera.position -= cameraSpeed * glm::normalize(glm::cross(camera.direction, camera.up));
    if (keyboardState['d']) camera.position += cameraSpeed * glm::normalize(glm::cross(camera.direction, camera.up));
    if (keyboardState[GLUT_KEY_CTRL_L]) camera.position.y -= cameraSpeed;
    if (keyboardState[' ']) camera.position.y += cameraSpeed;
    // 光源位置控制
    if (keyboardState['i']) shadowCamera.position.x += cameraSpeed;
    if (keyboardState['I']) shadowCamera.position.x -= cameraSpeed;
    if (keyboardState['o']) shadowCamera.position.y += cameraSpeed;
    if (keyboardState['O']) shadowCamera.position.y -= cameraSpeed;
    glutPostRedisplay();    // 重绘
}

GLuint loadCubemap(std::vector<const GLchar*> faces)
{
    GLuint textureID;
    glGenTextures(1, &textureID);
    glActiveTexture(GL_TEXTURE0);

    int width, height;
    unsigned char* image;

    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
    for (GLuint i = 0; i < faces.size(); i++)
    {
        image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);
        glTexImage2D(
            GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0,
            GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image
        );
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    return textureID;
}

// 初始化
void init()
{
    // 生成着色器程序对象
    gbufferProgram = getShaderProgram("shaders/gbuffer.fsh", "shaders/gbuffer.vsh");
    shadowProgram = getShaderProgram("shaders/shadow.fsh", "shaders/shadow.vsh");
    debugProgram = getShaderProgram("shaders/debug.fsh", "shaders/debug.vsh");
    skyboxProgram = getShaderProgram("shaders/skybox.fsh", "shaders/skybox.vsh");
    composite0 = getShaderProgram("shaders/composite0.fsh", "shaders/composite0.vsh");

    // ------------------------------------------------------------------------ // 

    // 读取 obj 模型
    Model tree1 = Model();
    tree1.translate = glm::vec3(2.5, 0, 2);
    tree1.scale = glm::vec3(0.0025, 0.0025, 0.0025);
    tree1.load("models/tree/tree02.obj");
    models.push_back(tree1);

    Model tree2 = Model();
    tree2.translate = glm::vec3(10, 0, 7);
    tree2.scale = glm::vec3(0.0015, 0.0015, 0.0015);
    tree2.load("models/tree/tree02.obj");
    models.push_back(tree2);

    Model plane = Model();
    plane.translate = glm::vec3(0, -1.1, 0);
    plane.scale = glm::vec3(10, 10, 10);
    plane.rotate = glm::vec3(0, 0, 0);
    plane.load("models/plane/plane.obj");
    models.push_back(plane);

    // 光源位置标志物
    Model vlight = Model();
    vlight.translate = glm::vec3(1, 0, -1);
    vlight.rotate = glm::vec3(-90, 0, 0);
    vlight.scale = glm::vec3(0.03, 0.03, 0.03);
    vlight.load("models/duck/12248_Bird_v1_L2.obj");
    models.push_back(vlight);

    // ------------------------------------------------------------------------ // 

    // 生成一个四方形做荧幕 -- 用以显示纹理中的数据
    Mesh msquare;
    msquare.vertexPosition = { glm::vec3(-1, -1, 0), glm::vec3(1, -1, 0), glm::vec3(-1, 1, 0), glm::vec3(1, 1, 0) };
    msquare.vertexTexcoord = { glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(0, 1), glm::vec2(1, 1) };
    msquare.index = { 0,1,2,2,1,3 };
    msquare.bindData();
    screen.meshes.push_back(msquare);

    // ------------------------------------------------------------------------ //

    // 生成一个立方体做天空盒的 “画布”
    Mesh cube;
    cube.vertexPosition = { // 立方体的 8 个顶点
        glm::vec3(-1, -1, -1),glm::vec3(1, -1, -1),glm::vec3(-1, 1, -1),glm::vec3(1, 1, -1),
        glm::vec3(-1, -1, 1),glm::vec3(1, -1, 1),glm::vec3(-1, 1, 1),glm::vec3(1, 1, 1)
    };
    cube.index = { 0,3,1,0,2,3,1,5,4,1,4,0,4,2,0,4,6,2,5,6,4,5,7,6,2,6,7,2,7,3,1,7,5,1,3,7 };
    cube.bindData();
    skybox.meshes.push_back(cube);

    // 加载立方体贴图
    std::vector<const GLchar*> faces;
    faces.push_back("skybox/right.jpg");
    faces.push_back("skybox/left.jpg");
    faces.push_back("skybox/top.jpg");
    faces.push_back("skybox/bottom.jpg");
    faces.push_back("skybox/back.jpg");
    faces.push_back("skybox/front.jpg");
    /*
    faces.push_back("skybox/DOOM16RT.png");
    faces.push_back("skybox/DOOM16LF.png");
    faces.push_back("skybox/DOOM16UP.png");
    faces.push_back("skybox/DOOM16DN.png");
    faces.push_back("skybox/DOOM16FT.png");
    faces.push_back("skybox/DOOM16BK.png");
    */
    skyboxTexture = loadCubemap(faces);

    // ------------------------------------------------------------------------ // 

    // 正交投影参数配置 -- 视界体范围 -- 调整到场景一般大小即可
    shadowCamera.left = -30;
    shadowCamera.right = 30;
    shadowCamera.bottom = -30;
    shadowCamera.top = 30;
    shadowCamera.position = glm::vec3(0, 4, 15);

    // 创建shadow帧缓冲
    glGenFramebuffers(1, &shadowMapFBO);
    // 创建阴影纹理
    glGenTextures(1, &shadowTexture);
    glBindTexture(GL_TEXTURE_2D, shadowTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapResolution, shadowMapResolution, 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_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // 将阴影纹理绑定到 shadowMapFBO 帧缓冲
    glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowTexture, 0);
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // ------------------------------------------------------------------------ // 

    // 创建 gubffer 帧缓冲
    glGenFramebuffers(1, &gbufferFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, gbufferFBO);

    // 创建颜色纹理
    glGenTextures(1, &gcolor);
    glBindTexture(GL_TEXTURE_2D, gcolor);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_RGBA, 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_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // 将颜色纹理绑定到 0 号颜色附件
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gcolor, 0);

    // 创建法线纹理
    glGenTextures(1, &gnormal);
    glBindTexture(GL_TEXTURE_2D, gnormal);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_RGBA, 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_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // 将法线纹理绑定到 1 号颜色附件
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gnormal, 0);

    // 创建世界坐标纹理
    glGenTextures(1, &gworldpos);
    glBindTexture(GL_TEXTURE_2D, gworldpos);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_RGBA, 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_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // 将世界坐标纹理绑定到 2 号颜色附件
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gworldpos, 0);

    // 创建深度纹理
    glGenTextures(1, &gdepth);
    glBindTexture(GL_TEXTURE_2D, gdepth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, windowWidth, windowHeight, 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_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    // 将深度纹理绑定到深度附件
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, gdepth, 0);

    // 指定附件索引
    GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
    glDrawBuffers(3, attachments);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);	// 解绑

    // ------------------------------------------------------------------------ // 

    glEnable(GL_DEPTH_TEST);  // 开启深度测试
    glClearColor(1.0, 1.0, 1.0, 1.0);   // 背景颜色
}

// 显示回调函数
void display()
{
    move(); // 移动控制 -- 控制相机位置

    // 最后一个物体作为光源位置的标志物
    models.back().translate = shadowCamera.position + glm::vec3(0, 0, 2);

    // ------------------------------------------------------------------------ // 

    // 从光源方向进行渲染
    glUseProgram(shadowProgram);
    glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFBO);
    glClear(GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, shadowMapResolution, shadowMapResolution);

    // 光源看向世界坐标原点
    shadowCamera.direction = glm::normalize(glm::vec3(0, 0, 0) - shadowCamera.position);
    // 传视图矩阵
    glUniformMatrix4fv(glGetUniformLocation(shadowProgram, "view"), 1, GL_FALSE, glm::value_ptr(shadowCamera.getViewMatrix(false)));
    // 传投影矩阵
    glUniformMatrix4fv(glGetUniformLocation(shadowProgram, "projection"), 1, GL_FALSE, glm::value_ptr(shadowCamera.getProjectionMatrix(false)));

    // 从光源方向进行绘制
    for (auto m : models)
    {
        m.draw(shadowProgram);
    }

    // ------------------------------------------------------------------------ // 

    // 绘制天空盒 -- 输出到 gbuffer 阶段的 3 张纹理中
    glUseProgram(skyboxProgram);
    glBindFramebuffer(GL_FRAMEBUFFER, gbufferFBO);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, windowWidth, windowHeight);

    // 传视图,投影矩阵
    glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "view"), 1, GL_FALSE, glm::value_ptr(camera.getViewMatrix()));
    glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "projection"), 1, GL_FALSE, glm::value_ptr(camera.getProjectionMatrix()));

    // 传cubemap纹理
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
    glUniform1i(glGetUniformLocation(skyboxProgram, "skybox"), 1);

    // 传递 zfar 和 znear 方便让天空盒的坐标置于最大视距
    glUniform1f(glGetUniformLocation(skyboxProgram, "near"), camera.zNear);
    glUniform1f(glGetUniformLocation(skyboxProgram, "far"), camera.zFar);

    // 立方体永远跟随相机
    skybox.translate = camera.position;

    glDepthMask(GL_FALSE);
    skybox.draw(skyboxProgram);
    glDepthMask(GL_TRUE);

    // ------------------------------------------------------------------------ // 

    // 正常绘制 -- 输出到 gbuffer 阶段的 3 张纹理中
    glUseProgram(gbufferProgram);

    // 传视图矩阵
    glUniformMatrix4fv(glGetUniformLocation(gbufferProgram, "view"), 1, GL_FALSE, glm::value_ptr(camera.getViewMatrix()));
    // 传投影矩阵
    glUniformMatrix4fv(glGetUniformLocation(gbufferProgram, "projection"), 1, GL_FALSE, glm::value_ptr(camera.getProjectionMatrix()));

    // 正常绘制
    for (auto m : models)
    {
        m.draw(gbufferProgram);
    }

    // ------------------------------------------------------------------------ // 

    // 后处理阶段: composite0 着色器进行渲染
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDisable(GL_DEPTH_TEST);
    glUseProgram(composite0);
    glViewport(0, 0, windowWidth, windowHeight);

    // 传递 zfar 和 znear 方便转线性深度
    glUniform1f(glGetUniformLocation(composite0, "near"), camera.zNear);
    glUniform1f(glGetUniformLocation(composite0, "far"), camera.zFar);

    // 传 gcolor 纹理
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, gcolor);
    glUniform1i(glGetUniformLocation(composite0, "gcolor"), 1);
    // 传 gnormal 纹理
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, gnormal);
    glUniform1i(glGetUniformLocation(composite0, "gnormal"), 2);
    // 传 gworldpos 纹理
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, gworldpos);
    glUniform1i(glGetUniformLocation(composite0, "gworldpos"), 3);
    // 传 gdepth 纹理
    glActiveTexture(GL_TEXTURE4);
    glBindTexture(GL_TEXTURE_2D, gdepth);
    glUniform1i(glGetUniformLocation(composite0, "gdepth"), 4);
    // 传阴影深度纹理
    glActiveTexture(GL_TEXTURE5);
    glBindTexture(GL_TEXTURE_2D, shadowTexture);
    glUniform1i(glGetUniformLocation(composite0, "shadowtex"), 5);

    // 传递矩阵: 转换到光源坐标的变换矩阵
    glm::mat4 shadowVP = shadowCamera.getProjectionMatrix(false) * shadowCamera.getViewMatrix(false);
    glUniformMatrix4fv(glGetUniformLocation(composite0, "shadowVP"), 1, GL_FALSE, glm::value_ptr(shadowVP));

    // 传递光源位置
    glUniform3fv(glGetUniformLocation(composite0, "lightPos"), 1, glm::value_ptr(shadowCamera.position));
    // 传递相机位置
    glUniform3fv(glGetUniformLocation(composite0, "cameraPos"), 1, glm::value_ptr(camera.position));

    // 绘制
    screen.draw(composite0);
    glEnable(GL_DEPTH_TEST);

    // ------------------------------------------------------------------------ // 
    
    /*
    // debug着色器输出一个四方形以显示纹理中的数据
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDisable(GL_DEPTH_TEST);   // 需要取消深度测试以保证其覆盖在原画面上
    glUseProgram(debugProgram);
    glViewport(0, 0, windowWidth, windowHeight);

    // 传递 zfar 和 znear 方便转线性深度
    glUniform1f(glGetUniformLocation(debugProgram, "near"), camera.zNear);
    glUniform1f(glGetUniformLocation(debugProgram, "far"), camera.zFar);

    // 传 gcolor 纹理
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, gcolor);
    glUniform1i(glGetUniformLocation(debugProgram, "gcolor"), 1);
    // 传 gnormal 纹理
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, gnormal);
    glUniform1i(glGetUniformLocation(debugProgram, "gnormal"), 2);
    // 传 gworldpos 纹理
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, gworldpos);
    glUniform1i(glGetUniformLocation(debugProgram, "gworldpos"), 3);
    // 传 gdepth 纹理
    glActiveTexture(GL_TEXTURE4);
    glBindTexture(GL_TEXTURE_2D, gdepth);
    glUniform1i(glGetUniformLocation(debugProgram, "gdepth"), 4);
    // 传阴影深度纹理
    glActiveTexture(GL_TEXTURE5);
    glBindTexture(GL_TEXTURE_2D, shadowTexture);
    glUniform1i(glGetUniformLocation(debugProgram, "shadowtex"), 5);

    // 绘制
    screen.draw(debugProgram);
    glEnable(GL_DEPTH_TEST);
    */
    
    // ------------------------------------------------------------------------ // 

    glutSwapBuffers();                  // 交换缓冲区
}

// -------------------------------- main -------------------------------- //

int main(int argc, char** argv)
{
    glutInit(&argc, argv);              // glut初始化
    glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(windowWidth, windowHeight);// 窗口大小
    glutCreateWindow("10 - deferred render"); // 创建OpenGL上下文

#ifdef __APPLE__
#else
    glewInit();
#endif

    init();

    // 绑定鼠标移动函数 -- 
    //glutMotionFunc(mouse);  // 左键按下并且移动
    glutPassiveMotionFunc(mouse);   // 鼠标直接移动
    //glutMouseWheelFunc(mouseWheel); // 滚轮缩放

    // 绑定键盘函数
    glutKeyboardFunc(keyboardDown);
    glutSpecialFunc(keyboardDownSpecial);
    glutKeyboardUpFunc(keyboardUp);
    glutSpecialUpFunc(keyboardUpSpecial);

    glutDisplayFunc(display);           // 设置显示回调函数 -- 每帧执行
    glutMainLoop();                     // 进入主循环

    return 0;
}
  • 5
    点赞
  • 2
    评论
  • 4
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值