[OpenGL] 植被的动画和笔刷效果

资源来自Unreal商店

 

植被的渲染

        植被的模型是通过建模导入得到的,本身是由多个面片组成的。

        整个植被包含了albedo贴图+法线贴图+mask贴图。

        我们使用一张mask贴图来完成透明测试,丢弃额外的像素。同时,需要关闭背面剔除,避免移动视角后植被消失。

默认渲染

        

透明测试渲染

 

植被的动画

        接下来给植被加入一些随风轻微晃动的效果。此处的计算比较简单,就是使其沿着法线方向和垂直法线的方向做正弦周期摆动。为了避免整个植被都在晃动,有一种“站不稳”的感觉,此处设定了晃动幅度与y轴坐标相关联,也就是越高的地方,晃动幅度越大。

       为了避免每个植被晃动一致,我们可以加入一些随机参数。此处使用worldPos.x变量保证每个植被得晃动周期能够错开。

        此处的计算放在GPU中完成,在顶点着色器中实现,大致的计算如下:

float y = 100 * localPos.y;
y =  y / (1 + y);
y = max(0,  (y - 0.5) / 20);

float ratio = fWindSpeed * time;
vec3 dir1 = v_normal;
vec3 dir2 = cross(v_normal,vec3(0, 1, 0));

float strength = sin(ratio + worldPos.x) * y;
vec3 offset1 = dir1 * strength;
vec3 offset2 = dir2 * strength;
vec3 offset = mix(a1, a2, 0.5);

worldPos = worldPos + offset;

实例化渲染

       渲染大量的植被会给GPU带来一定的负担。像这样大量的、较为一致的物体,可以使用实例化渲染进行优化。

        在OpenGL中,我们使用glDrawElementsInstanced来完成实例化渲染。

        由于每株植被具有一些特殊的信息,比如它们的位置、大小可能是不一致的,我们可以把这些额外的数据绑定到顶点上,一并传递给GPU。

        此处,可以定义一个结构体存储这些特殊信息:

struct SInstanceParam
{
    QVector3D m_pos;
    QVector3D m_scale;
    SInstanceParam() { }
    SInstanceParam(const QVector3D& pos)
        :m_pos(pos), m_scale(QVector3D(1,1,1)) { }
};

       除了额外的顶点数据,我们本身还有一个结构体,存储的是基本的顶点信息:

struct VertexData
{
    QVector3D position;
    QVector3D tangent;
    QVector3D normal;
    QVector2D texcoord;
};

        对于每个Mesh而言,我们使用一个MeshBuffer类来记录它们的buffer数据,里面目前包含了arrayBuf(记录基本顶点信息),indexBuf(记录顶点的索引关系,即哪几个点构成一个三角形),以及我们新加入的instanceBuf(记录实例化渲染中,额外的一些顶点数据)。也包括了实例化数据的初始化、重新分配以及更新的方法。

struct MeshBuffer
{
    QOpenGLBuffer arrayBuf;
    QOpenGLBuffer indexBuf;
    QOpenGLBuffer instanceBuf;
    int vertexNum = 0;
    int indiceNum = 0;
    int instanceNum = 1;
    MeshBuffer() : indexBuf(QOpenGLBuffer::IndexBuffer)
    {
        arrayBuf.create();
        indexBuf.create();
        instanceBuf.create();
    }

    ~MeshBuffer()
    {
        arrayBuf.destroy();
        indexBuf.destroy();
        instanceBuf.destroy();
    }

    bool IsInit()
    {
        return vertexNum && indiceNum;
    }

    void Init(VertexData* vertex, int num)
    {
        vertexNum = num;
        arrayBuf.bind();
        arrayBuf.allocate(vertex, vertexNum * static_cast<int>(sizeof(VertexData)));
    }

    void Init(TerrainVertexData* vertex, int num)
    {
        vertexNum = num;
        arrayBuf.bind();
        arrayBuf.allocate(vertex, vertexNum * static_cast<int>(sizeof(TerrainVertexData)));
    }

    void Init(SInstanceParam* data, int num)
    {
        instanceNum = num;
        instanceBuf.bind();
        instanceBuf.allocate(data, num * static_cast<int>(sizeof(SInstanceParam)));
    }

    void Init(GLushort* indice, int num)
    {
        indiceNum = num;
        indexBuf.bind();
        indexBuf.allocate(indice, indiceNum * static_cast<int>(sizeof(GLushort)));
    }

    void bind()
    {
        arrayBuf.bind();
        indexBuf.bind();
    }

    void bindInstance()
    {
        instanceBuf.bind();
    }

    void realloc(VertexData* vertex, int num, int offset = 0)
    {
        arrayBuf.bind();
        arrayBuf.write(offset, vertex, num * static_cast<int>(sizeof(VertexData)));
    }

    void realloc(TerrainVertexData* vertex, int num, int offset = 0)
    {
        arrayBuf.bind();
        arrayBuf.write(offset, vertex, num * static_cast<int>(sizeof(TerrainVertexData)));
    }

    void realloc(GLushort* indice, int num, int offset = 0)
    {
        indexBuf.bind();
        indexBuf.write(offset, indice, num * static_cast<int>(sizeof(GLushort)));
    }

    void realloc(SInstanceParam* data, int num)
    {
        instanceNum = num;
        instanceBuf.destroy();
        instanceBuf.create();
        instanceBuf.bind();
        instanceBuf.allocate(data, num * static_cast<int>(sizeof(SInstanceParam)));
    }

    GLuint updateInstance(QString name, int offset, int size, int totalSize, QOpenGLShaderProgram* program, QOpenGLExtraFunctions* gl, bool bFinish)
    {
        instanceBuf.bind();
        GLuint location = static_cast<GLuint>(program->attributeLocation(name));
        gl->glEnableVertexAttribArray(location);

        gl->glVertexAttribPointer(location, size, GL_FLOAT, GL_FALSE, totalSize, (void*)offset);
        gl->glVertexAttribDivisor(location,1);
        return location;
    }
};

        我们定义一个模型(Model) 是多个Mesh的组合,每个对象包含了一个模型,每次点击地面新增植被的时候,我们更新对应的实例数据,这些数据以数组的形式存在。

        也就是说,如果我们有n个不同类型的植被,我们将创建n个模型。每个模型包含了该类型植被的多个实例,这些实例的数据都存储在MeshBuffer的instanceBuf中。

         此处定义instanceNum,大于等于0时才开启实例化渲染,否则默认正常渲染。

class Model
{
private:
    int instanceNum = -1;
    vector<Mesh*> vecMesh;
public:
    // 每次鼠标点击地面的时候,调用一下这个函数,更新实例化数据
    void SetInstanceData(vector<SInstanceParam>& data)
    {
        int num = static_cast<int>(data.size());

        instanceNum = num;
        for(auto& mesh : vecMesh)
        {
            mesh->buffer->realloc(data.data(), num);
        }
    }
    // ...
}

        接下来是渲染部分,我们传入基本的顶点数据后,绑定实例参数的buffer,并传入数据。


void GeometryEngine::drawObj(MeshBuffer* meshBuffer, QOpenGLShaderProgram* program, bool bTess)
{
    meshBuffer->bind();

    auto gl = QOpenGLContext::currentContext()->extraFunctions();

    int offset = 0;

    int vertexLocation = program->attributeLocation("a_position");
    program->enableAttributeArray(vertexLocation);
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));

    offset += sizeof(QVector3D);

    int tangentLocation = program->attributeLocation("a_tangent");
    program->enableAttributeArray(tangentLocation);
    program->setAttributeBuffer(tangentLocation, GL_FLOAT, offset, 3, sizeof(VertexData));

    offset += sizeof(QVector3D);

    int normalLocation = program->attributeLocation("a_normal");
    program->enableAttributeArray(normalLocation);
    program->setAttributeBuffer(normalLocation, GL_FLOAT, offset, 3, sizeof(VertexData));

    offset += sizeof(QVector3D);

    int texcoordLocation = program->attributeLocation("a_texcoord");
    program->enableAttributeArray(texcoordLocation);
    program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));

    offset = 0;
    if(meshBuffer->instanceNum >= 1)
    {
        GLuint loc1 = meshBuffer->updateInstance("a_offset", offset, 3, sizeof(SInstanceParam), program, gl, false);
        offset += sizeof(QVector3D);
        GLuint loc2 = meshBuffer->updateInstance("a_scale", offset, 3, sizeof(SInstanceParam), program, gl, true);

        gl->glDrawElementsInstanced(GL_TRIANGLES, meshBuffer->indiceNum, GL_UNSIGNED_SHORT, nullptr, meshBuffer->instanceNum);
        gl->glVertexAttribDivisor(loc1, 0);
        gl->glVertexAttribDivisor(loc2, 0);
    }
    else if(meshBuffer->instanceNum == -1)
    {
         // ...
         // 此处为正常渲染模块
    }
}

        在对应的着色器中,我们可以像使用普通顶点数据一样,使用传入的实例化数据,并且能确保每个植被都有自己独立的变量:

in vec3 a_offset;
in vec3 a_scale;

点选创建效果

        图中实现了点击地面后创建植被的效果,通过鼠标点选地面计算对应世界坐标的方法已在地形画刷一文中介绍过。此处使用的是记录相机空间的深度信息,然后调用OpenGL的内置API,从深度图中读取鼠标点击位置的深度,通过矩阵计算还原出对应的坐标。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用OpenGL绘制动画效果茶壶,可以按照以下步骤进行: 1. 加载茶壶模型 使用OpenGL的模型加载库,例如Assimp,加载茶壶模型。 2. 设置视图和投影矩阵 使用OpenGL的矩阵库,例如glm,设置视图和投影矩阵。 3. 设置光照 使用OpenGL的光照库,例如glLight,设置光照。 4. 绘制茶壶 使用OpenGL的绘制函数,例如glBegin和glEnd,绘制茶壶。 5. 实现动画效果 使用OpenGL的定时器,例如glutTimerFunc,实现动画效果。可以通过改变茶壶的位置,旋转角度或者大小来实现动画效果。 下面是一个使用OpenGL绘制动画效果茶壶的示例代码: ```c++ #include <GL/glut.h> #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #include <iostream> #include <vector> #include <assimp/Importer.hpp> #include <assimp/scene.h> #include <assimp/postprocess.h> using namespace std; // 定义茶壶模型 struct Model { vector<GLfloat> vertices; vector<GLfloat> normals; }; Model teapotModel; // 定义茶壶的位置和旋转角度 GLfloat teapotX = 0.0f; GLfloat teapotY = 0.0f; GLfloat teapotZ = -5.0f; GLfloat teapotAngle = 0.0f; // 加载茶壶模型 void loadTeapotModel() { Assimp::Importer importer; const aiScene* scene = importer.ReadFile("teapot.obj", aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs); if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { cout << "Error: Failed to load teapot model!" << endl; return; } aiMesh* mesh = scene->mMeshes[0]; for (int i = 0; i < mesh->mNumFaces; i++) { aiFace face = mesh->mFaces[i]; for (int j = 0; j < 3; j++) { aiVector3D vertex = mesh->mVertices[face.mIndices[j]]; aiVector3D normal = mesh->mNormals[face.mIndices[j]]; teapotModel.vertices.push_back(vertex.x); teapotModel.vertices.push_back(vertex.y); teapotModel.vertices.push_back(vertex.z); teapotModel.normals.push_back(normal.x); teapotModel.normals.push_back(normal.y); teapotModel.normals.push_back(normal.z); } } } // 初始化OpenGL void init() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_DEPTH_TEST); loadTeapotModel(); } // 绘制茶壶 void drawTeapot() { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glVertexPointer(3, GL_FLOAT, 0, &teapotModel.vertices[0]); glNormalPointer(GL_FLOAT, 0, &teapotModel.normals[0]); glDrawArrays(GL_TRIANGLES, 0, teapotModel.vertices.size() / 3); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } // 绘制场景 void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); glMultMatrixf(glm::value_ptr(view)); glm::mat4 projection = glm::perspective(glm::radians(45.0f), 1.0f, 0.1f, 100.0f); glMultMatrixf(glm::value_ptr(projection)); glTranslatef(teapotX, teapotY, teapotZ); glRotatef(teapotAngle, 0.0f, 1.0f, 0.0f); drawTeapot(); glutSwapBuffers(); } // 更新动画 void update(int value) { teapotAngle += 1.0f; if (teapotAngle > 360.0f) { teapotAngle = 0.0f; } glutPostRedisplay(); glutTimerFunc(16, update, 0); } // 处理键盘事件 void keyboard(unsigned char key, int x, int y) { switch (key) { case 'w': teapotY += 0.1f; break; case 's': teapotY -= 0.1f; break; case 'a': teapotX -= 0.1f; break; case 'd': teapotX += 0.1f; break; case 'q': teapotZ += 0.1f; break; case 'e': teapotZ -= 0.1f; break; } glutPostRedisplay(); } // 主函数 int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL Teapot Animation"); init(); glutDisplayFunc(display); glutTimerFunc(0, update, 0); glutKeyboardFunc(keyboard); glutMainLoop(); return 0; } ``` 在该示例代码中,我们使用了Assimp库加载茶壶模型,使用了glm库设置视图和投影矩阵,使用了glut库实现定时器和键盘事件处理。在display函数中,我们使用glTranslatef和glRotatef函数分别改变茶壶的位置和旋转角度。在update函数中,我们使用glutTimerFunc函数实现动画效果。在keyboard函数中,我们处理键盘事件,改变茶壶的位置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值