在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.

此外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向适应的向量计算库
- 向量类:
glm::vec2
: 2D 向量。glm::vec3
: 3D 向量。glm::vec4
: 4D 向量(通常用于颜色或齐次坐标)。
- 矩阵类:
glm::mat2
: 2x2 矩阵。glm::mat3
: 3x3 矩阵。glm::mat4
: 4x4 矩阵(常用于变换矩阵)。
- 四元数类:
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);
- 投影矩阵 (
perspective
和orthographic
):
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.
mFlags
: 这个标志表示场景的一些特性,例如是否是不完整的 (AI_SCENE_FLAGS_INCOMPLETE
) 或者是否有无效的数据 (AI_SCENE_FLAGS_INVALID_DATA
)。mRootNode
: 指向场景根节点的指针。每个场景都有一个根节点,所有其他节点都是它的子节点。通过遍历这个节点树,你可以获取场景的所有几何信息。mNumMeshes
和mMeshes
: 分别表示场景中网格的数量和指向网格数组的指针。网格包含了顶点、面和其他几何数据。mNumMaterials
和mMaterials
: 分别表示场景中材质的数量和指向材质数组的指针。材质定义了网格的外观属性。mNumTextures
和mTextures
: 分别表示场景中文本贴图的数量和指向文本贴图数组的指针。请注意,不是所有的模型格式都支持直接导出纹理,所以这个成员可能为空。mNumCameras
和mCameras
: 分别表示场景中相机的数量和指向相机数组的指针。并不是所有模型文件都会包含相机信息。mNumLights
和mLights
: 分别表示场景中光源的数量和指向光源数组的指针。同样地,并非所有模型文件都包含光源信息。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个子节点,从而形成一个层次结构。
mTransformation
:
- 类型:
aiMatrix4x4
- 描述: 表示节点的本地变换矩阵,它定义了该节点相对于其父节点的位置、旋转和缩放。
mNumMeshes
和mMeshes
:
- 类型:
unsigned int
和unsigned int*
- 描述:
mNumMeshes
表示此节点直接关联的网格数量;mMeshes
是一个索引数组,指向aiScene
的mMeshes
数组中的相应网格。如果mNumMeshes
为 0,则该节点没有直接关联的网格。
mParent
:
- 类型:
aiNode*
- 描述: 指向该节点的父节点的指针。根节点的
mParent
为nullptr
。
mNumChildren
和mChildren
:
- 类型:
unsigned int
和aiNode**
- 描述:
mNumChildren
表示该节点的子节点数量;mChildren
是一个指针数组,指向该节点的所有子节点。
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
mPrimitiveTypes
:
- 类型:
unsigned int
- 描述: 表示该网格中包含的图元类型,例如点 (
aiPrimitiveType_POINT
)、线 (aiPrimitiveType_LINE
) 或三角形 (aiPrimitiveType_TRIANGLE
)。
mNumVertices
和mVertices
:
- 类型:
unsigned int
和aiVector3D*
- 描述:
mNumVertices
表示顶点的数量;mVertices
是指向aiVector3D
数组的指针,每个元素代表一个顶点的位置。
mNormals
:
- 类型:
aiVector3D*
- 描述: 指向法线数组的指针。如果网格包含法线数据,则每个顶点都有一个对应的法线向量。
mTextureCoords
:
- 类型:
aiVector3D**
- 描述: 指向纹理坐标数组的二维数组指针。第一维是纹理坐标集的数量(最多8个),第二维是实际的纹理坐标。如果某个顶点有纹理坐标,则可以通过这个成员访问。
mColors
:
- 类型:
aiColor4D**
- 描述: 指向颜色数组的二维数组指针。第一维是颜色集的数量(最多8个),第二维是实际的颜色值。如果顶点有色值,则可以在这里找到。
mNumFaces
和mFaces
:
- 类型:
unsigned int
和aiFace*
- 描述:
mNumFaces
表示面的数量;mFaces
是指向aiFace
数组的指针,每个aiFace
定义了一个由若干顶点组成的多边形(通常是三角形)。
mMaterialIndex
:
- 类型:
unsigned int
- 描述: 网格使用的材质在场景的
mMaterials
数组中的索引。通过这个索引,你可以获取与该网格关联的aiMaterial
对象。
mNumBones
和mBones
:
- 类型:
unsigned int
和aiBone**
- 描述: 如果网格支持骨骼动画,则
mBones
包含了指向aiBone
数组的指针,每个aiBone
定义了一个影响顶点位置的骨骼。mNumBones
是骨骼的数量。
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
提供了一系列的 Get
和 Set
函数。最常用的 Get
函数包括:
Get(AI_MATKEY key, aiColor4D& out)
:
- 获取指定键的颜色值。
Get(AI_MATKEY key, float& out)
:
- 获取指定键的浮点数值。
Get(AI_MATKEY key, bool& out)
:
- 获取指定键的布尔值。
GetTexture(aiTextureType type, unsigned int index, aiString\* path)
:
- 获取指定类型的纹理路径。
index
参数允许你访问同一类型下的多个纹理(例如,多层扩散纹理)。
HasProperty(const char\* key)
:
- 检查材质是否具有给定键的属性。
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结构中
mWidth
和mHeight
:
- 类型:
unsigned int
- 描述: 分别表示纹理图像的宽度和高度(以像素为单位)。对于非图像格式(例如程序生成的纹理),这些值可能为0。
mData
:
- 类型:
unsigned char*
- 描述: 指向包含纹理数据的缓冲区。注意,并不是所有情况下都会提供实际的纹理数据;某些导入器可能会直接返回文件路径而不是加载图像数据到内存中。
mHeight
:
- 类型:
unsigned int
- 描述: 纹理的高度(以像素为单位)。
achFormatHint
:
- 类型:
char[AI_TEXTURE_FORMAT_MAX]
- 描述: 提供关于纹理格式的提示字符串,例如
"jpg"
或"png"
。这可以帮助你确定如何正确地解码纹理数据。
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;
}
Comments NOTHING