c++和opengl实现gis_自制游戏引擎 - GameMachine - 渲染流水线 (OpenGL)

GameMachine可以单独编译DirectX11渲染模块:

Froser:自制游戏引擎 - GameMachine - 渲染流水线 (DirectX11)​zhuanlan.zhihu.com

默认情况下,GameMachine自身是实现了OpenGL的渲染的。

流水线是指图元从被构造到被渲染所要经历的过程。每一个阶段在运行时(渲染时)都有严格的顺序,但是对它们进行设置并不一定要遵循上面的顺序。

和之前文章所说的DirectX11一样,OpenGL 3.3以上的版本也有一个固定的流水线:

e078008416164fda5fff99f4c989f19f.png

接下来,我们大致参照GameMachine源码来讲解各个着色器设置阶段。我们将对比DirectX11的流水线来进行介绍。

1. Vertex Specification

类似于DirectX11的IA阶段。我们需要准备好VAO(Vertex Array Object)、VBO(Vertex Buffer Object),进行布局的定义,并且准备好缓存。

GMModelDataProxy类是用于将这些顶点数据传输给GPU的代理类,对DirectX11和OpenGL有各自的实现。我们节选一段OpenGL下的代码:

 void GMGLModelDataProxy::transfer()
{
	D(d);
	D_BASE(db, GMModelDataProxy);
	if (d->inited)
		return;

	prepareParentModel();

	GMModel* model = getModel();
	if (!model->isNeedTransfer())
		return;

	prepareTangentSpace();

	GMModelBufferData bufferData;
	Vector<GMVertex> packedVertices;
	// 把数据打入顶点数组
	packVertices(packedVertices);

	GMsize_t verticeCount = 0;

	GLuint vao;

	GMGLBeginGetErrorsAndCheck();
	glGenVertexArrays(1, &vao);
	bufferData.arrayId = vao;
	glBindVertexArray(vao);

	GLuint vbo;
	glGenBuffers(1, &vbo);
	bufferData.vertexBufferId = vbo;

	GLenum usage = model->getUsageHint() == GMUsageHint::StaticDraw ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW;
	glBindBuffer(GL_ARRAY_BUFFER, bufferData.vertexBufferId);
	glBufferData(GL_ARRAY_BUFFER, sizeof(GMVertex) * packedVertices.size(), packedVertices.data(), usage);

	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Position),	GMVertex::PositionDimension,	GL_FLOAT, GL_FALSE, sizeof(GMVertex), 0);
	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Normal),		GMVertex::NormalDimension,		GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(3));
	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Texcoord),	GMVertex::TexcoordDimension,	GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(6));
	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Tangent),		GMVertex::TangentDimension,		GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(8));
	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Bitangent),	GMVertex::BitangentDimension,	GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(11));
	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Lightmap),	GMVertex::LightmapDimension,	GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(14));
	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Color),		GMVertex::ColorDimension,		GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(16));
	if (glVertexAttribIPointer)
		glVertexAttribIPointer(gmVertexIndex(GMVertexDataType::BoneIds),GMVertex::BoneIDsDimension,		GL_INT,				sizeof(GMVertex), BIT32_OFFSET(20));
	else // 可能某些ES版本不支持
		glVertexAttribPointer(gmVertexIndex(GMVertexDataType::BoneIds),	GMVertex::BoneIDsDimension,		GL_INT,   GL_FALSE,	sizeof(GMVertex), BIT32_OFFSET(20));
	glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Weights),		GMVertex::WeightsDimension,		GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(24));

	GM_FOREACH_ENUM_CLASS(type, GMVertexDataType::Position, GMVertexDataType::EndOfVertexDataType)
	{
		glEnableVertexAttribArray(gmVertexIndex(type));
	}

	if (model->getDrawMode() == GMModelDrawMode::Index)
	{
		Vector<GMuint32> packedIndices;
		// 把数据打入顶点数组
		packIndices(packedIndices);

		glGenBuffers(1, &bufferData.indexBufferId);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferData.indexBufferId);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GMuint32) * packedIndices.size(), packedIndices.data(), GL_STATIC_DRAW);

		verticeCount = packedIndices.size();
	}
	else
	{
		verticeCount = packedVertices.size();
	}

	glBindVertexArray(0);
	GMGLEndGetErrorsAndCheck();

	for (auto& part : model->getParts())
	{
		part->clear();
	}

	GMModelBuffer* modelBuffer = new GMModelBuffer();
	modelBuffer->setData(bufferData);

	model->setVerticesCount(verticeCount);
	model->setModelBuffer(modelBuffer);
	modelBuffer->releaseRef();
	d->inited = true;
	model->doNotTransferAnymore();
}

可以看到,上述代码生成了VAO、VBO,并且定义了顶点布局,并且通过glBind*函数进行了绑定。

2. Vertex Shader (VS)

同DirectX11 VS阶段。

3. Tessellation

类似DirectX11 Hull Shader阶段。

4. Geometry Shader

类似DirectX11 GS阶段。

5. Vertex Post-Processing

顶点的后处理主要做两件事情:

  1. 变换反馈(Transform Feedback),将VS阶段输出的顶点坐标反馈回来,即获取VS阶段变换后的坐标。这个可以用来实现一些丰富的粒子效果。不过GameMachine没有使用此阶段。
  2. 裁剪。通过设置裁剪距离gl_ClipDistance,OpenGL会对不在距离范围内的顶点进行裁剪。

6. Primitive Assembly

图元装配用于处理裁减面(Cull Face)等。

7. Rasterization

光栅化阶段同DirectX11光栅化阶段。

8. Fragment Shader

同DirectX11 PS阶段。

9. Per-Sample Processing

同DirectX11 OM阶段,即进行深度测试、模板测试、混合等。

GameMachine遵循以上流水线对流程进行抽象。例如,在OpenGL的绘制流程中:

void GMGLTechnique::draw(GMModel* model)
{
	D(d);
	GMGLBeginGetErrorsAndCheck();
	glBindVertexArray(model->getModelBuffer()->getMeshBuffer().arrayId);

	prepareStencil(*d->engine);
	prepareScreenInfo(getShaderProgram());
	beforeDraw(model);
	startDraw(model);
	afterDraw(model);

	glBindVertexArray(0);
	GMGLEndGetErrorsAndCheck();
}

void GMGLTechnique_3D::beforeDraw(GMModel* model)
{
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	// 材质
	prepareMaterial(model->getShader());

	// 应用Shader
	prepareShaderAttributes(model);

	// 设置光照模型
	GMIlluminationModel illuminationModel = prepareIlluminationModel(model);

	// 纹理
	prepareTextures(model, illuminationModel);
}

我们设置了VAO、模板、Shader的uniform参数等,在DirectX11的渲染中我们做的事情也是类似的:

void GMDx11Technique::draw(GMModel* model)
{
	prepareScreenInfo();
	prepareBuffer(model);
	prepareLights();
	prepareMaterials(model);
	prepareRasterizer(model);
	prepareBlend(model);
	prepareDepthStencil(model);
	prepareTextures(model);
	prepareDebug(model);
	passAllAndDraw(model);
}

总的来说,绘制过程一部分是OpenGL/DirectX的API,另外一部分是对着色器的变量设置。只要理解了渲染流水线,就可以写出适应于不同渲染API的足够抽象的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者对游戏的说明: 首先,您应当以一种批判的眼光来看待本程序。这个游戏是我制作 的第一部RPG游戏,无任何经验可谈,完全按照自己对游戏的理解进 行设计的。当我参照了《圣剑英雄2》的源码之后,才体会到专业游 戏引擎的博大精深。 该程序的内核大约有2000余行,能够处理人物的行走、对话、战斗, 等等。由于该程序的结构并不适于这种规模的程序,故不推荐您详 细研究该程序。所附地图编辑器的源程序我已经添加了详细的注释, 其程序结构也比较合理,可以作为初学VC的例子。 该程序在VC的程序向导所生成的SDI框架的基础上修改而成。它没有 使用任何关于VC底的东西。程序的绝大部分都是在CgameView类中 制作的,只有修改窗口特征的一段代码在CMainFrm类中。其他的类 统统没有用到。另外添加的一个类是CEnemy类。 整个游戏的故事情节分成8段,分别由Para1.h ~ Para8.h八个文件 实现。由于程序仅仅能够被动的处理各种各样的消息,所以情节的 实现也只能根据系统的一些参数来判断当前应当做什么。在程序中 使用了冗长的if……else if……结构来实现这种判断。 当然,在我的记录本上,详细的记录了每个事件的判断条件。这种 笨拙的设计当然是不可取的。成都金点所作《圣剑英雄II》采用了 剧本解读的方式,这才是正统的做法。但这也需要更多的编程经验 和熟练的code功夫。 下面列举的是程序编制过程中总结出来的经验和教训。 第一,对话方式应该采用《圣剑英雄II》的剧本方式。 现在的方式把一个段落中所有的对话都混在一个文件中,然后给每 句话一个号码相对应。这样做虽然降低了引擎的难度,却导致剧情的 编写极其繁琐。 第二,运动和显示应当完全分开。 现在的程序中,运动和显示是完全同步的。即:在定时器中调用所有 敌人的运动函数,然后将主角的动画向前推一帧,接着绘制地图,调 用所有敌人的显示函数、重绘主角。这样的好处是不会掉帧,但带来 的问题是,如果要提高敌人的运动速度,那么帧数也跟着上去了。所 以当DEMO版反馈说速度太慢的时候,我修改起来非常困难。而这个问 题到最后也仅仅是将4步一格该成了2步一格。 第三,VC中数组存在上限。如果用“int aaa[1000000000]”定义一个 数组,编译器肯定不会给分配那么大的内存空间。而在这个程序中, 地图矩阵、NPC矩阵都超过了VC中数组的上限。但这一点知道的太晚了。 在1.0版本中已经发现地图最右端缺少了几行,但不知道是什么原因 造成的。(地图编辑器中未出现此问题,因为地图编辑器是用“序列 化”的方式存盘读盘的。)解决这个问题的方法是用“new”来分配 内存空间。 第四,由于不知道应该如何使用“new”和“delete”,几乎所有的DC 都使用了全局变量。这是完全没有必要的。程序运行期大约会耗用20 多M的内存空间,相当于一个大型游戏所使用的内存空间了。 另外,在游戏的剧情、美工方面也有许多问题,总之一个词“业余”。 我就不总结了。下一部作品,我将争取在程序上有一个质的飞跃。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值