Type | Description | Required | |
---|---|---|---|
magFilter | integer | Magnification filter. | No |
minFilter | integer | Minification filter. | No |
wrapS | integer | s wrapping mode. | No, default: 10497 |
wrapT | integer | t wrapping mode. | No, default: 10497 |
tinygltf
库注释:
// glTF 2.0 spec does not define default value for minFilter
and
// magFilter
. Set -1 in TinyGLTF(issue #186)
int minFilter =
-1; // optional. -1 = no filter defined. [“NEAREST”, “LINEAR”,
// “NEAREST_MIPMAP_LINEAR”, “LINEAR_MIPMAP_NEAREST”,
// “NEAREST_MIPMAP_LINEAR”, “LINEAR_MIPMAP_LINEAR”]
所以需要处理一下不配时候的情况,自己设置好默认值,否则可能会导致纹理渲染不出来。
至此我们的数值数据以及纹理数据就加载好了。
场景创建
下面我们来创建Scene
,Scene
里面有会包含Node
,Node
里又有Mesh
,Mesh
里有可以有好几个部分,每个部分是一个Primitive
,这个层次关系是glTF
规范里约定的。
创建所有场景:
void Engine::buildScenes() {
auto buffers = buildBuffers(model_);
auto textures = buildTextures(model_);
scenes_.resize(model_.scenes.size());
for (auto i = 0; i < model_.scenes.size(); ++i) {
scenes_[i] = buildScene(model_, i, buffers, textures);
}
}
std::shared_ptr
Engine::buildScene(const tinygltf::Model &model, unsigned int sceneIndex,
const std::shared_ptr<std::vector> &buffers,
const std::shared_ptr<std::vector> &textures) {
auto scene = std::make_sharedtriangle::Scene();
for (auto i = 0; i < model.scenes[sceneIndex].nodes.size(); ++i) {
scene->addNode(
buildNode(model, model.scenes[sceneIndex].nodes[i], buffers, textures));
}
return scene;
}
节点创建:
std::shared_ptr
Engine::buildNode(const tinygltf::Model &model, unsigned int nodeIndex,
const std::shared_ptr<std::vector> &buffers,
const std::shared_ptr<std::vector> &textures,
std::shared_ptr parent) {
auto node = std::make_shared(parent);
auto nodeMatrix = model.nodes[nodeIndex].matrix;
glm::mat4 matrix(1.0f);
if (nodeMatrix.size() == 16) {
matrix[0].x = nodeMatrix[0], matrix[0].y = nodeMatrix[1],
matrix[0].z = nodeMatrix[2], matrix[0].w = nodeMatrix[3];
matrix[1].x = nodeMatrix[4], matrix[1].y = nodeMatrix[5],
matrix[1].z = nodeMatrix[6], matrix[1].w = nodeMatrix[7];
matrix[2].x = nodeMatrix[8], matrix[2].y = nodeMatrix[9],
matrix[2].z = nodeMatrix[10], matrix[2].w = nodeMatrix[11];
matrix[3].x = nodeMatrix[12], matrix[3].y = nodeMatrix[13],
matrix[3].z = nodeMatrix[14], matrix[3].w = nodeMatrix[15];
} else {
if (model.nodes[nodeIndex].translation.size() == 3) {
glm::translate(matrix, glm::vec3(model.nodes[nodeIndex].translation[0],
model.nodes[nodeIndex].translation[1],
model.nodes[nodeIndex].translation[2]));
}
if (model.nodes[nodeIndex].rotation.size() == 4) {
matrix *= glm::mat4_cast(glm::quat(model.nodes[nodeIndex].rotation[3],
model.nodes[nodeIndex].rotation[0],
model.nodes[nodeIndex].rotation[1],
model.nodes[nodeIndex].rotation[2]));
}
if (model.nodes[nodeIndex].scale.size() == 3) {
glm::scale(matrix, glm::vec3(model.nodes[nodeIndex].scale[0],
model.nodes[nodeIndex].scale[1],
model.nodes[nodeIndex].scale[2]));
}
}
node->setMatrix(matrix);
if (model.nodes[nodeIndex].mesh >= 0) {
node->setMesh(
buildMesh(model, model.nodes[nodeIndex].mesh, buffers, textures));
}
for (auto &childNodeIndex : model.nodes[nodeIndex].children) {
node->addChild(buildNode(model, childNodeIndex, buffers, textures, node));
}
return node;
}
注意节点的变换即可以用matrix
的方式给出,也可以用translation
、 rotation
、scale
的方式给出:
Any node can define a local space transformation either by supplying a matrix
property, or any of translation
, rotation
, and scale
properties (also known as TRS properties). translation
and scale
are FLOAT_VEC3
values in the local coordinate system. rotation
is a FLOAT_VEC4
unit quaternion value, (x, y, z, w)
, in the local coordinate system.
另外注意当以translation
、 rotation
、scale
方式给出时,变换顺序是TRS
,在shader
里矩阵是左乘顶点,也就是说先做缩放变换,再做旋转变换,最后做平移变换。
节点里可以包含mesh
、material
,也可以是空节点,注意判断是否配置了。接着再对子节点继续递归创建。
下面来看Mesh
的创建:
我们可以看到,Mesh
是由Primitive
组成的:
“meshes”: [
{
“primitives”: [
{
“attributes”: {
“NORMAL”: 1,
“POSITION”: 2,
“TEXCOORD_0”: 3
},
“indices”: 0,
“mode”: 4,
“material”: 0
}
],
“name”: “Mesh”
}
]
这个Primitive
是什么东西呢?我们来看glTF
文档的解释:
In glTF, meshes are defined as arrays of primitives. Primitives correspond to the data required for GPU draw calls. Primitives specify one or more attributes
, corresponding to the vertex attributes used in the draw calls. Indexed primitives also define an indices
property. Attributes and indices are defined as references to accessors containing corresponding data. Each primitive also specifies a material and a primitive type that corresponds to the GPU primitive type (e.g., triangle set).
可以把它认为是Mesh
的一个组成部分,就是说一个大Mesh
可以再细节成几个子部分,每个部分可以做为一次draw call
的单位,当然也可以不分,像这个模型里就没有分,它只有一个Primitive
。Primitive
里描述了attribute
的构成,以及通过indices
来指定这些attribute
的来源是什么,这个indices
就是accessor
的索引。
std::shared_ptr
Engine::buildMesh(const tinygltf::Model &model, unsigned int meshIndex,
const std::shared_ptr<std::vector> &buffers,
const std::shared_ptr<std::vector> &textures) {
auto meshPrimitives =
std::make_shared<std::vector<std::shared_ptr>>();
const auto &primitives = model.meshes[meshIndex].primitives;
auto vaos = std::make_shared<std::vector>(primitives.size());
GL_CHECK(glGenVertexArrays(vaos->size(), vaos->data()));
for (auto i = 0; i < primitives.size(); ++i) {
GL_CHECK(glBindVertexArray(vaos->at(i)));
meshPrimitives->push_back(
buildPrimitive(model, meshIndex, i, vaos, buffers, textures));
}
GL_CHECK(glBindVertexArray(0));
return std::make_shared(meshPrimitives);
}
std::shared_ptr
Engine::buildPrimitive(const tinygltf::Model &model, unsigned int meshIndex,
unsigned int primitiveIndex,
const std::shared_ptr<std::vector> &vaos,
const std::shared_ptr<std::vector> &buffers,
const std::shared_ptr<std::vector> &textures) {
const auto &primitive = model.meshes[meshIndex].primitives[primitiveIndex];
for (auto &attribute : preDefinedAttributes) {
const auto &attributeName = attribute.first;
const auto &attributeLocation = attribute.second;
const auto iterator = primitive.attributes.find(attributeName);
if (iterator == primitive.attributes.end()) {
continue;
}
const auto &accessor = model.accessors[(*iterator).second];
const auto &bufferView = model.bufferViews[accessor.bufferView];
const auto bufferIdx = bufferView.buffer;
GL_CHECK(glEnableVertexAttribArray(attributeLocation));
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, buffers->at(bufferIdx)));
const auto byteOffset = accessor.byteOffset + bufferView.byteOffset;
GL_CHECK(glVertexAttribPointer(
attributeLocation, accessor.type, accessor.componentType, GL_FALSE,
bufferView.byteStride, (const GLvoid *)byteOffset));
}
std::shared_ptr meshPrimitive;
if (primitive.indices >= 0) {
const auto &accessor = model.accessors[primitive.indices];
const auto &bufferView = model.bufferViews[accessor.bufferView];
const auto bufferIndex = bufferView.buffer;
GL_CHECK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers->at(bufferIndex)));
meshPrimitive = std::make_shared(
vaos->at(primitiveIndex), primitive.mode, accessor.count,
accessor.componentType, accessor.byteOffset + bufferView.byteOffset);
} else {
const auto accessorIndex = (*begin(primitive.attributes)).second;
const auto &accessor = model.accessors[accessorIndex];
meshPrimitive =
std::make_shared(vaos->at(primitiveIndex), primitive.mode,
accessor.count, accessor.componentType);
}
meshPrimitive->setMaterial(
buildMaterial(model, primitive.material, textures));
return meshPrimitive;
}
关键点是对于attributes
所指定的字段的索引号,以及indices
的索引号,通过accessor
得到buffer
数据块中不同部分的访问方式,再结合buffer view
来确定这个字段所对应的数据,比如:
// accessor 0
{
“bufferView”: 0,
“byteOffset”: 0,
“componentType”: 5123,
“count”: 36,
“max”: [
23
],
“min”: [
0
],
“type”: “SCALAR”
}
// buffer view 0
{
“buffer”: 0,
“byteOffset”: 768,
“byteLength”: 72,
“target”: 34963
}
这里accessor 0
又指向了buffer view 0
,accessor 0
中指定了偏移量为0个字节,buffer view 0
中指定了偏移量为768个字节,最终在数据块上取数据的偏移量就是两个相加也就是768字节。componentType
对应的是GL_UNSIGNED_SHORT
,accessor 0
中指定取36个值,buffer view 0
中指定取长度为72个字节的数据,而一个GL_UNSIGNED_SHORT
占用2个字段,36个正好是72个字节,这就对应上了。而target
值34963对应GL_ELEMENT_ARRAY_BUFFER
,就也就顶点索引,因此accessor 0
实际上提供就是顶点索引数组。
可以把它打印出来看看验证一下:
std::shared_ptr<std::vector> buildBuffers(const tinygltf::Model &model)
{
auto buffers = std::make_shared<std::vector>(model.buffers.size(), 0);
GL_CHECK(glGenBuffers(buffers->size(), buffers->data()));
for (auto i = 0; i < model.buffers.size(); ++i) {
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, buffers->at(i)));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER, model.buffers[i].data.size(),
model.buffers[i].data.data(), GL_STATIC_DRAW));
}
for (int i = 768; i < 768 + 72; i += 2) {
unsigned int index;
memcpy(&index, model.buffers[0].data.data() + i, 2);
// print
}
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, 0));
return buffers;
}
index 0 = 0
index 1 = 1
index 2 = 2
index 3 = 3
index 4 = 2
index 5 = 1
index 6 = 4
index 7 = 5
index 8 = 6
index 9 = 7
index 10 = 6
index 11 = 5
index 12 = 8
index 13 = 9
index 14 = 10
index 15 = 11
index 16 = 10
index 17 = 9
index 18 = 12
index 19 = 13
index 20 = 14
index 21 = 15
index 22 = 14
index 23 = 13
index 24 = 16
index 25 = 17
index 26 = 18
index 27 = 19
index 28 = 18
index 29 = 17
index 30 = 20
index 31 = 21
index 32 = 22
index 33 = 23
index 34 = 22
index 35 = 21
可以看到,这36个索引号,范围是从0~23,一共24个,正方体6个面,每个面2个三角形,每个三角形有3个顶点索引,这两个三角形有2个顶点是共用的,因此索引数组共有3*2*6=36个值,索引号共有4*6=24个
下面来看attributes
,对于Primitive
中的attribute
:
“attributes”: {
“NORMAL”: 1,
“POSITION”: 2,
“TEXCOORD_0”: 3
}
后面的编号也是Accessor
的索引,和上面说的indices
道理是一样的。对于attributes
这里通过glVertexAttribPointer
中给每个attribute
指定数据type
、stride
及offset
,并且这里我们使用了vao
,通过vao
就能一次性地把所有attribute
的glVertexAttribPointer
配置给记录下来,使用时只需绑定vao
即可,否则渲染前需要对所有attribute
都重新来一遍glVertexAttribPointer
配置,就比较麻烦。
至此就完成了Primitive
的创建,此时还只创建了形状,并没有纹理,接下来我们来创建Material
,里面就包含了纹理信息:
std::shared_ptr
Engine::buildMaterial(const tinygltf::Model &model, unsigned int materialIndex,
const std::shared_ptr<std::vector> &textures) {
auto baseColorIndex = model.materials[materialIndex]
.pbrMetallicRoughness.baseColorTexture.index;
auto baseColorTexture =
(baseColorIndex >= 0 ? textures->at(baseColorIndex)
: buildDefaultBaseColorTexture(model));
const auto baseColorTextureLocation = GL_CHECK(
glGetUniformLocation(program_->getProgram(), UNIFORM_BASE_COLOR_TEXTURE));
return std::make_shared(baseColorTexture, baseColorTextureLocation);
}
glTF
中的Material
比较复杂,参见:github.com/KhronosGrou…
这里只实现了base color texture
,尚未包含PBR
等效果,后续文章我们再来完善,这里注意base color texture
也是可以不配的,这里我们创建一个白色纹理来做为默认的颜色。
这样我们渲染前所需要的数据就准备好了。
渲染
我们先来看shader
:
// vertex shader
#version 300 es
precision mediump float;
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_normal;
layout(location = 2) in vec2 a_texCoord0;
out vec2 v_texCoord0;
uniform mat4 u_modelViewProjectMatrix;
void main() {
gl_Position = u_modelViewProjectMatrix * a_position;
v_texCoord0 = a_texCoord0;
}
// fragment shader
#version 300 es
precision mediump float;
in vec2 v_texCoord0;
out vec4 outColor;
uniform sampler2D u_baseColorTexture;
void main() {
outColor = texture(u_baseColorTexture, v_texCoord0);
}
由于我们现在只实现了base color
,所以shader
很简单,vertex shader
中有model-view-project
矩阵变换,fragment shader
中就简单地取base color texture
就行了。
前面提到Primitive
是一次draw call
的单位,因此我们对Primitive
来执行draw call
:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
更多学习和讨论,欢迎加入我们!
有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
这里有2000+小伙伴,让你的学习不寂寞~·
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
让你的学习不寂寞~·
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算