gldemo
天空渲染

渲染顺序

天空和其他有实体的物体不同,是位于无限远处的,且充满整个屏幕。任何物体都会挡住天空,任何物体都不会被天空挡住。因此我们把天空的渲染放在整个渲染管线的最前端,在渲染其他物体之前首先绘制天空,且关闭深度缓冲的写入,避免后续深度测试时的遮挡。

tech-skybox-00.png

SkyboxRenderer.cpp:

void SkyboxRenderer::onRender(OpenGLFunctions * gl, glm::mat4 projection, glm::mat4 view, glm::mat4 model)
{
if (!meshInitialized) {
m_vertexBuffer = new QOpenGLBuffer();
float buf[8] = { -1, -1, -1, 1, 1, -1, 1, 1 };
m_vertexBuffer->create();
m_vertexBuffer->bind();
m_vertexBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw);
m_vertexBuffer->allocate(buf, 8 * sizeof(float));
meshInitialized = true;
}
assert(shader != NULL);
// 绑定shader
shader->bind();
m_vertexBuffer->bind();
// offset=0 size=2 stride=default
shader->setAttributeBuffer(0, GL_FLOAT, 0, 2, 0);
// 准备shader中的矩阵
glm::mat4 projInv = glm::inverse(projection);
glm::mat4 viewInv = glm::inverse(view);
gl->glUniformMatrix4fv(shader->uniformLocation("MAT_PROJ_INV"), 1, GL_FALSE, glm::value_ptr(projInv));
gl->glUniformMatrix4fv(shader->uniformLocation("MAT_VIEW_INV"), 1, GL_FALSE, glm::value_ptr(viewInv));
// no depth
gl->glDepthMask(GL_FALSE);
gl->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
gl->glDepthMask(GL_TRUE);
// 释放
m_vertexBuffer->release();
shader->release();
}

天空模型

绘制天空时,实际上渲染的是一个完全覆盖视场范围的长方形面片(视口坐标为 \((\pm1, \pm1)\)),在顶点着色器中经过反变换计算世界坐标下的方向向量,在片元着色器中按照高度角分段插值得到像素颜色。

反变换时,从视口坐标变换到相机坐标时,补齐齐次坐标的w分量为1,因为此时需要考虑视口变换的平移分量,将屏幕上的点变换到相机坐标中虚拟屏幕上的点。

\[ \text{camPosDir} = M_{\text{PROJ}}^{-1} \cdot [x, y, 0.0, 1.0]^T \]

从相机坐标变换到世界坐标时,补齐齐次坐标w分量为0,因为此时的向量表示相机视线的方向向量,与相机的平移无关。

\[ \text{worldDir} = M_{\text{VIEW}}^{-1} \cdot [x_{cam}, y_{cam}, z_{cam}, 0.0]^T \]

skybox.vert:

void main()
{
gl_Position = vec4(xy.x, xy.y, 0.999, 1.0); // @far clip plane
// compute world direction
vec3 camPosDir = (MAT_PROJ_INV * vec4(xy.x, xy.y, 0.0, 1.0)).xyz;
worldDir = (MAT_VIEW_INV * vec4(camPosDir, 0.0)).xyz;
}

昼夜更替

昼夜更替是通过将天空各段的颜色值在时间维度上进行插值实现的。设定好各个关键时间点的天空颜色之后,插值得到待求时间的天空颜色,并通过uniform传入天空的着色器中。

SkyboxRenderer.cpp:

temptime = timetime / 10.0;
//夜晚-黎明
if (temptime <= 6) {
skycolorchange = glm::vec3(0, 0.0, 0.0) + glm::vec3(0, temptime * 0.0092, temptime * 0.0188);
sladderchange = glm::vec3(0.0, 0.0, 0.25) + glm::vec3(temptime * 0.0125, temptime * 0.0167, temptime * 0.0271);
gladderchange = glm::vec3(0.0, 0.0, 0.3) + glm::vec3(temptime * 0.0443, temptime * 0.05, temptime * 0.0390);
groundcolorchange = glm::vec3(0.2, 0.2, 0.2) + glm::vec3(temptime * 0.0083, temptime * 0.0083, temptime * 0.0083);
}
//日出
else if (temptime <= 8) {
skycolorchange = ...
...
}
//天亮
else if (temptime <= 10) {
...
}
//白天
else if (temptime <= 16) {
...
}
//日落
else if (temptime <= 18) {
...
}
//天黑
else if (temptime <= 20) {
...
}
//夜晚
else {
...
}