本文主要讲3D的变换,3D的旋转在OpenGL扩展辅助库中有GLM实现,本文主要从理解角度,手工实现3D变换,其中使用的是4元矩阵。
1. 缩放
2. 平移
3. 旋转
缩放
缩放公式
实现:
- 只要直接构造一个$4 \times 4$矩阵即可实现缩放。
- 构造方式:
1. C/C++风格
2. 使用glm
void transform(GLuint pid, GLfloat sx, GLfloat sy, GLfloat sz){
/**
* 这里使用的是专门遵循GLSL标准的数学运算库:GLM,该库的使用可以从上面头文件的说明得到;
* 官方网页说明速度慢,更加详细的说明可以参考官方github文档:https://github.com/g-truc/glm/blob/master/manual.md
*/
// glm::mat4 trans = glm::mat4(1.0f);
// trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
GLfloat scale[] ={
sx, 0.0f, 0.0f, 0.0f,
0.0f, sy, 0.0f, 0.0f,
0.0f, 0.0f, sz, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
unsigned int transformLoc = glGetUniformLocation(pid, "transform");
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, scale);
}
- C/C++风格
GLfloat scale[] ={
sx, 0.0f, 0.0f, 0.0f,
0.0f, sy, 0.0f, 0.0f,
0.0f, 0.0f, sz, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
unsigned int transformLoc = glGetUniformLocation(pid, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, scale);
- glm风格
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
unsigned int transformLoc = glGetUniformLocation(pid, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
平移
平移公式
实现
void transform(GLuint pid, GLfloat sx, GLfloat sy, GLfloat sz){
/**
* 这里使用的是专门遵循GLSL标准的数学运算库:GLM,该库的使用可以从上面头文件的说明得到;
* 官方网页说明速度慢,更加详细的说明可以参考官方github文档:https://github.com/g-truc/glm/blob/master/manual.md
*/
// glm::mat4 trans = glm::mat4(1.0f);
// trans = glm::translate(trans, glm::vec3(sx, sy, sz));
GLfloat translate[] ={
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
sx, sy, sz, 1.0f
};
unsigned int transformLoc = glGetUniformLocation(pid, "transform");
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, translate);
}
-
注意:
- 因为内存表达的缘故,所以最后一列,在表示为数组的时候是最后一行。
旋转
旋转公式
-
常见的2D平面中的旋转公式
-
3D中按照z轴旋转的公式
-
3D中按照x轴旋转的公式
-
3D中按照y旋转的公式
-
3D中按照指定向量
旋转的公式
实现
void transform(GLuint pid, GLfloat angle, GLfloat sx, GLfloat sy, GLfloat sz){
/**
* 这里使用的是专门遵循GLSL标准的数学运算库:GLM,该库的使用可以从上面头文件的说明得到;
* 官方网页说明速度慢,更加详细的说明可以参考官方github文档:https://github.com/g-truc/glm/blob/master/manual.md
*/
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::rotate(trans, glm::radians(angle), glm::vec3(sx, sy, sz));
GLfloat r_sin = sinf(angle * (3.14159f / 180.0f));
GLfloat r_cos = cosf(angle * (3.14159f / 180.0f));
GLfloat dis = sqrt(sx * sx + sy * sy + sz * sz); // 注意旋转向量(轴)必须单位化,否会引起缩放效果
sx /= dis;
sy /= dis;
sz /= dis;
GLfloat rotate[] ={
sx*sx*(1-r_cos)+r_cos, sy*sx*(1-r_cos)+sz*r_sin, sz*sx*(1-r_cos)-sy*r_sin, 0.0f,
sx*sy*(1-r_cos)-sz*r_sin, sy*sy*(1-r_cos)+r_cos, sz*sy*(1-r_cos)+sx*r_sin, 0.0f,
sx*sz*(1-r_cos)+sy*r_sin, sy*sz*(1-r_cos)-sx*r_sin, sz*sz*(1-r_cos)+r_cos, 0.0f,
0.0f, 0.0f , 0.0f, 1.0f
};
unsigned int transformLoc = glGetUniformLocation(pid, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, rotate);
}
GLM使用
-
GLM的向量与矩阵都是结构体,具备结构体的知识,结合.h文件,不使用API帮助都可以调用。
- 需要注意的是使用下标访问,访问的是列(这也是内存的结构表示的问题)
下面是一个简答的入门例子
// https://github.com/g-truc/glm
/*
下载后直接拷贝到系统的include目录使用即可
*/
#include <stdio.h>
#include <stdlib.h>
#include <glm/vec3.hpp> // glm::vec3
#include <glm/vec4.hpp> // glm::vec4
#include <glm/mat4x4.hpp> // glm::mat4
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
#include <glm/ext/scalar_constants.hpp> // glm::pi
#include <glm/gtc/type_ptr.hpp>
int main(int argc, char const *argv[])
{
/* code */
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f); // 向量
glm::mat4 trans = glm::mat4(1.0f); // 矩阵
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
// vec = trans * vec;
float *m = glm::value_ptr(trans); // 返回glm对象的内存地址
for(int i = 0; i < 16; i++){ // 使用指针访问数据
printf("%f\t", m[I]);
if((i+ 1)%4==0){
printf("\n");
}
}
printf("\n=================\n");
glm::vec4 c= trans[3]; // 使用glm对象访问数据
printf("%f,%f,%f,%f\n", c.x, c.y, c.z,c.w);
return 0;
}
// g++ glm01_mat.cpp -omain
完整代码结构
- 公用glfw封装头文件
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
GLFWwindow* initContext(); // 上下文初始化
void destroyConext();
GLboolean initOpenGL(); // OpenGL初始化与加载
////////////////////////////////
#endif
- 公用glfw封装实现
#include "common.h"
// 上下文初始化
GLFWwindow* initContext(){
if(!glfwInit()){
printf("GLFW初始化失败!\n");
return NULL;
}
// 设置提示
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
GLFWwindow* win = glfwCreateWindow(600,400, "OpenGL着色器", NULL, NULL);
if(! win){
printf("创建窗体失败!\n");
return NULL;
}
// 设置当前调用线程的上下文为win;
glfwMakeContextCurrent(win);
return win;
}
void destroyConext(){
glfwTerminate();
}
// OpenGL初始化与加载
GLboolean initOpenGL(){
if(glewInit() != GLEW_OK){ // GLEW_OK:#define GLEW_OK 0
printf("OpenGL加载失败!\n");
return GL_FALSE;
}
return GL_TRUE;
}
- 主体结构实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include "common.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <glm/vec3.hpp> // glm::vec3
#include <glm/vec4.hpp> // glm::vec4
#include <glm/mat4x4.hpp> // glm::mat4
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
#include <glm/ext/scalar_constants.hpp> // glm::pi
#include <glm/gtc/type_ptr.hpp>
GLuint yqData(); // 数据准备
GLuint yqIndecies(); // 索引
void yqTexture(GLuint textureID[2]); // 纹理
GLuint yqShader(); // GLSL
void transform(GLuint pid, GLfloat angle, GLfloat sx, GLfloat sy, GLfloat sz); // 变换
int main(int argc, char const *argv[]){
GLFWwindow *win = initContext();
if(!win){
return -1;
}
if(!initOpenGL()){
destroyConext();
return -1;
}
GLuint arrayID = yqData();
glBindVertexArray(arrayID); // 必须开启顶点分组
GLuint indeciesID = yqIndecies();
glBindVertexArray(0); // 关闭顶点分组
GLuint programmID = yqShader();
GLuint textureID[2];
yqTexture(textureID);
/**
* 需要把纹理对象传递给Shader处理
*/
// 获取Shader中的sTexture对象
// 激活Shader程序
glUseProgram(programmID); // 已经激活,就无需再激活
// 获取Shader程序中
GLint location = glGetUniformLocation(programmID, "sTexture");
glUniform1i(location, 0); // 设置纹理对象为索引为0,
// 纹理也是与顶点类似,使用所用管理,纹理与顶点的管理采用16个单元完成,使用都需要激活
// 顶点激活:glVertexAttribPointer
// 纹理激活:glActiveTexture
GLint location_2 = glGetUniformLocation(programmID, "sTexture_2");
glUniform1i(location_2, 1); // 设置纹理对象为索引为0,
glUseProgram(0); // 关闭Shader程序
GLdouble oldTime = glfwGetTime();
GLfloat angle = 0.0f;
while(!glfwWindowShouldClose(win)){
if(glfwGetTime() - oldTime > 0.1){
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(arrayID); // 绑定顶点分组
glUseProgram(programmID); // 使用Shader
transform(programmID, angle, 1.0f, 1.0f, 1.0f);
angle -= 1.0f;
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
glUseProgram(0); // 解除使用Shader
glBindVertexArray(0); // 接触顶点分组
glfwSwapBuffers(win);
oldTime = glfwGetTime();
}
// glfwWaitEvents();
glfwPollEvents();
}
destroyConext();
return 0;
}
GLuint yqData(){
// 顶点属性数组
GLuint arrayID;
glGenVertexArrays(1, &arrayID);
glBindVertexArray(arrayID);
// 数据(顶点坐标 + 纹理坐标)
GLfloat vertices[] = { // 纹理坐标按照(0,0) - (1,1)之间的4个点确定
0.0f, 0.8f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-0.3f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
};
// 数据缓冲
GLuint bufferID;
glGenBuffers(1, &bufferID);
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices), vertices, GL_STATIC_DRAW);
// 顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), NULL);
glEnableVertexAttribArray(0);
// 文理属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (const void *)(3 * sizeof(GLfloat))); // 顶点属性(输入):注意location=3,对应的顶点索引也是3
glEnableVertexAttribArray(1);
// 颜色索引
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (const void *)(5 * sizeof(GLfloat))); // 顶点属性(输入):注意location=3,对应的顶点索引也是3
glEnableVertexAttribArray(2);
// 关闭顶点分组的操作
glBindVertexArray(0); // 要使用再切换回来
return arrayID;
}
GLuint yqIndecies(){
unsigned int indices[] = { // 注意绘制的顺序
0, 1, 2,
0, 1, 3,
1, 2, 3,
0, 2, 3
};
GLuint indexID;
glGenBuffers(1, &indexID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexID); // 指定索引缓冲的类型
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 拷贝索引数据
return indexID;
}
GLuint yqShader(){
// 纹理坐标的传递,纹理坐标的使用
const char *vertexShaderSource = ""
"#version 410 core\n"
"layout (location = 0) in vec3 aPos;\n" // 顶点坐标
"layout (location = 1) in vec2 aTexture;\n" // 纹理坐标
"layout (location = 2) in vec4 aColor;\n" // 颜色坐标
"out vec2 vTexture;\n" // 传递纹理坐标到片着色器
"out vec4 vColor;\n" // 传递颜色坐标到片着色器
"uniform mat4 transform;\n"
"void main(){\n"
" gl_Position = transform * vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
" vTexture = aTexture;\n" // 输出到下一个着色器
" vColor = aColor;\n" // 输出到下一个着色器
"}\0"; // 空字符
const char *fragmentShaderSource = ""
"#version 410 core\n"
"out vec4 FragColor;\n"
"in vec2 vTexture;\n" // 上面顶点着色器传递过来的纹理坐标,用来生成采样
"in vec4 vColor;\n" // 上面顶点着色器传递过来的纹理坐标,用来生成采样
"uniform sampler2D sTexture;\n"
"uniform sampler2D sTexture_2;\n"
"void main(){\n"
" FragColor = mix(texture(sTexture, vTexture), texture(sTexture_2, vTexture), 0.2) * vColor;\n" // 采样
"}\n\0";
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// glUseProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
void yqTexture(GLuint textureID[2]){
// 创建一个纹理ID
glGenTextures(1, textureID);
// 绑定纹理ID到纹理的管理数据
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID[0]); //指定内存的纹理类型
// 使用第三方库加载图像
int width, height, depth;
unsigned char *data = stbi_load("texture.png", &width, &height, &depth, 0);
printf("图像大小:(%d,%d,%d)\n", width, height, depth);
// 设置纹理使用的图像数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D); // 生成类型为2D的纹理映射
// 释放图像
stbi_image_free(data);
//////////////////////////////////////////////////
// 绑定纹理ID到纹理的管理数据
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureID[1]); //指定内存的纹理类型
// 使用第三方库加载图像
data = stbi_load("arrow.jpeg", &width, &height, &depth, 0);
printf("图像大小:(%d,%d,%d)\n", width, height, depth);
// 设置纹理使用的图像数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D); // 生成类型为2D的纹理映射
// 释放图像
stbi_image_free(data);
}
void transform(GLuint pid, GLfloat angle, GLfloat sx, GLfloat sy, GLfloat sz){
/**
* 这里使用的是专门遵循GLSL标准的数学运算库:GLM,该库的使用可以从上面头文件的说明得到;
* 官方网页说明速度慢,更加详细的说明可以参考官方github文档:https://github.com/g-truc/glm/blob/master/manual.md
*/
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::rotate(trans, glm::radians(angle), glm::vec3(sx, sy, sz));
GLfloat r_sin = sinf(angle * (3.14159f / 180.0f));
GLfloat r_cos = cosf(angle * (3.14159f / 180.0f));
GLfloat dis = sqrt(sx * sx + sy * sy + sz * sz);
sx /= dis;
sy /= dis;
sz /= dis;
GLfloat rotate[] ={
sx*sx*(1 - r_cos) + r_cos, sy*sx*(1 - r_cos) + sz * r_sin, sz*sx*(1 - r_cos) - sy * r_sin, 0.0f,
sx*sy*(1 - r_cos) - sz * r_sin, sy*sy*(1 - r_cos) + r_cos, sz*sy*(1 - r_cos) + sx * r_sin, 0.0f,
sx*sz*(1 - r_cos) + sy * r_sin, sy*sz*(1 - r_cos) - sx * r_sin, sz*sz*(1 - r_cos) + r_cos, 0.0f,
0.0f, 0.0f , 0.0f, 1.0f
};
unsigned int transformLoc = glGetUniformLocation(pid, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, rotate);
}
// g++ -o main gl03_rotate.cpp common.cpp -l glfw -l glew -framework opengl
-
效果
3D旋转运行效果