cocos2dx - shader實現任意動畫的殘影效果


 本節主要講利用cocos2dx機制實現opengl es shader腳本的繪制

  這里先看下最終效果:

                    

這里分別實現了灰度效果及殘影的效果。

 

一、繪制基類

  這里主要參考了cocos2dx源碼中 RenderTexture 的實現,有興趣的可以了解下。

繪制基類RenderShader主要實現以下方法:

//******************************************************************
// 文件名:    RenderShader.h
// 創建人:    稀飯lei
// 版  本:    1.0
// 描  述:    特效基類
//******************************************************************
#ifndef _RenderShader_H__
#define _RenderShader_H__ 
#include "cocos2d.h"
USING_NS_CC;

// GL紋理坐標組
static const  GLfloat ccRenderTextcord[8] ={
    0.0f, 1.0f,
    1.0f, 1.0f,
    0.0f, 0.0f,
    1.0f, 0.0f,
};
class RenderShader : public Node {
public:
    static RenderShader* create();

    virtual bool LoadByteArray(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray);    //加載 shader文件

    void    begin();                // 用於設置繪制開始要調用的onBegin

    void    end();                    // 用於設置繪制結束要調用的onEnd

    void    push2Draw(Node* node);  // 添加待繪制節點

    virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;  // cocos2dx繪制回調

protected:
    virtual void onBegin();

    virtual void onEnd();

    //virtual void onClear();

    virtual void Render();                // 繪制實現


    virtual bool init();

    virtual void CreateFrameBuffer();    //創建FBO及texture

    RenderShader(void);

    virtual ~RenderShader(void);
 

    GLuint m_nFrameBuffer;            // 用於當前繪制的fbo
 GLuint m_nFrameBufferTexture;    // 用於綁定在fbo中的texture

    GLProgram* m_glprogram;            // GL繪制管理
 GLint m_nOldFBO;                // 原來的fbo
 CustomCommand _beginCommand;
    CustomCommand _endCommand;
    GroupCommand _groupCommand;

    std::set<Node*> sRenderChild;    // 需要繪制的節點
 Size    m_sRenderSize;
};

#endif

這里有幾個主要實現:

1、我們用push2Draw(Node* node);  方法將待繪制的節點添加到sRenderChild中等待處理。

2、參考RenderTexture 的實現,我們也利用begin,end將需要繪制的節點利用visit訪問添加到當前的RenderShader類所在的render層中進行繪制。

3、在實際繪制過程中調用的onBegin中緩存對應的坐標系,同時綁定我們創建FBO使得visit進來的節點可以繪制到當前FBO中。然后在onEnd中調用render方法將我們的FBO內容繪制原來cocos2dx底層的FBO上。

4、在render()實現如何繪制當前FBO的內容到cocos2dx的FBO上。(實際shader作業的地方)

詳細實現代碼如下:

#include "RenderShader.h"  
#define STRINGIFY(A)  #A
const char* ccPositionTextureColor_frag_test = STRINGIFY(
\n#ifdef GL_ES\n
  precision lowp float;
\n#endif\n
 
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;

void main()
{
    vec4 color = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
    float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
    gl_FragColor = vec4(vec3(gray), color.a);
}
);

RenderShader* RenderShader::create()
{
    auto node = new RenderShader();
    if (node  && node->init())
    {
        node->autorelease();
        return node;
    }
    delete node;
    return nullptr;
}

bool RenderShader::init()
{
    if (m_glprogram)
    {
        m_glprogram->release();
        m_glprogram = nullptr;
    }

    CreateFrameBuffer();
  
    m_glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, ccPositionTextureColor_frag_test);
    m_glprogram->retain();

    return true;
}

RenderShader::RenderShader(void)
    :m_nFrameBuffer(0), m_nFrameBufferTexture(0), m_glprogram(nullptr)
{
    GLView* glView = Director::getInstance()->getOpenGLView();
    if (!glView)
    {
        return;
    }
    //屏幕大小 
    m_sRenderSize = glView->getFrameSize();
}

RenderShader::~RenderShader(void)
{
    if (m_glprogram)
    {
        m_glprogram->release();
        m_glprogram = nullptr;
    }
    if (m_nFrameBuffer)
    {
        glDeleteFramebuffers(1, &m_nFrameBuffer);
        m_nFrameBuffer = 0;
    }
    if (m_nFrameBufferTexture)
    {
        glDeleteTextures(1, &m_nFrameBufferTexture);
        m_nFrameBufferTexture = 0;
    }
}

bool RenderShader::LoadByteArray(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray)
{
    if (m_glprogram)
    {
        m_glprogram->release();
        m_glprogram = nullptr;
    }

    this->CreateFrameBuffer();

    m_glprogram = GLProgram::createWithByteArrays(vShaderByteArray, fShaderByteArray);
    m_glprogram->retain();

    return true;
}


void RenderShader::onBegin()
{
    if (!m_nFrameBuffer)
    {
        return;
    }
    Director* director = Director::getInstance();
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    //save old fbo and bind own
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer);
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
}

void RenderShader::onEnd()
{
    if (!m_nFrameBuffer)
    {
        return;
    }

    // restore viewport for render to direct fbo
    Director *director = Director::getInstance();
    // restore viewport
    director->setViewport();
    this->Render();

    glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO);
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}


void RenderShader::Render()
{
    if (!m_glprogram || !m_nFrameBufferTexture)
    {
        return;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO);
    m_glprogram->use();
    m_glprogram->setUniformsForBuiltins();

    //GLuint glTexture = m_glprogram->getUniformLocationForName("CC_Texture0");
    //glActiveTexture(GL_TEXTURE0);
    //glBindTexture(GL_TEXTURE_2D, m_nFrameBufferTexture);
    //glUniform1i(glTexture, 0);
    GL::bindTexture2D(m_nFrameBufferTexture);
    Size sSize = Director::getInstance()->getVisibleSize();
    float x = 0;
    float y = 0;
    float w = sSize.width;
    float h = sSize.height;
    GLfloat vertices[] = {
        x, y + h,
        x + w, y + h,
        x, y,
        x + w, y
    };
    
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, ccRenderTextcord);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);


    /*GL::bindVAO(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);*/
}



void RenderShader::CreateFrameBuffer()
{
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO);
    if (m_nFrameBuffer == 0)
    {
        glGenFramebuffers(1, &m_nFrameBuffer);

        if (m_nFrameBuffer == 0)
        {
            CCLOG("m_FilterFrameBuffer == 0");
            return;
        }
    }
    glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer);

    if (m_nFrameBufferTexture == 0)
    {
        glGenTextures(1, &m_nFrameBufferTexture);  
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_nFrameBufferTexture);

        GLsizei nWidth = m_sRenderSize.width;
        GLsizei nHeight = m_sRenderSize.height;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        GLenum err = glGetError();
        if (err != GL_NO_ERROR)
        {
            CCLOG("cocos2d: Texture2D: Error uploading compressed texture glError: 0x%04X", err);
            return;
        }

    }
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_nFrameBufferTexture, 0);


    GLenum error;
    if ((error = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE)
    {
        CCLOG("Failed to make complete framebuffer object 0x%X", error);
        return;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO);
}

void RenderShader::begin()
{ 
    Director* director = Director::getInstance();
    CCASSERT(nullptr != director, "Director is null when seting matrix stack");
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    _groupCommand.init(_globalZOrder);

    Renderer *renderer = Director::getInstance()->getRenderer();
    renderer->addCommand(&_groupCommand);
    renderer->pushGroup(_groupCommand.getRenderQueueID());

    _beginCommand.init(_globalZOrder);
    _beginCommand.func = CC_CALLBACK_0(RenderShader::onBegin, this);

    renderer->addCommand(&_beginCommand);
}

void RenderShader::end()
{
    _endCommand.init(_globalZOrder);
    _endCommand.func = CC_CALLBACK_0(RenderShader::onEnd, this);

    Director* director = Director::getInstance();
    CCASSERT(nullptr != director, "Director is null when seting matrix stack");

    Renderer *renderer = director->getRenderer();
    renderer->addCommand(&_endCommand);
    renderer->popGroup();

    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

}

void RenderShader::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    //Begin will create a render group using new render target
    begin();

    ////clear screen
    //_clearCommand.init(_globalZOrder);
    //_clearCommand.func = CC_CALLBACK_0(RenderShader::onClear, this);
    //renderer->addCommand(&_clearCommand);

    //! make sure all children are drawn
    {
        auto it = sRenderChild.begin();
        while (it != sRenderChild.end())
        {
            if (Node* node = *it)
            {
                node->visit(renderer, transform, flags);
                node->release();
            }
            ++it;
        }
        sRenderChild.clear();
    }
    //End will pop the current render group
    end();
}


void    RenderShader::push2Draw(Node* node)
{
    if (node && sRenderChild.find(node) == sRenderChild.end())
    {
        node->retain();
        sRenderChild.insert(node);
    }
    else
    {
        CCLOG(" push same!!!");
    }
}
View Code

這里shader代碼實現的是灰度圖的效果,其他效果可以自己修改ccPositionTextureColor_frag_test內容。

 

二、實際使用

1、將RenderShader當成正常的Node使用,設置父節點及index坐標等。

        m_pShader = RenderShader::create();
        this->addChild(m_pShader);

 

2、因為RenderShader在draw中對需要繪制的子節點進行了清理,所以需要每次重新對子節點進行添加,添加動畫節點如下:

void CPlayer::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{ 
    if (m_pShader)
    {
        m_pShader->push2Draw(m_pNode);
    } 
}

主要事項:RenderShader在Render方法中將內容繪制到原來的FBO中,而cocos2dx對每個需要繪制的同一index層級會在開始的時候單獨創建一個FBO來處理。因而如果RenderShader單獨一個層級可能導致此時的FBO沒有創建,導致沒有繪制效果。

到這里我們就實現灰色角色的效果了,使用也依照cocos2dx的用法,相對簡單。

 

三、殘影效果擴展

主要實現代碼如下:

//******************************************************************
// 文件名:    RemindShader.h
// 創建人:    稀飯lei
// 版  本:    1.0
// 描  述:    殘影特效
//******************************************************************
#ifndef _RemindShader_H__
#define _RemindShader_H__ 
#include "RenderShader.h"
USING_NS_CC;
#define REMIND_RENDER_COUNT 5  // 殘影個數
class RemindShader :public RenderShader
{
public:
    static RemindShader* create();

    virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;
private:
    virtual void onBegin() override;

    //virtual void onClear();

    virtual void Render() override;

    virtual bool init() override;

    virtual void CreateFrameBuffer() override;

    RemindShader(void);

    virtual ~RemindShader(void);

    GLuint m_pTextureArr[REMIND_RENDER_COUNT]; 
      
    int   m_nRenderFrq;            // 渲染間隔幀率

    int      m_nRenderCount;        // 渲染間隔計數

    int   m_nCurTexutreIndex;    // 當前渲染的紋理ID
};

#endif

#include "RemindShader.h"

#define STRINGIFY(A)  #A
const char* ccRemind_fsh = STRINGIFY(
    \n#ifdef GL_ES\n
    precision lowp float;
    \n#endif\n

    varying vec2 v_texCoord;
    uniform float u_alpha;

    void main()
    {
        vec4 pcolor = texture2D(CC_Texture0, v_texCoord) * u_alpha;
        gl_FragColor = pcolor;
    }
    );


RemindShader* RemindShader::create()
{
    auto node = new RemindShader();
    if (node  && node->init())
    {
        node->autorelease();
        return node;
    }
    delete node;
    return nullptr;
}

bool RemindShader::init()
{
    if (m_glprogram)
    {
        m_glprogram->release();
        m_glprogram = nullptr;
    }

    CreateFrameBuffer();
    m_glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, ccRemind_fsh);
    m_glprogram->retain();
    return true;
}

RemindShader::RemindShader(void)
: m_nCurTexutreIndex(0), m_nRenderFrq(10), m_nRenderCount(0)
{
    memset(m_pTextureArr, 0, REMIND_RENDER_COUNT);
}

RemindShader::~RemindShader(void)
{
    if (m_pTextureArr[0])
    {
        glDeleteTextures(REMIND_RENDER_COUNT, m_pTextureArr);
        memset(m_pTextureArr, 0, REMIND_RENDER_COUNT);
    }
}
 
void RemindShader::onBegin()
{
    if (!m_nFrameBuffer)
    {
        return;
    }    
    Director* director = Director::getInstance(); 
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
//    director->setProjection(director->getProjection());
//    GLView* glView = director->getOpenGLView();
//    if (!glView)
//    {
//        return;
//    }
//    Size frameSize =glView->getFrameSize();
//    {
//        // Calculate the adjustment ratios based on the old and new projections
//        Size size = director->getWinSizeInPixels();
//        float widthRatio = size.width / frameSize.width;
//        float heightRatio = size.height / frameSize.height;
//        //caculate the projections of size change
//        Mat4 orthoMatrix;
//        Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix);
//        director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix);
//    }
//    
//    //calculate viewport
//    {
//        glViewport(0, 0, (GLsizei)(frameSize.width), (GLsizei)(frameSize.height));
//    }
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO);
    // 僅在計數為0才更新新的渲染
    if (m_nRenderCount == 0)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTextureArr[m_nCurTexutreIndex], 0);
        m_nCurTexutreIndex = m_nCurTexutreIndex + 1 < REMIND_RENDER_COUNT ? m_nCurTexutreIndex + 1 : 0;

         glClearColor(0.0, 0.0, 0.0, 0.0);
         glClear(GL_COLOR_BUFFER_BIT);
    }
}

void RemindShader::Render()
{
    if (!m_glprogram )
    {
        return;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO);
    m_glprogram->use();
    m_glprogram->setUniformsForBuiltins();

    Size sSize = Director::getInstance()->getVisibleSize();
    float x = 0;
    float y = 0;
    float w = sSize.width;
    float h = sSize.height;

    // 除當前紋理外的紋理存儲的都是拖影
    int nBeginIndex = m_nCurTexutreIndex;
    int nCurIndex = nBeginIndex;
    GLfloat falpha = 0.3f;
    float addf = 0.05f;

    // 渲染存在m_pTextureArr隊列里的拖影紋理到主場景
    do
    {    
        Point delta = _position;// this->convertToWorldSpace(_position);
        GLfloat vertices[] = {
            x   - delta.x,y+ h - delta.y,
            x+w - delta.x,y+ h - delta.y,
            x - delta.x  , y - delta.y,
            x+w - delta.x, y - delta.y
        };
        //GLuint glTexture = m_glprogram->getUniformLocationForName("CC_Texture0");
        //glActiveTexture(GL_TEXTURE0);
        //glBindTexture(GL_TEXTURE_2D, m_pTextureArr[nCurIndex]);
        //glUniform1i(glTexture, 0);
        GL::bindTexture2D(m_pTextureArr[nCurIndex]);

        GLuint glAlpha = m_glprogram->getUniformLocationForName("u_alpha");
        glUniform1f(glAlpha, falpha);

        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, ccRenderTextcord);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        falpha += addf;
        addf += 0.025f;
        nCurIndex = ++nCurIndex<REMIND_RENDER_COUNT ? nCurIndex : 0; 
    } while (nCurIndex != nBeginIndex);
//
//    GL::bindVAO(0);
    //glBindBuffer(GL_ARRAY_BUFFER, 0);
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void RemindShader::CreateFrameBuffer()
{

    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO);
    if (m_nFrameBuffer == 0)
    {
        glGenFramebuffers(1, &m_nFrameBuffer);

        if (m_nFrameBuffer == 0)
        {
            CCLOG("m_FilterFrameBuffer == 0");
            return;
        }
    }
    GLsizei nWidth = m_sRenderSize.width;
    GLsizei nHeight = m_sRenderSize.height;
    glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer);
    if (m_pTextureArr[0] == 0)
    {
        glGenTextures(REMIND_RENDER_COUNT, m_pTextureArr);
        for (int i = 0; i < REMIND_RENDER_COUNT; i++)
        {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, m_pTextureArr[i]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
            GLenum err = glGetError();
            if (err != GL_NO_ERROR)
            {
                CCLOG("cocos2d: Texture2D: Error uploading compressed texture glError: 0x%04X", err);
                return;
            }
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTextureArr[i], 0);
            glClearColor(0.0, 0.0, 0.0, 0.0);
            glClear(GL_COLOR_BUFFER_BIT);
        }
    }
    glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO);
}
  

void RemindShader::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    if (sRenderChild.size() <= 0 )
    {
        if (m_nFrameBuffer)
        {
            // 清理所有緩存的紋理
            glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO);
            glBindFramebuffer(GL_FRAMEBUFFER, m_nFrameBuffer);
            for (int i = 0; i < REMIND_RENDER_COUNT; i++)
            {
                glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTextureArr[i], 0);
                glClearColor(0.0, 0.0, 0.0, 0.0);
                glClear(GL_COLOR_BUFFER_BIT);
            }
            glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO);
        }
        m_nRenderCount = m_nRenderFrq - 1;
        return;
    }
    //Begin will create a render group using new render target
    begin();

    if (++m_nRenderCount == m_nRenderFrq)
    {
        //! make sure all children are drawn 
        auto it = sRenderChild.begin();
        while (it != sRenderChild.end())
        {
            if (Node* node = *it)
            {
                node->visit(renderer, transform, flags);
                node->release();
            }
            ++it;
        }
        m_nRenderCount = 0;    // 0 提示需要渲染進去
    }
    sRenderChild.clear();
    //End will pop the current render group
    end();

}
View Code

幾個關鍵點:

1、利用REMIND_RENDER_COUNT控制需要創建的Texture個數,通過每次繪制到不同的Texture上來保存原來的效果。

2、shader中利用u_alpha參數控制殘影的透明度,在render中繪制到原來的FBO上時調用不同的透明度來實現殘影漸隱的效果。

3、通過m_nRenderFrq來控制間隔的繪制次數,來實現殘影的殘留效果遠近。

這樣就實現了殘影的效果了,使用的方法還是跟基類一樣。

完整代碼地址:https://github.com/mydishes/cocos2dx-Ex/tree/master/Shader


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM