OpenGL开发中的着色器编程语言与辅助库

proanimer 发布于 2024-12-30 376 次阅读


在OpenGL中着色器的编程语言叫做GLSL,类似C语言但是内置了许多有用的函数. 这里简单学习其基础语法和包含特殊函数.

GLSL

着色器编程语言,在GPU下编程

开发环境搭建

这里使用vscode进行开发,由于着色器语言不像C++,Java这种通用语言,它的语法检查和Lint没有这么丰富. 在vscode主要下载两个插件,一个是语法高亮Shader languages support for VS Code - Visual Studio Marketplace,另一个是Linthsimpson/vscode-glsllint: VSCode extension to lint GLSL shading language files.

image-20241226175503864

此外Lint的插件本身不提供Linter,需要自己下载KhronosGroup/glslang: Khronos-reference front end for GLSL/ESSL, partial front end for HLSL, and a SPIR-V generator.

语法提示规则与文件名后缀相关

The applied stage-specific rules are based on the file extension:

  • .vert for a vertex shader
  • .tesc for a tessellation control shader
  • .tese for a tessellation evaluation shader
  • .geom for a geometry shader
  • .frag for a fragment shader
  • .comp for a compute shader

For ray tracing pipeline shaders:

  • .rgen for a ray generation shader
  • .rint for a ray intersection shader
  • .rahit for a ray any-hit shader
  • .rchit for a ray closest-hit shader
  • .rmiss for a ray miss shader
  • .rcall for a callable shader

此外还可以配置代码片段GLSL snippets for visual studio code/kode studio,输入缩写即可提示.

数据类型

float,int,bool,vec,mat, struct,[]

重要辅助函数

向量和矩阵运算

dot,cross,normalize,transpose,inverse

纹理采样

texture,mix

光照计算

clamp,mix,reflect

GLM

与opengl向适应的向量计算库

  1. 向量类:
  • glm::vec2: 2D 向量。
  • glm::vec3: 3D 向量。
  • glm::vec4: 4D 向量(通常用于颜色或齐次坐标)。
  1. 矩阵类:
  • glm::mat2: 2x2 矩阵。
  • glm::mat3: 3x3 矩阵。
  • glm::mat4: 4x4 矩阵(常用于变换矩阵)。
  1. 四元数类:
  • glm::quat: 四元数,用于表示旋转。

常用方法

向量操作

  • 创建向量:
  glm::vec3 v(1.0f, 2.0f, 3.0f);
  • 向量加法:
  glm::vec3 sum = v1 + v2;
  • 向量减法:
  glm::vec3 difference = v1 - v2;
  • 标量乘法:
  glm::vec3 scaled = v * scalar;
  • 点积 (dot product):
  float dotProduct = glm::dot(v1, v2);
  • 叉积 (cross product) - 仅适用于3D向量:
  glm::vec3 crossProduct = glm::cross(v1, v2);
  • 长度/模 (length/magnitude):
  float length = glm::length(v);
  • 标准化 (normalize):
  glm::vec3 normalizedV = glm::normalize(v);

矩阵操作

  • 创建单位矩阵:
  glm::mat4 identity = glm::mat4(1.0f);
  • 平移矩阵:
  glm::mat4 translateMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z));
  • 缩放矩阵:
  glm::mat4 scaleMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(sx, sy, sz));
  • 旋转矩阵:
  glm::mat4 rotateMatrix = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(axisX, axisY, axisZ));
  • 组合变换:
  glm::mat4 model = glm::translate(glm::mat4(1.0f), translation) *
                    glm::rotate(glm::mat4(1.0f), rotationAngle, rotationAxis) *
                    glm::scale(glm::mat4(1.0f), scale);
  • 视图矩阵 (lookAt):
  glm::mat4 view = glm::lookAt(cameraPosition, cameraTarget, upVector);
  • 投影矩阵 (perspectiveorthographic):
  glm::mat4 projection = glm::perspective(glm::radians(fov), aspectRatio, nearPlane, farPlane);
  // 或者正交投影
  glm::mat4 orthoProjection = glm::ortho(left, right, bottom, top, nearPlane, farPlane);

四元数操作

  • 创建四元数:
  glm::quat q = glm::quat(angle, glm::vec3(axisX, axisY, axisZ));
  • 从旋转矩阵转换到四元数:
  glm::quat qFromMat = glm::quat_cast(rotationMatrix);
  • 四元数插值 (slerp):
  glm::quat interpolatedQ = glm::slerp(q1, q2, t);
  • 四元数转欧拉角:
  glm::vec3 eulerAngles = glm::eulerAngles(q);
  • 四元数转旋转矩阵:
  glm::mat4 matFromQuat = glm::mat4_cast(q);

Assimp

模型加载库

读取文件

Assimp::Importer::ReadFile()

​ 该类将读取文件并处理其数据,将导入的数据作为指向一个对象的指针返回。现在可以从文件中提取所需的数据。

​ 导入器为自己管理所有资源。如果导入器被销毁,那么由它创建/读取的所有数据也将被销毁。因此,使用Importer最简单的方法是在本地创建一个实例,使用它的结果,然

#include <assimp/Importer.hpp>      // C++ importer interface
#include <assimp/scene.h>           // Output data structure
#include <assimp/postprocess.h>     // Post processing flags

bool DoTheImportThing( const std::string& pFile) {
  // Create an instance of the Importer class
  Assimp::Importer importer;

  // And have it read the given file with some example postprocessing
  // Usually - if speed is not the most important aspect for you - you'll
  // probably to request more postprocessing than we do in this example.
  const aiScene* scene = importer.ReadFile( pFile,
    aiProcess_CalcTangentSpace       |
    aiProcess_Triangulate            |
    aiProcess_JoinIdenticalVertices  |
    aiProcess_SortByPType);

  // If the import failed, report it
  if (nullptr == scene) {
    DoTheErrorLogging( importer.GetErrorString());
    return false;
  }

  // Now we can access the file's contents.
  DoTheSceneProcessing( scene);

  // We're done. Everything will be cleaned up by the importer destructor
  return true;
}

后简单地让它离开作用域。

数据结构

以结构集合的形式返回导入的数据。aiScene形成数据的根,从这里可以访问从导入文件中读取的所有节点,网格,材质,动画或纹理

默认情况下,所有3D数据都以右手坐标系提供,例如OpenGL使用的坐标系。在这个坐标系中,+X指向右侧,+Y指向上方,+Z指向屏幕外的观察者

输出面顺序为逆时针方向

Scene

场景包含一个rootNode用于遍历以及mesh和material,通过node中的mesh索引获得具体mesh,通过mesh中的material索引获得具体material,mesh还包括faces,一个face就是一个primitive.

  1. mFlags: 这个标志表示场景的一些特性,例如是否是不完整的 (AI_SCENE_FLAGS_INCOMPLETE) 或者是否有无效的数据 (AI_SCENE_FLAGS_INVALID_DATA)。
  2. mRootNode: 指向场景根节点的指针。每个场景都有一个根节点,所有其他节点都是它的子节点。通过遍历这个节点树,你可以获取场景的所有几何信息。
  3. mNumMeshesmMeshes: 分别表示场景中网格的数量和指向网格数组的指针。网格包含了顶点、面和其他几何数据。
  4. mNumMaterialsmMaterials: 分别表示场景中材质的数量和指向材质数组的指针。材质定义了网格的外观属性。
  5. mNumTexturesmTextures: 分别表示场景中文本贴图的数量和指向文本贴图数组的指针。请注意,不是所有的模型格式都支持直接导出纹理,所以这个成员可能为空。
  6. mNumCamerasmCameras: 分别表示场景中相机的数量和指向相机数组的指针。并不是所有模型文件都会包含相机信息。
  7. mNumLightsmLights: 分别表示场景中光源的数量和指向光源数组的指针。同样地,并非所有模型文件都包含光源信息。
  8. mMetaData: 包含有关场景的元数据。这可以包括版本号、作者等信息
// 假设我们已经有一个 aiScene* scene

// 访问根节点
aiNode* rootNode = scene->mRootNode;

// 遍历所有网格
for (unsigned int i = 0; i < scene->mNumMeshes; ++i) {
    aiMesh* mesh = scene->mMeshes[i];
    // 处理网格...
}

// 遍历所有材质
for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
    aiMaterial* material = scene->mMaterials[i];
    // 处理材质...
}

// 如果有纹理,遍历它们
if (scene->mNumTextures > 0) {
    for (unsigned int i = 0; i < scene->mNumTextures; ++i) {
        aiTexture* texture = scene->mTextures[i];
        // 处理纹理...
    }
}

// 如果有相机,遍历它们
if (scene->mNumCameras > 0) {
    for (unsigned int i = 0; i < scene->mNumCameras; ++i) {
        aiCamera* camera = scene->mCameras[i];
        // 处理相机...
    }
}

// 如果有光源,遍历它们
if (scene->mNumLights > 0) {
    for (unsigned int i = 0; i < scene->mNumLights; ++i) {
        aiLight* light = scene->mLights[i];
        // 处理光源...
    }
}
Nodes

节点是场景中名字不大的实体,相对于它们的父节点有一个位置和方向。从场景的根节点开始,所有节点可以有0到x个子节点,从而形成一个层次结构。

  1. mTransformation:
  • 类型: aiMatrix4x4
  • 描述: 表示节点的本地变换矩阵,它定义了该节点相对于其父节点的位置、旋转和缩放。
  1. mNumMeshesmMeshes:
  • 类型: unsigned intunsigned int*
  • 描述: mNumMeshes 表示此节点直接关联的网格数量;mMeshes 是一个索引数组,指向 aiScenemMeshes 数组中的相应网格。如果 mNumMeshes 为 0,则该节点没有直接关联的网格。
  1. mParent:
  • 类型: aiNode*
  • 描述: 指向该节点的父节点的指针。根节点的 mParentnullptr
  1. mNumChildrenmChildren:
  • 类型: unsigned intaiNode**
  • 描述: mNumChildren 表示该节点的子节点数量;mChildren 是一个指针数组,指向该节点的所有子节点。
  1. mName:
  • 类型: aiString
  • 描述: 节点的名字。在某些情况下,这个名字可能被用来标识特定的节点或作为动画等的参考
// 假设我们有一个 aiNode* node

// 获取节点的变换矩阵
aiMatrix4x4 transformation = node->mTransformation;

// 遍历所有与该节点关联的网格
for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
    unsigned int meshIndex = node->mMeshes[i];
    aiMesh* mesh = scene->mMeshes[meshIndex];
    // 处理网格...
}

// 获取父节点
if (node->mParent != nullptr) {
    aiNode* parentNode = node->mParent;
    // 处理父节点...
}

// 遍历所有子节点
for (unsigned int i = 0; i < node->mNumChildren; ++i) {
    aiNode* childNode = node->mChildren[i];
    // 处理子节点...
}

// 获取节点名称
std::string nodeName(node->mName.C_Str());
Mesh
  1. mPrimitiveTypes:
  • 类型: unsigned int
  • 描述: 表示该网格中包含的图元类型,例如点 (aiPrimitiveType_POINT)、线 (aiPrimitiveType_LINE) 或三角形 (aiPrimitiveType_TRIANGLE)。
  1. mNumVerticesmVertices:
  • 类型: unsigned intaiVector3D*
  • 描述: mNumVertices 表示顶点的数量;mVertices 是指向 aiVector3D 数组的指针,每个元素代表一个顶点的位置。
  1. mNormals:
  • 类型: aiVector3D*
  • 描述: 指向法线数组的指针。如果网格包含法线数据,则每个顶点都有一个对应的法线向量。
  1. mTextureCoords:
  • 类型: aiVector3D**
  • 描述: 指向纹理坐标数组的二维数组指针。第一维是纹理坐标集的数量(最多8个),第二维是实际的纹理坐标。如果某个顶点有纹理坐标,则可以通过这个成员访问。
  1. mColors:
  • 类型: aiColor4D**
  • 描述: 指向颜色数组的二维数组指针。第一维是颜色集的数量(最多8个),第二维是实际的颜色值。如果顶点有色值,则可以在这里找到。
  1. mNumFacesmFaces:
  • 类型: unsigned intaiFace*
  • 描述: mNumFaces 表示面的数量;mFaces 是指向 aiFace 数组的指针,每个 aiFace 定义了一个由若干顶点组成的多边形(通常是三角形)。
  1. mMaterialIndex:
  • 类型: unsigned int
  • 描述: 网格使用的材质在场景的 mMaterials 数组中的索引。通过这个索引,你可以获取与该网格关联的 aiMaterial 对象。
  1. mNumBonesmBones:
  • 类型: unsigned intaiBone**
  • 描述: 如果网格支持骨骼动画,则 mBones 包含了指向 aiBone 数组的指针,每个 aiBone 定义了一个影响顶点位置的骨骼。mNumBones 是骨骼的数量。
  1. mName:
  • 类型: aiString
  • 描述: 网格的名字。某些情况下,这个名字可能是有意义的,比如用于标识特定的网格或作为其他资源的引用。
// 假设我们有一个 aiMesh* mesh 和 aiScene* scene

// 获取网格的材质
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];

// 遍历所有顶点
for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
    aiVector3D position = mesh->mVertices[i];
    // 处理顶点位置...

    if (mesh->HasNormals()) {
        aiVector3D normal = mesh->mNormals[i];
        // 处理法线...
    }

    if (mesh->HasTextureCoords(0)) { // 假设使用第0个纹理坐标集
        aiVector3D texCoord = mesh->mTextureCoords[0][i];
        // 处理纹理坐标...
    }
}

// 遍历所有面
for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
    aiFace face = mesh->mFaces[i];
    // 处理面...
    for (unsigned int j = 0; j < face.mNumIndices; ++j) {
        unsigned int vertexIndex = face.mIndices[j];
        // 使用顶点索引...
    }
}

// 如果有骨骼信息,遍历它们
if (mesh->HasBones()) {
    for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
        aiBone* bone = mesh->mBones[i];
        // 处理骨骼...
    }
}

aiMesh 还提供了一些辅助函数来检查网格是否包含特定的数据类型:

  • HasPositions(): 返回网格是否有顶点位置。
  • HasNormals(): 返回网格是否有法线数据。
  • HasTangentsAndBitangents(): 返回网格是否有切线和副切线数据。
  • HasTextureCoords(unsigned int): 接受一个参数指定纹理坐标集的索引,返回网格是否有对应的纹理坐标。
  • HasVertexColors(unsigned int): 接受一个参数指定颜色集的索引,返回网格是否有对应的顶点颜色。
  • HasFaces(): 返回网格是否有面数据。
  • HasBones(): 返回网格是否有骨骼信息。
Material
颜色属性
  • AI_MATKEY_COLOR_DIFFUSE: 扩散(漫反射)颜色。
  • AI_MATKEY_COLOR_AMBIENT: 环境光颜色。
  • AI_MATKEY_COLOR_SPECULAR: 高光(镜面反射)颜色。
  • AI_MATKEY_COLOR_EMISSIVE: 发射光颜色。
  • AI_MATKEY_COLOR_TRANSPARENT: 透明颜色。
  • AI_MATKEY_COLOR_REFLECTIVE: 反射颜色。
浮点数属性
  • AI_MATKEY_SHININESS: 高光强度。
  • AI_MATKEY_SHININESS_STRENGTH: 高光强度因子。
  • AI_MATKEY_REFRACTI: 折射率。
布尔属性
  • AI_MATKEY_ENABLE_WIREFRAME: 是否启用线框模式。
纹理属性
  • AI_MATKEY_TEXTURE_BASE: 基础纹理。
  • AI_MATKEY_TEXTURE_DIFFUSE: 扩散(漫反射)纹理。
  • AI_MATKEY_TEXTURE_SPECULAR: 高光(镜面反射)纹理。
  • AI_MATKEY_TEXTURE_AMBIENT: 环境光纹理。
  • AI_MATKEY_TEXTURE_EMISSIVE: 发射光纹理。
  • AI_MATKEY_TEXTURE_HEIGHT: 高度图。
  • AI_MATKEY_TEXTURE_NORMALS: 法线贴图。
  • AI_MATKEY_TEXTURE_SHININESS: 高光贴图。
  • AI_MATKEY_TEXTURE_OPACITY: 不透明度贴图。
  • AI_MATKEY_TEXTURE_DISPLACEMENT: 位移贴图。
  • AI_MATKEY_TEXTURE_LIGHTMAP: 光照贴图。
  • AI_MATKEY_TEXTURE_REFLECTION: 反射贴图。
方法

为了访问上述属性,aiMaterial 提供了一系列的 GetSet 函数。最常用的 Get 函数包括:

  1. Get(AI_MATKEY key, aiColor4D& out):
  • 获取指定键的颜色值。
  1. Get(AI_MATKEY key, float& out):
  • 获取指定键的浮点数值。
  1. Get(AI_MATKEY key, bool& out):
  • 获取指定键的布尔值。
  1. GetTexture(aiTextureType type, unsigned int index, aiString\* path):
  • 获取指定类型的纹理路径。index 参数允许你访问同一类型下的多个纹理(例如,多层扩散纹理)。
  1. HasProperty(const char\* key):
  • 检查材质是否具有给定键的属性。
  1. Get(AI_MATKEY key, unsigned int& out):
  • 获取指定键的无符号整数值(例如,用于获取纹理的数量)
// 假设我们有一个 aiMaterial* material

// 获取扩散颜色
aiColor4D diffuseColor;
if (AI_SUCCESS == material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor)) {
    // 使用 diffuseColor...
}

// 获取高光强度
float shininess;
if (AI_SUCCESS == material->Get(AI_MATKEY_SHININESS, shininess)) {
    // 使用 shininess...
}

// 获取第一个扩散纹理路径
aiString texturePath;
if (AI_SUCCESS == material->GetTexture(aiTextureType_DIFFUSE, 0, &texturePath)) {
    // 使用 texturePath.C_Str()...
}

// 检查是否有环境光颜色
if (material->HasProperty(AI_MATKEY_COLOR_AMBIENT)) {
    aiColor4D ambientColor;
    if (AI_SUCCESS == material->Get(AI_MATKEY_COLOR_AMBIENT, ambientColor)) {
        // 使用 ambientColor...
    }
}
Texture

通常,资源使用的纹理存储在单独的文件中,但是也有文件格式将纹理直接嵌入到模型文件中。这样的纹理被加载到aittexture结构中

  1. mWidthmHeight:
  • 类型: unsigned int
  • 描述: 分别表示纹理图像的宽度和高度(以像素为单位)。对于非图像格式(例如程序生成的纹理),这些值可能为0。
  1. mData:
  • 类型: unsigned char*
  • 描述: 指向包含纹理数据的缓冲区。注意,并不是所有情况下都会提供实际的纹理数据;某些导入器可能会直接返回文件路径而不是加载图像数据到内存中。
  1. mHeight:
  • 类型: unsigned int
  • 描述: 纹理的高度(以像素为单位)。
  1. achFormatHint:
  • 类型: char[AI_TEXTURE_FORMAT_MAX]
  • 描述: 提供关于纹理格式的提示字符串,例如 "jpg""png"。这可以帮助你确定如何正确地解码纹理数据。
  1. mFilename:
  • 类型: aiString
  • 描述: 包含纹理文件的相对或绝对路径名。这是最常用的方式来获取纹理资源的位置。
// 假设我们有一个 aiTexture* texture

// 获取纹理文件名
std::string filename(texture->mFilename.C_Str());
std::cout << "Texture filename: " << filename << std::endl;

// 如果有纹理数据,可以尝试读取其大小
if (texture->mWidth > 0 && texture->mHeight > 0) {
    std::cout << "Texture dimensions: " << texture->mWidth << "x" << texture->mHeight << std::endl;
}

// 根据格式提示来决定如何处理纹理数据
std::string formatHint(texture->achFormatHint);
if (!formatHint.empty()) {
    std::cout << "Texture format hint: " << formatHint << std::endl;
}

// 如果有纹理数据,可以直接使用指针访问
if (texture->mData != nullptr) {
    // 注意:这里只是示例,通常你需要根据具体的格式解码这些数据
    unsigned char* data = texture->mData;
    // 使用纹理数据...
}

stb_image

加载图像库,通过stbi_load读取图像的宽高和通道

GLuint LoadTexture(const char *path, bool clip) {
  // 生成纹理
  GLuint texture;
  glGenTextures(1, &texture);
  stbi_set_flip_vertically_on_load(true);
  // 加载图像
  int img_width, img_height, nrChannels;
  unsigned char *data =
      stbi_load(path, &img_width, &img_height, &nrChannels, 0);
  if (data) {
    GLenum format;
    if (nrChannels == 1) {
      format = GL_RED;
    } else if (nrChannels == 3) {
      format = GL_RGB;
    } else if (nrChannels == 4) {
      format = GL_RGBA;
    } else {
      throw std::runtime_error("No available format.");
    }

    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, format, img_width, img_height, 0, format,
                 GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
    if (clip) {
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    } else {
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    }
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  } else {
    std::cerr << "Failed to load texture" << std::endl;
    std::cout << "Error: Failed to load the image because "
              << stbi_failure_reason();
    return -1;
  }

  stbi_image_free(data);
  return texture;
}
此作者没有提供个人介绍
最后更新于 2024-12-30