3. 实例化-画100个正方体
目录概述
数据的传递流程
(1)准备顶点属性缓冲区
-
positionVBO:用于存放正方体顶点位置的缓冲区,一个正方体需要24个顶点位置来描述,一个顶点位置三个数(x,y,z)
-
colorVBO:用于存放正方体颜色的缓冲区,每个正方体一种颜色,这里一共有100个正方体,一个颜色四个数(x,y,z,a)
-
mvpVBO:用于存放正方体的mvp变换矩阵的缓冲区,每个正方体一个mvp变换矩阵,这里一共有100个正方体
-
indicesIBO:用于存放正方体顶点顺序的缓冲区,比如它为{0,2,4,6,8,10},那么就从positionVBO中取出下标为{0,2,4,6,8,10}的顶点位置,画两个三角形(一个三角形三个顶点);一个正方体需要12个三角形来描述,所以它为36个indices
(2)计算mvp矩阵
(3)将数据传给顶点着色器以及片段着色器来画图
图形学原理
1. 齐次坐标
齐次坐标(x,y,z,w),它是为了兼容点的平移操作,使得我们可以用同一个公式对点和方向作运算。
(x,y,z,w)同时除于w得到坐标(x/w,y/w,x/w)
-
当w == 1时,向量(x,y,z,1)为空间中的点
-
当w == 0时,向量(x,y,z,0)为方向
齐次坐标主要是兼容点的平移操作,在空间中平移方向是没有意义的:
2. 二维坐标间的转换
1. 二维旋转矩阵
把点(x,y)旋转到点(x',y')
2. 先平移后旋转,以及先旋转后平移问题
也就是矩阵乘法的顺序问题,设旋转矩阵为R,平移矩阵为T
-
先平移后旋转:M = R * T
-
先旋转后平移:M = T * R
3. 二维坐标转换
在xy坐标系中,有一点P(x0,y0),表示的是:点P(x0,y0)相对于xy坐标系原点的值为x0和y0。
转换到x'y'坐标系之后,变为P(x0',y0'),表示的是:点P(x0',y0')相对于x'y'坐标系原点的值为x0'和y0'
它们之间的相对位置时不变的,只是换了一种表示方法。
就比如:小明说,杯子在我的右边;小东说,杯子在我的左边;是一样的道理。这里就是把(杯子在我右边)转换为(杯子在我左边)
(1)为了将对象描述从xy坐标变换到x'y'坐标,必须建立把x'y'轴叠加到xy轴的变换,这需要分两步进行:
-
将x'y'系统的坐标原点(x0,y0)平移到xy系统的原点(0,0);
-
将x'轴旋转到x轴上
所以,就是先平移后旋转:M = R * T
举例:设x'轴与x轴之间的夹角为45度,x'y'系统的坐标原点为(2,2),将点P(1,1)变换到x'y'系统上,由几何关系可以得到变换后P点坐标的值为(-√2,0)
(2)任何旋转矩阵的元素可以表示为一组正交单位向量的元素
4. 旋转矩阵的逆矩阵
旋转矩阵的逆矩阵可以通过矩阵转置,或者将旋转角取负值来获得
3. 三维坐标间转换
1. 三维坐标绕轴旋转
2. 轴角与旋转矩阵
(1)轴角:绕一个给定轴K(x,y,z)(向量)旋转给定角度。也就是原定坐标轴{A}绕给定向量K(x,y,z)旋转给定角度后,得到坐标系{B}
注意:向量K(x,y,z)为单位向量
它的旋转矩阵为:
(2)也可以理解为:一个向量V绕着向量K旋转角,得到向量V(rot)
公式的推导:详情请见:https://www.bilibili.com/video/BV1h7411c7zK?from=search&seid=5987430286330119296
推导过程(TODO)
涉及到:欧拉角,四元数,旋转矩阵,轴角之间的关系
4. MVP矩阵
1. 不同的坐标系
-
局部空间(Local Space,或者称为物体空间(Object Space)):物体坐标系
-
世界空间(World Space):世界坐标系
-
观察空间(View Space,或者称为视觉空间(Eye Space)):眼睛坐标系
-
裁剪空间(Clip Space)
-
屏幕空间(Screen Space)
由上图可以知道:
-
M:模型矩阵:将物体坐标变换为世界坐标
-
V:视图矩阵:将世界坐标变换为眼睛坐标
-
P:投影矩阵:将眼睛坐标变换为裁剪坐标
2. 模型矩阵
将物体坐标变换为世界坐标:
3. 视图矩阵
将世界坐标变换为眼睛坐标
4. 投影矩阵
将眼睛坐标变换为裁剪坐标
1. 正交投影
将一个上下坐标为t(top)和b(bottom),前后坐标为n(near)和f(far),左右坐标为l(left)和r(right)的正方体:
-
将其中心移动到坐标原点
-
压缩成边长为2(-1,1)的正方体
2. 齐次坐标不变性
(x,y,z,1)和(kx,ky,kz,k!=0z)和(xz,yz,z^2,z!=0)在三维空间中,这些都代表的是同一个点(x,y,z)
例如:(1,0,0,1)和(2,0,0,2)都代表着点(1,0,0)
3. 透视投影
将左边的梯形体压缩成右边的长方体
注意:n和f是不变的,所以,可以得出:
(1)对于任何在n平面上的点,其坐标的z分量不变
(2)对于任何在f平面上的点,其坐标的z分量不变
从下图可以看出,(x,y,z)坐标和(x',y',z')坐标之间存在相似三角形关系
4. 得出投影矩阵
投影矩阵就是先做透视投影,把梯形体压缩成一个长方体;然后做正交投影,把这个正方体,放到原点,并压缩成边长为2(-1,1)的正方体
OpenGL的为啥为负的?(TODO)
5. 如何求长方体上下左右前后的坐标
给出可视角度fovY,nearZ和长宽比aspect(16:9,或者4:3等等),就可以求出长方体的上下左右前后的坐标了
t(top),b(bottom)= - t,r(right),l(left)= -r,n(nearZ),f(farZ)
然后代入投影矩阵公式,就可以得出投影矩阵的值了。
源码解析
主程序
#include "esUtil.h"
#include <stdlib.h>
#include <math.h>
#include <android/log.h>
#define NUM_INSTANCES 100
#define POSITION_LOC 0
#define COLOR_LOC 1
#define MVP_LOC 2
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "lylesUtil", __VA_ARGS__))
typedef struct
{
GLuint programObject;
GLuint positionVBO;
GLuint colorVBO;
GLuint mvpVBO;
GLuint indicesIBO;
int numIndices;
GLfloat angle[NUM_INSTANCES];
} myUserData;
// 初始化顶点着色器和片段着色器
// 初始化myUserData里面的数据
int Init(MYESContext *myesContext)
{
GLfloat *positions;
GLuint *indices;
myUserData *userData = (myUserData *)myesContext->userData;
char vShaderStr[] =
"#version 300 es \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec4 a_color; \n"
"layout(location = 2) in mat4 a_mvpMatrix; \n"
"out vec4 v_color; \n"
"void main() \n"
"{ \n"
" v_color = a_color; \n"
" gl_Position = a_mvpMatrix * a_position; \n" // 在这里设置mvp变换矩阵
"} \n";
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"in vec4 v_color; \n"
"layout(location = 0) out vec4 outColor; \n"
"void main() \n"
"{ \n"
" outColor = v_color; \n"
"} \n";
userData->programObject = myesLoadProgram(vShaderStr, fShaderStr);
// 1\. 生成正方体的position数据和indices数据
userData->numIndices = myesGenCube(0.1f, &positions, NULL, NULL, &indices);
glGenBuffers(1, &userData->indicesIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->indicesIBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*userData->numIndices, indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
free(indices);
glGenBuffers(1, &userData->positionVBO);
glBindBuffer(GL_ARRAY_BUFFER, userData->positionVBO);
glBufferData(GL_ARRAY_BUFFER, 24*sizeof(GLfloat)*3, positions, GL_STATIC_DRAW);
free(positions);
{
GLubyte colors[NUM_INSTANCES][4];
int instance;
srandom(0);
for (instance = 0; instance < NUM_INSTANCES; instance++) {
colors[instance][0] = random() % 255;
colors[instance][1] = random() % 255;
colors[instance][2] = random() % 255;
colors[instance][3] = 0;
}
glGenBuffers(1, &userData->colorVBO);
glBindBuffer(GL_ARRAY_BUFFER, userData->colorVBO);
glBufferData(GL_ARRAY_BUFFER, NUM_INSTANCES*4, colors, GL_STATIC_DRAW);
}
{
int instance;
for (instance = 0; instance < NUM_INSTANCES; instance++) {
userData->angle[instance] = (float) (random() % 32768) / 32767.0f * 360.0f;
}
glGenBuffers(1, &userData->mvpVBO);
glBindBuffer(GL_ARRAY_BUFFER, userData->mvpVBO);
glBufferData(GL_ARRAY_BUFFER, NUM_INSTANCES * sizeof(ESMatrix), NULL, GL_DYNAMIC_DRAW);
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
return GL_TRUE;
}
// 更新mvp变换矩阵
void Update(MYESContext *myesContext, float deltaTime)
{
myUserData *userData = (myUserData*) myesContext->userData;
ESMatrix *matrixBuf;
ESMatrix perspective;
float aspect;
int instance = 0;
int numRows;
int numColumns;
// 比例=长/高
aspect = (GLfloat) myesContext->width / (GLfloat)myesContext->height;
// 先得到一个单位矩阵
myesMatrixLoadIdentity(&perspective);
// 然后得到投影矩阵P
myesPerspective(&perspective, 90.0f, aspect, 0.1f, 100.0f);
glBindBuffer(GL_ARRAY_BUFFER, userData->mvpVBO);
matrixBuf = (ESMatrix *)glMapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(ESMatrix) * NUM_INSTANCES, GL_MAP_WRITE_BIT);
numRows = (int) sqrtf(NUM_INSTANCES);
numColumns = numRows;
for (instance = 0; instance < NUM_INSTANCES; instance++) {
ESMatrix modelview;
float translateX = ((float)(instance % numRows) / (float)numRows)*2.0f - 1.0f;
float translateY = ((float)(instance/numColumns)/(float)numColumns)*2.0f - 1.0f;
// 先得到一个单位矩阵
myesMatrixLoadIdentity(&modelview);
// 然后将正方体平移到坐标(translateX, translateY, -1.0f),得到模型矩阵M
myesTranslate(&modelview, translateX, translateY, -1.0f);
userData->angle[instance] += (deltaTime*40.0f);
if (userData->angle[instance] >= 360.0f) {
userData->angle[instance] -= 360.0f;
}
// 绕轴(0,0,1)旋转angle角度,得到视图矩阵V,然后和前面的模型矩阵M相乘得到VM矩阵
myesRotate(&modelview, userData->angle[instance], 0.0, 0, 1.0);
// 再乘一下,得到VMP矩阵
myesMatrixMultiply(&matrixBuf[instance], &modelview, &perspective);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
}
void Draw(MYESContext *myesContext)
{
myUserData *userData = (myUserData *)myesContext->userData;
// 视口变换
glViewport(0, 0, myesContext->width, myesContext->height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(userData->programObject);
glBindBuffer(GL_ARRAY_BUFFER, userData->positionVBO);
glVertexAttribPointer(POSITION_LOC, 3, GL_FLOAT, GL_FALSE,
3*sizeof(GLfloat), (const void *)NULL);
glEnableVertexAttribArray(POSITION_LOC);
glBindBuffer(GL_ARRAY_BUFFER, userData->colorVBO);
glVertexAttribPointer(COLOR_LOC, 4, GL_UNSIGNED_BYTE,
GL_TRUE, 4*sizeof(GLubyte), (const void *)NULL);
glEnableVertexAttribArray(COLOR_LOC);
// void glVertexAttribDivisor (GLuint index, GLuint divisor);
// 指示OpenGL ES对每个实例(instance)读取一次或者多次顶点属性。
// divisor为1,表示每个图元实例(每个正方体)读取一次顶点属性,相当于指针P+1这样子
// divisor为0,则是(每个顶点),读取一次顶点属性
glVertexAttribDivisor(COLOR_LOC, 1);
glBindBuffer(GL_ARRAY_BUFFER, userData->mvpVBO);
// 对于4x4矩阵,需要消耗4个顶点属性来存储它们
glVertexAttribPointer(MVP_LOC + 0, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)NULL);
glVertexAttribPointer(MVP_LOC + 1, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)(sizeof(GLfloat)*4));
glVertexAttribPointer(MVP_LOC + 2, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)(sizeof(GLfloat)*8));
glVertexAttribPointer(MVP_LOC + 3, 4, GL_FLOAT, GL_FALSE, sizeof(ESMatrix), (const void *)(sizeof(GLfloat)*12));
glEnableVertexAttribArray(MVP_LOC + 1);
glEnableVertexAttribArray(MVP_LOC + 1);
glEnableVertexAttribArray(MVP_LOC + 2);
glEnableVertexAttribArray(MVP_LOC + 3);
glVertexAttribDivisor(MVP_LOC + 0, 1);
glVertexAttribDivisor(MVP_LOC + 1, 1);
glVertexAttribDivisor(MVP_LOC + 2, 1);
glVertexAttribDivisor(MVP_LOC + 3, 1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->indicesIBO);
//void glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount);
// mode表示要渲染的图元;count为绘制的index数量;type为index中的元素索引类型;indices为存放index的地方;
// instancecount为要绘制的图元实例的数量
// 用一次API调用,进行多次渲染具有不同属性(例如不同的变换矩阵、颜色、或者大小)的一个对象
// 这里就是用一次API调用,渲染了100个具有不同变换矩阵、颜色的正方体
glDrawElementsInstanced(GL_TRIANGLES, userData->numIndices, GL_UNSIGNED_INT, (const void *)NULL, NUM_INSTANCES);
}
void Shutdown(MYESContext *myesContext)
{
myUserData *userData = (myUserData *)myesContext->userData;
glDeleteBuffers(1, &userData->positionVBO);
glDeleteBuffers(1, &userData->colorVBO);
glDeleteBuffers(1, &userData->mvpVBO);
glDeleteBuffers(1, &userData->indicesIBO);
glDeleteProgram(userData->programObject);
}
int myesMain(MYESContext *myesContext)
{
myesContext->userData = malloc(sizeof(myUserData));
// 这个width和height是没有用的,userData中的width和height是用的是系统中的
myesCreateWindow(myesContext, "Example 7-1 Instancing", 640, 480, MY_ES_WINDOW_RGB | MY_ES_WINDOW_DEPTH);
if (!Init(myesContext)) {
return GL_FALSE;
}
esRegisterShutdownFunc(myesContext, Shutdown);
esRegisterUpdateFunc(myesContext, Update);
esRegisterDrawFunc(myesContext, Draw);
return GL_TRUE;
}
变换矩阵函数
// 生成单位矩阵
void myesMatrixLoadIdentity(ESMatrix *result)
{
memset(result, 0x0, sizeof(ESMatrix));
result->m[0][0] = 1.0f;
result->m[1][1] = 1.0f;
result->m[2][2] = 1.0f;
result->m[3][3] = 1.0f;
}
// 生成投影矩阵的
void myesPerspective(ESMatrix *result, float fovy, float aspect, float nearZ, float farZ)
{
GLfloat frustumW, frustumH;
frustumH = tanf(fovy / 360.0f * PI) * nearZ;
frustumW = frustumH * aspect;
myesFrustum(result, -frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);
}
void myesFrustum(ESMatrix *result, float left, float right, float bottom, float top, float nearZ, float farZ)
{
float deltaX = right - left;
float deltaY = top - bottom;
float deltaZ = farZ - nearZ;
ESMatrix frust;
if ((nearZ <= 0.0f) || (farZ <= 0.0f) ||
(deltaX <= 0.0f) || (deltaY <= 0.0f) || (deltaZ <= 0.0f)) {
return;
}
frust.m[0][0] = 2.0f * nearZ / deltaX;
frust.m[0][1] = frust.m[0][2] = frust.m[0][3] = 0.0f;
frust.m[1][1] = 2.0f * nearZ / deltaY;
frust.m[1][0] = frust.m[1][2] = frust.m[1][3] = 0.0f;
frust.m[2][0] = (right + left) / deltaX;
frust.m[2][1] = (top + bottom) / deltaY;
frust.m[2][2] = - (nearZ + farZ) / deltaZ;
frust.m[2][3] = -1.0f;
frust.m[3][2] = -2.0f * nearZ * farZ / deltaZ;
frust.m[3][0] = frust.m[3][1] = frust.m[3][3] = 0.0f;
myesMatrixMultiply(result, &frust, result);
}
// 矩阵乘法
void myesMatrixMultiply ( ESMatrix *result, ESMatrix *srcA, ESMatrix *srcB )
{
ESMatrix tmp;
int i;
for ( i = 0; i < 4; i++ )
{
tmp.m[i][0] = ( srcA->m[i][0] * srcB->m[0][0] ) +
( srcA->m[i][1] * srcB->m[1][0] ) +
( srcA->m[i][2] * srcB->m[2][0] ) +
( srcA->m[i][3] * srcB->m[3][0] ) ;
tmp.m[i][1] = ( srcA->m[i][0] * srcB->m[0][1] ) +
( srcA->m[i][1] * srcB->m[1][1] ) +
( srcA->m[i][2] * srcB->m[2][1] ) +
( srcA->m[i][3] * srcB->m[3][1] ) ;
tmp.m[i][2] = ( srcA->m[i][0] * srcB->m[0][2] ) +
( srcA->m[i][1] * srcB->m[1][2] ) +
( srcA->m[i][2] * srcB->m[2][2] ) +
( srcA->m[i][3] * srcB->m[3][2] ) ;
tmp.m[i][3] = ( srcA->m[i][0] * srcB->m[0][3] ) +
( srcA->m[i][1] * srcB->m[1][3] ) +
( srcA->m[i][2] * srcB->m[2][3] ) +
( srcA->m[i][3] * srcB->m[3][3] ) ;
}
memcpy ( result, &tmp, sizeof ( ESMatrix ) );
}
// 平移矩阵
void myesTranslate ( ESMatrix *result, GLfloat tx, GLfloat ty, GLfloat tz )
{
result->m[3][0] += ( result->m[0][0] * tx + result->m[1][0] * ty + result->m[2][0] * tz );
result->m[3][1] += ( result->m[0][1] * tx + result->m[1][1] * ty + result->m[2][1] * tz );
result->m[3][2] += ( result->m[0][2] * tx + result->m[1][2] * ty + result->m[2][2] * tz );
result->m[3][3] += ( result->m[0][3] * tx + result->m[1][3] * ty + result->m[2][3] * tz );
}
// 轴角,旋转矩阵
void myesRotate ( ESMatrix *result, GLfloat angle, GLfloat x, GLfloat y, GLfloat z )
{
GLfloat sinAngle, cosAngle;
GLfloat mag = sqrtf ( x * x + y * y + z * z );
sinAngle = sinf ( angle * PI / 180.0f );
cosAngle = cosf ( angle * PI / 180.0f );
if ( mag > 0.0f )
{
GLfloat xx, yy, zz, xy, yz, zx, xs, ys, zs;
GLfloat oneMinusCos;
ESMatrix rotMat;
x /= mag;
y /= mag;
z /= mag;
xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * sinAngle;
ys = y * sinAngle;
zs = z * sinAngle;
oneMinusCos = 1.0f - cosAngle;
rotMat.m[0][0] = ( oneMinusCos * xx ) + cosAngle;
rotMat.m[0][1] = ( oneMinusCos * xy ) - zs;
rotMat.m[0][2] = ( oneMinusCos * zx ) + ys;
rotMat.m[0][3] = 0.0F;
rotMat.m[1][0] = ( oneMinusCos * xy ) + zs;
rotMat.m[1][1] = ( oneMinusCos * yy ) + cosAngle;
rotMat.m[1][2] = ( oneMinusCos * yz ) - xs;
rotMat.m[1][3] = 0.0F;
rotMat.m[2][0] = ( oneMinusCos * zx ) - ys;
rotMat.m[2][1] = ( oneMinusCos * yz ) + xs;
rotMat.m[2][2] = ( oneMinusCos * zz ) + cosAngle;
rotMat.m[2][3] = 0.0F;
rotMat.m[3][0] = 0.0F;
rotMat.m[3][1] = 0.0F;
rotMat.m[3][2] = 0.0F;
rotMat.m[3][3] = 1.0F;
myesMatrixMultiply ( result, &rotMat, result );
}
}
参考
1. 第三课:矩阵
http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-3-matrices/
2. 旋转矩阵
https://zh.wikipedia.org/wiki/%E6%97%8B%E8%BD%AC%E7%9F%A9%E9%98%B5
3. CG07-3D变换和欧拉角/轴角/四元数
https://www.bilibili.com/video/BV1h7411c7zK?from=search&seid=5987430286330119296
4. GAMES101-现代计算机图形学入门-闫令琪
https://www.bilibili.com/video/BV1X7411F744?p=4
5. 坐标系统
https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/08%20Coordinate%20Systems/
6. View Transform(视图变换)详解
https://www.cnblogs.com/graphics/archive/2012/07/12/2476413.html