opengl模仿portal传送门效果

opengl模仿portal传送门效果

  • portal
  • 大体思路
  • 虚拟相机
  • 模板测试
  • 斜视锥体
  • 结果

最近了解了下计算机图形学和opengl的使用。CG方向本身比较有意思,相信未来随着VR和AR新设备的发展热度一定能够上升。可惜我接触时间不长,所以用这个项目作为最后的记录,结束我的CG学习阶段。

portal

opengl模仿portal传送门效果_第1张图片
portal是我玩过的优秀解密游戏之一,其中最为人称道的就是它的传送门机制以及其中的物理计算。这次就记录如何使用opengl实现他的传送门图形效果。

大体思路

实现方法应该是不唯一的。这里记录使用模板测试和虚拟相机实现的方法。
模板测试:把画面分割为门内和门外两部分。
虚拟相机:使用虚拟相机渲染门内的场景,使用原始相机(就是玩家位置的相机)渲染门外的场景。
最后把实现过程写为递归函数,实现两个门恰好相对视线无线延伸的效果。
opengl模仿portal传送门效果_第2张图片

虚拟相机

如果称局部坐标变换到世界坐标使用的是模型变换矩阵model,从世界坐标变换到相机坐标使用的相机变换矩阵view。那么相机位置所带来的影响表现为渲染过程中相机变换矩阵view的不同。我们使用两个传送门的model矩阵将原始的view矩阵变换虚拟相机位置对应新的view‘矩阵,使用新的view’矩阵对门内的场景进行渲染。
opengl模仿portal传送门效果_第3张图片
如上图所示,本来从蓝色相机位置渲染门外场景,我们要计算绿色虚拟相机位置对应的view,这个位置就如同人站在另一个传送门能看到的内容。
约定矩阵对坐标进行右乘,即perspectiveviewmodel*vertex_pos的顺序。那么新的相机变换矩阵计算公式如下在这里插入图片描述
因为玩家在这一次看的是门的正面,在另外一侧就如同从们的背面看过去,所以我们要在中间额外乘一个旋转矩阵。代码看起来就是这个样子。

glm::mat4 destView = viewMat * portal.model
			* glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 1.0f, 0.0f))
			* glm::inverse(portal.destPortal->model);

模板测试

伪代码:
opengl模仿portal传送门效果_第4张图片
模板缓冲记录数值与递归的层数直接作为模板测试条件,按由内到外的顺序进行渲染,开始渲染前要更新门的深度值,避免门后的顶点对门内已经渲染的物体发生遮挡。

斜视锥体

当传送门位于墙壁上时,我们计算得到新的视图变换矩阵,虚拟相机的位置是可能位于一个墙壁的后面的。这时进行渲染可能渲染的结果会被墙壁完全遮挡。为了解决这个问题我们就要使用斜视锥体方法更改近平面位置为门所在平面,让渲染的结果不再包括门后的内容。
opengl模仿portal传送门效果_第5张图片
斜视锥体的理论推导看这里
具体的就是更改原始投影矩阵的第三列,代码如下:

glm::mat4 const clippedProjMat(glm::mat4 const &viewMat, glm::mat4 projMat) const
	{
		glm::vec3 Normal = glm::normalize(model * glm::vec4(0, 0, 1, 0));
		glm::vec4 clipPlane(Normal,glm::dot(Normal, d_position));//四维向量表示门平面的解析式
		clipPlane = glm::inverse(glm::transpose(viewMat)) * clipPlane;
		glm::vec4 q;
		q.x = (glm::sign(clipPlane.x) + projMat[2][0]) / projMat[0][0];
		q.y = (glm::sign(clipPlane.y) + projMat[2][1]) / projMat[1][1];
		q.z = -1.0F;
		q.w = (1.0F + projMat[2][2]) / projMat[3][2];
		glm::vec4 c = clipPlane * (2.0f / glm::dot(clipPlane, q));
		projMat[0][2] = c.x;
		projMat[1][2] = c.y;
		projMat[2][2] = c.z + 1.0F;
		projMat[3][2] = c.w;
		return projMat;
	}

结果

主体代码如下:

//@参数 当前看向的门 当前相机位置对应的view矩阵 投影阵
void drawRecursivePortals(Portal &portal, glm::mat4 const &viewMat, glm::mat4 const &projMat, size_t maxRecursionLevel, size_t recursionLevel)
{
		// 禁止颜色缓冲和深度缓冲
		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
		glDepthMask(GL_FALSE);
		glDisable(GL_DEPTH_TEST);
		//启用模板测试
		glEnable(GL_STENCIL_TEST);
		//模板测试不通过时加1
		glStencilFunc(GL_NOTEQUAL, recursionLevel, 0xFF);
		glStencilOp(GL_INCR, GL_KEEP, GL_KEEP);
		glStencilMask(0xFF);
		//对门框范围进行渲染 更新区域模板数值
		portal.draw(viewMat, projMat);

		//计算新的view矩阵
		glm::mat4 destView = viewMat * portal.model
			* glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 1.0f, 0.0f))
			* glm::inverse(portal.destPortal->model);
		//最后一层门 渲染景物
		if (recursionLevel == maxRecursionLevel)
		{
			//打开颜色缓冲深度缓冲
			glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
			glDepthMask(GL_TRUE);
			glClear(GL_DEPTH_BUFFER_BIT);
			glEnable(GL_DEPTH_TEST);
			//模板测试检验但不可写
			glEnable(GL_STENCIL_TEST);
			glStencilMask(0x00);
			//等于模板数值的通过测试
			glStencilFunc(GL_EQUAL, recursionLevel + 1, 0xFF);
			//渲染所有普通景物
			drawNonPortals(destView, portal.clippedProjMat(destView, projMat));
		}
		else
		{
			//更新递归层数 用新的投影矩阵和相机矩阵进行渲染
			drawRecursivePortals(portal, destView, portal.clippedProjMat(destView, projMat), maxRecursionLevel, recursionLevel + 1);
		}

		//关闭深度测试和颜色缓冲 重新把模板缓冲数值减一
		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
		glDepthMask(GL_FALSE);
		glEnable(GL_STENCIL_TEST);
		glStencilMask(0xFF);

		glStencilFunc(GL_NOTEQUAL, recursionLevel + 1, 0xFF);
		glStencilOp(GL_DECR, GL_KEEP, GL_KEEP);

		portal.draw(viewMat, projMat);

	if (recursionLevel > 0) {
		//渲染景物
		// 关闭模板测试和颜色缓冲 在门的位置记录门的深度
		glDisable(GL_STENCIL_TEST);
		glStencilMask(0x00);
		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

		glEnable(GL_DEPTH_TEST);
		glDepthMask(GL_TRUE);
		glDepthFunc(GL_ALWAYS);
		glClear(GL_DEPTH_BUFFER_BIT);

		portal.draw(viewMat, projMat);

		glDepthFunc(GL_LESS);


		//打开模板测试 的呢关于模板缓冲数值的通过测试 渲染这一层门内的内容
		glEnable(GL_STENCIL_TEST);
		glStencilMask(0x00);

		glStencilFunc(GL_EQUAL, recursionLevel, 0xFF);

		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		glDepthMask(GL_TRUE);

		glEnable(GL_DEPTH_TEST);

		drawNonPortals(viewMat, projMat);
	}
	
}

opengl模仿portal传送门效果_第6张图片
opengl模仿portal传送门效果_第7张图片

参考资料
斜视锥体深度投影与裁剪
learnopengl中文版

你可能感兴趣的:(OpenGl/Opengles)