OpenGL-你好,正方形

2020-07-10  本文已影响0人  卖馍工程师

案例练习,在窗口绘制一个正方形,并且用方向键控制移动

如果你是初学者,请先阅读 案例--绘制三角形,这篇文章里会对基本函数做详细的解读。这里,我们就不再赘述了。

绘制正方形与绘制三角形基本流程是一样的,不同的是,正方形有4个顶点,三角形有3个顶点,我们只需要在绘制三角形的基础上做一些小小的修改。

为了方便后面的正方形移动,我们事先定义正方形4个顶点到中心点的距离为blockSize

GLfloat blockSize = 0.1f;

由图很容易可以看出,那么正方形的边长为:blockSize *2, 即0.2。

修改顶点数组:

//正方形四个点的坐标
GLfloat vVerts[] = {
    -blockSize, -blockSize, 0.0f,   // A点
    blockSize, -blockSize, 0.0f,    // B点
    blockSize, blockSize, 0.0f,     // C点
    -blockSize, blockSize, 0.0f,    // D点
};

修改setupRC函数中图元连接方式:

//将 GL_TRIANGLES(三角形) 修改为 GL_TRIANGLE_FAN (正方形),4个顶点
triangleBatch.Begin(GL_TRIANGLE_FAN, 4);

只需要对三角形案例修改上面的代码,我们就可以得到了正方形

绘制正方形

接下来实现通过键位来控制正方形移动

案例目标:通过 键盘上的“ 上 下 左 右 ”按钮来控制正方形的移动

要想通过键盘来控制图形,我们需要在main函数中注册一个函数。

glutSpecialFunc(SpecialKeys);

当我们通过键位控制图形的时候,会在SpecialKeys函数中回调。

对于正方形ABCD,当我们移动ABCD的时候,ABCD四个顶点的位置会发生变化,但是ABCD四个顶点的相对位置,并不会改变,我们可以以D点为例,计算D点移动之后D‘的位置信息,最后 通过四个顶点之间的相对位置关系,计算出其他顶点更新之后的位置信息。

首先定义移动步长:

//步长
GLfloat stepSize = 0.025f;

当D点向上移动一个键位时,此时D’点坐标x保持不变,y增加一个步长;
当D点向下移动一个键位时,此时D’点坐标x保持不变,y减少一个步长;
当D点向左移动一个键位时,此时D’点坐标y保持不变,x减少一个步长;
当D点向右移动一个键位时,此时D’点坐标y保持不变,x增加一个步长;

实现代码如下:

//key 枚举值,x、y是位置
void SpecialKeys(int key, int x, int y){
    //步长
    GLfloat stepSize = 0.025f;
    
    //取出参考点D点的坐标的x,y值
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[10];
    
    //根据移动方向,更新D点移动后的坐标
    if (key == GLUT_KEY_UP) {
        blockY += stepSize;
    }
    if (key == GLUT_KEY_DOWN) {
        blockY -= stepSize;
    }
    if (key == GLUT_KEY_LEFT) {
        blockX -= stepSize;
    }
    if (key == GLUT_KEY_RIGHT) {
        blockX += stepSize;
    }
  
    //根据相对位置信息更新其他顶点坐标
    // A点新坐标
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize * 2;
    
    // B点新坐标
    vVerts[3] = blockX + blockSize * 2;
    vVerts[4] = blockY - blockSize * 2;

    // C点新坐标
    vVerts[6] = blockX + blockSize * 2;
    vVerts[7] = blockY;

    // D点新坐标
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    printf("(%f,%f)\n",vVerts[9],vVerts[10]);
    
    //更新顶点数据
    triangleBatch.CopyVertexData3f(vVerts);
    
    //重新渲染提交 --> RenderScene
    glutPostRedisplay();
    
}

至此,我们可以完成了,可以通过键位控制正方形移动的功能。

运行代码一直向右移动,正方形移出了窗口边界。

正方形移动出边界了

通常情况下,这不是我们想要的结果,我们要做边界碰撞检测。

在规范化坐标系下,坐标系统是在-1.0~1.0之间的。

当正方形移动到最左边的时候,实际上就是 blockX 到达-1.0点;
当正方形移动到最右边的时候,实际上是 blockX + 边长 到达了1.0点;
当正方形移动到最上边的时候,实际上是 blockY 到达了-1.0点;
当正方形移动到最下边的时候,实际上是 blockY + 边长到达了1.0点

所以我们添加边界检测代码,如下:

//key 枚举值,x、y是位置
void SpecialKeys(int key, int x, int y){
    //步长
    GLfloat stepSize = 0.025f;
    
    //取出参考点D点的坐标的x,y值
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[10];
 
    //根据移动方向,更新D点移动后的坐标
    if (key == GLUT_KEY_UP) {
        blockY += stepSize;
    }
    if (key == GLUT_KEY_DOWN) {
        blockY -= stepSize;
    }
    if (key == GLUT_KEY_LEFT) {
        blockX -= stepSize;
    }
    if (key == GLUT_KEY_RIGHT) {
        blockX += stepSize;
    }
    
    //触碰到边界(4个边界)的处理
    
    //当正方形移动超过最左边的时候
    if (blockX < -1.0f) {
        blockX = -1.0f;
    }
    //当正方形移动到最右边时
    //1.0 - blockSize * 2 = 总边长 - 正方形的边长 = 最左边点的位置
    if (blockX > (1.0f - blockSize * 2)) {
        blockX = 1.0f - blockSize * 2;
    }
    //当正方形移动到最下面时
    //-1.0 - blockSize * 2 = Y(负轴边界) - 正方形边长 = 最下面点的位置
    if (blockY < -1.0f + blockSize * 2) {
        blockY = -1.0f + blockSize * 2;
    }
    //当正方形移动到最上面时
    if (blockY > 1.0f) {
        blockY = 1.0f;
    }
   
    //根据相对位置信息更新其他顶点坐标
    // A点新坐标
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize * 2;
    // B点新坐标
    vVerts[3] = blockX + blockSize * 2;
    vVerts[4] = blockY - blockSize * 2;
    // C点新坐标
    vVerts[6] = blockX + blockSize * 2;
    vVerts[7] = blockY;
    // D点新坐标
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    
    //更新顶点数据
    triangleBatch.CopyVertexData3f(vVerts);
    
    //重新渲染提交 --> RenderScene
    glutPostRedisplay();
    
}

至此,边界碰触问题得到了解决。

此案例中我们用到了4个顶点,通过计算其中一个顶点位置信息,根据顶点间相对位置计算其他顶点位置信息。那么,当有100个,1000个顶点的时候呢?

显然是不能够这样做的,为此,我们引入变换矩阵,下面我们利用变换矩阵来实现上面的案例。

利用矩阵实现

我们可以根据x,y移动的距离, 通过平移矩阵 图形顶点位置 * 平移矩阵 = 移动后的图形顶点位置,得到最终的效果。

我们对上面的实现,做一些小小的修改。

首先我们不再需要计算每一个顶点的移动距离,我们只需要记录图形在x,y方向上的整体平移距离。

// 记录移动图形时,在x轴上平移的距离
GLfloat xPos = 0.0f;
// 记录移动图形时,在y轴上平移的距离
GLfloat yPos = 0.0f;
// 步长
GLfloat stepSize = 0.025f;

利用矩阵,对于“上下左右”的移动过程中的距离计算是一样的

  if (key == GLUT_KEY_UP) {
       yPos += stepSize;
  }
    
  if (key == GLUT_KEY_DOWN) {
        yPos -= stepSize;
  }
    
  if (key == GLUT_KEY_LEFT) {
        xPos -= stepSize;
  }
    
  if (key == GLUT_KEY_RIGHT) {
        xPos += stepSize;
  }

不同的是对于边界检测,我们要换个思维来理解一下

我们可以将平移矩阵变化过程中的图形平移的距离 理解成 根据图形中心点 来进行移动的,所以我们要计算的移动距离就是两个中心点之间的平移距离。

当正方形中心点移动到最左边的时候,实际上正方形的最左边已经超出了屏幕的一半边长值,xPos走多了,所以要让xPos - blockSize > -1.0始终成立;即当xPos < (-1.0 + blockSize)时,让其等于(-1.0 + blockSize)
当正方形中心点移动到最右边的时候,实际上正方形的最右边已经超出了屏幕的一半边长值,xPos走多了,所以要让xPos + blockSize < 1.0始终成立;即当xPos > 1.0- blockSize时,让其等于1.0-blockSize
当正方形中心点移动到最上边的时候,实际上正方形的最上边已经超出了屏幕的一半边长值,yPos走多了,所以要让yPos + blockSize < 1.0始终成立;即当yPos > 1.0 - blockSize时,让其等于1.0 - blockSize
当正方形中心点移动到最下边的时候,实际上正方形的最下边已经超出了屏幕的一半边长值,yPos走多了,所以要让yPos -blockSize > -1.0始终成立;即当yPos < -1.0 + blockSize 时,让其等于-1.0 + blockSize

综上,SpecialKeys函数中的完整代码如下:

//使用矩阵方式(一起搞定),不需要修改每个顶点,只需要记录移动步长,碰撞检测
void SpecialKeys(int key, int x, int y){
    // 步长
    GLfloat stepSize = 0.025f;
    
    if (key == GLUT_KEY_UP) {
        yPos += stepSize;
    }
    
    if (key == GLUT_KEY_DOWN) {
        yPos -= stepSize;
    }
    
    if (key == GLUT_KEY_LEFT) {
        xPos -= stepSize;
    }
    
    if (key == GLUT_KEY_RIGHT) {
        xPos += stepSize;
    }
    
    //碰撞检测 xPos是x轴上平移距离, yPos是y轴上平移距离
    if (xPos < (-1.0f + blockSize)) {  
        xPos = -1.0f + blockSize;
    }
    
    if (xPos > (1.0f - blockSize)) {
        xPos = 1.0f - blockSize;
    }
    
    if (yPos < (-1.0f + blockSize)) {
        yPos = -1.0f + blockSize;
    }
    
    if (yPos > (1.0f - blockSize)) {
        yPos = 1.0f - blockSize;
    }
    
    glutPostRedisplay();
    
}

至此,对于正方形的移动以及边缘碰就做好了,下面我们要应用到着色器中,在三角形以及上面的实现方法时我们用到了 单元着色器 ,当使用平移矩阵的时候,我们需要用到 平面着色器 ,来传入相应的变换矩阵(关于着色器类型的了解,可以阅读 渲染架构中关于固定着色器的介绍

修改代码如下:

//开始渲染
void RenderScene(void)

{
    //1.清除一个或者一组特定的缓存区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //2.设置颜色RGBA
    GLfloat vRed[] = {1.0f, 0.5f, 0.0f, 1.0f};

    //定义矩阵
    M3DMatrix44f mTransformMatrix;
    
    //平移矩阵
    m3dTranslationMatrix44(mTransformMatrix, xPos, yPos, 0.0f);
    
    //使用平面着色器
    //参数1:存储着色器类型
    //参数2:使用什么矩阵变换
    //参数3:颜色
    shaderManager.UseStockShader(GLT_SHADER_FLAT, mTransformMatrix, vRed);
    
    //提交着色器
    triangleBatch.Draw();
    glutSwapBuffers();
}

至此,利用平移矩阵对于此案例的实现就完成了。

上一篇下一篇

猜你喜欢

热点阅读