陰影對映(Shadow Map)的研究(五)
阿新 • • 發佈:2019-01-27
陰影對映(Shadow Map)的研究(五)
我成功地將別人的例子加以改進,使用QOpenGLWidget作為渲染視窗,將陰影對映渲染了出來。目前可以確定的是,使用OpenGL ES 2.0作為渲染的介面要求,能夠讓目前絕大多數機器都能夠順利相容,但是囿於渲染視窗,可能在某些平臺上表現不好。如果移植到Qt Quick 2,這樣能夠支援的平臺就更多了。現在我將這些介面統統使用Qt的方式實現了,移植到Qt Quick 2也很簡單。
這裡主要參考的是OpenGLUnderQML這個例子,自定義了一個QQuickItem的子類,使其作為渲染的檢視。緊接著,定義了多個TexturedCube,指定相關屬性,並且使用專用的TexturedCubeRenderer負責渲染,最後將這些類匯出至QML環境中。這樣就可以使用QML來編寫場景了。這種方法,在Qt 3D中廣泛使用,它也將成為我以後開發3D應用的指令碼描述方式。例子與上一個例子的顯示差別並不大,目前只實現了陰影對映這個功能,有感興趣的同行們可以使用這個例子進行擴充套件,實現自己想要的效果。
作為參考範例,貼出Cube類的程式碼:
Cube.h
Cube.cpp#ifndef MYCUBE_H #define MYCUBE_H #include <QUrl> #include <QVector3D> #include <QObject> class View; class CubeRenderer; class Cube: public QObject { Q_OBJECT Q_PROPERTY( qreal length READ length WRITE setLength NOTIFY lengthChanged ) Q_PROPERTY( QUrl source READ source WRITE setSource NOTIFY sourceChanged ) Q_PROPERTY( QVector3D translate READ translate WRITE setTranslate NOTIFY translateChanged ) public: explicit Cube( QObject* parent = Q_NULLPTR ); void initialize( void ); void render( void ); void renderShadow( void ); void sync( void ); void release( void ); void setView( View* view ) { m_view = view; } qreal length( void ) { return m_length; } void setLength( qreal length ); QUrl source( void ) { return m_source; } void setSource( const QUrl& source ); QVector3D translate( void ) { return m_translate; } void setTranslate( const QVector3D& translate ); friend class CubeRenderer; signals: void lengthChanged( void ); void sourceChanged( void ); void translateChanged( void ); protected: qreal m_length; QUrl m_source; QVector3D m_translate; bool m_lengthIsDirty: 1; bool m_sourceIsDirty: 1; bool m_translateIsDirty: 1; View* m_view; CubeRenderer* m_renderer; }; #endif // MYCUBE_H
#include <math.h> #include <QOpenGLFunctions> #include <QOpenGLBuffer> #include <QOpenGLShaderProgram> #include <QOpenGLTexture> #include <QQmlFile> #include "View.h" #include "Cube.h" #define VERTEX_COUNT 36 #define CUBE_LENGTH 25.0 #define TEXTURE_UNIT GL_TEXTURE0 #define SHADOW_TEXTURE_UNIT GL_TEXTURE1 static void canonicalPosition( QVector3D& position ) { if ( !qFuzzyIsNull( position.x( ) ) ) position.setX( position.x( ) / fabsf( position.x( ) ) ); if ( !qFuzzyIsNull( position.y( ) ) ) position.setY( position.y( ) / fabsf( position.y( ) ) ); if ( !qFuzzyIsNull( position.z( ) ) ) position.setZ( position.z( ) / fabsf( position.z( ) ) ); } class CubeRenderer: protected QOpenGLFunctions { struct Vertex { void set( const QVector3D& _position, const QVector3D& _normal, const QVector2D& _texCoord ) { position = _position; normal = _normal; texCoord = _texCoord; } QVector3D position; QVector3D normal; QVector2D texCoord; }; public: enum ShadowType { NoShadow = 0,// 以後依次遞增 SimpleShadow, PCFShadow }; explicit CubeRenderer( Cube* plane, ShadowType shadowType ): m_cube( plane ), m_shadowType( shadowType ), m_vertexBuffer( QOpenGLBuffer::VertexBuffer ), m_texture( QOpenGLTexture::Target2D ) { initializeOpenGLFunctions( ); // 根據建立的次數來建立著色器 if ( s_count++ == 0 ) { s_program = new QOpenGLShaderProgram; s_program->addShaderFromSourceFile( QOpenGLShader::Vertex, ":/Common.vert" ); s_program->addShaderFromSourceFile( QOpenGLShader::Fragment, ":/Common.frag" ); s_program->link( ); s_program->bind( ); s_positionLoc = s_program->attributeLocation( "position" ); s_normalLoc = s_program->attributeLocation( "normal" ); s_texCoordLoc = s_program->attributeLocation( "texCoord" ); s_modelMatrixLoc = s_program->uniformLocation( "modelMatrix" ); s_viewMatrixLoc = s_program->uniformLocation( "viewMatrix" ); s_projectionMatrixLoc = s_program->uniformLocation( "projectionMatrix" ); s_lightPositionLoc = s_program->uniformLocation( "lightPosition" ); s_lightViewProjectionMatrixLoc = s_program->uniformLocation( "lightViewProjectionMatrix" ); s_modelViewNormalMatrixLoc = s_program->uniformLocation( "modelViewNormalMatrix" ); s_shadowTypeLoc = s_program->uniformLocation( "shadowType" ); int textureLoc = s_program->uniformLocation( "texture" ); int shadowLoc = s_program->uniformLocation( "shadowTexture" ); s_program->setUniformValue( textureLoc, TEXTURE_UNIT - GL_TEXTURE0 ); s_program->setUniformValue( shadowLoc, SHADOW_TEXTURE_UNIT - GL_TEXTURE0 ); s_program->release( ); } // 設定頂點座標 qreal semi = CUBE_LENGTH / 2.0; const QVector3D basicVertices[] = { QVector3D( semi, -semi, semi ), QVector3D( semi, -semi, -semi ), QVector3D( -semi, -semi, -semi ), QVector3D( -semi, -semi, semi ), QVector3D( semi, semi, semi ), QVector3D( semi, semi, -semi ), QVector3D( -semi, semi, -semi ), QVector3D( -semi, semi, semi ) }; const QVector3D normals[] = { QVector3D( 1.0, 0.0, 0.0 ), QVector3D( 0.0, 1.0, 0.0 ), QVector3D( 0.0, 0.0, 1.0 ), QVector3D( -1.0, 0.0, 0.0 ), QVector3D( 0.0, -1.0, 0.0 ), QVector3D( 0.0, 0.0, -1.0 ) }; const QVector2D texCoords[] = { QVector2D( 0.0, 0.0 ), QVector2D( 0.0, 1.0 ), QVector2D( 1.0, 0.0 ), QVector2D( 1.0, 1.0 ) }; m_vertices = new Vertex[VERTEX_COUNT]; Vertex* v = m_vertices; // 前面 v[0].set( basicVertices[7], normals[2], texCoords[2] ); v[1].set( basicVertices[3], normals[2], texCoords[0] ); v[2].set( basicVertices[0], normals[2], texCoords[1] ); v[3].set( basicVertices[4], normals[2], texCoords[3] ); v[4].set( basicVertices[7], normals[2], texCoords[2] ); v[5].set( basicVertices[0], normals[2], texCoords[1] ); // 後面 v[6].set( basicVertices[5], normals[5], texCoords[2] ); v[7].set( basicVertices[2], normals[5], texCoords[1] ); v[8].set( basicVertices[6], normals[5], texCoords[3] ); v[9].set( basicVertices[5], normals[5], texCoords[2] ); v[10].set( basicVertices[1], normals[5], texCoords[0] ); v[11].set( basicVertices[2], normals[5], texCoords[1] ); // 上面 v[12].set( basicVertices[4], normals[1], texCoords[2] ); v[13].set( basicVertices[5], normals[1], texCoords[3] ); v[14].set( basicVertices[6], normals[1], texCoords[1] ); v[15].set( basicVertices[4], normals[1], texCoords[2] ); v[16].set( basicVertices[6], normals[1], texCoords[1] ); v[17].set( basicVertices[7], normals[1], texCoords[0] ); // 下面 v[18].set( basicVertices[0], normals[4], texCoords[3] ); v[19].set( basicVertices[2], normals[4], texCoords[0] ); v[20].set( basicVertices[1], normals[4], texCoords[1] ); v[21].set( basicVertices[0], normals[4], texCoords[3] ); v[22].set( basicVertices[3], normals[4], texCoords[2] ); v[23].set( basicVertices[2], normals[4], texCoords[0] ); // 左面 v[24].set( basicVertices[2], normals[3], texCoords[0] ); v[25].set( basicVertices[3], normals[3], texCoords[1] ); v[26].set( basicVertices[7], normals[3], texCoords[3] ); v[27].set( basicVertices[2], normals[3], texCoords[0] ); v[28].set( basicVertices[7], normals[3], texCoords[3] ); v[29].set( basicVertices[6], normals[3], texCoords[2] ); // 右面 v[30].set( basicVertices[4], normals[0], texCoords[2] ); v[31].set( basicVertices[1], normals[0], texCoords[1] ); v[32].set( basicVertices[5], normals[0], texCoords[3] ); v[33].set( basicVertices[1], normals[0], texCoords[1] ); v[34].set( basicVertices[4], normals[0], texCoords[2] ); v[35].set( basicVertices[0], normals[0], texCoords[0] ); m_vertexBuffer.setUsagePattern( QOpenGLBuffer::DynamicDraw ); m_vertexBuffer.create( ); m_vertexBuffer.bind( ); m_vertexBuffer.allocate( v, VERTEX_COUNT * sizeof( Vertex ) ); m_vertexBuffer.release( ); // 設定紋理濾波 m_texture.setMinificationFilter( QOpenGLTexture::LinearMipMapLinear ); m_texture.setMagnificationFilter( QOpenGLTexture::Linear ); } ~CubeRenderer( void ) { m_vertexBuffer.destroy( ); m_texture.destroy( ); delete []m_vertices; if ( --s_count == 0 ) { delete s_program; } } void render( void ) { s_program->bind( ); m_vertexBuffer.bind( ); // 繪製box int offset = 0; setVertexAttribute( s_positionLoc, GL_FLOAT, 3, offset ); offset += 3 * sizeof( GLfloat ); setVertexAttribute( s_normalLoc, GL_FLOAT, 3, offset ); offset += 3 * sizeof( GLfloat ); setVertexAttribute( s_texCoordLoc, GL_FLOAT, 2, offset ); // 攝像機的MVP矩陣 QMatrix4x4& viewMatrix = m_cube->m_view->viewMatrix( ); s_program->setUniformValue( s_modelMatrixLoc, m_modelMatrix ); s_program->setUniformValue( s_viewMatrixLoc, viewMatrix ); s_program->setUniformValue( s_projectionMatrixLoc, m_cube->m_view->projectionMatrix( ) ); s_program->setUniformValue( s_modelViewNormalMatrixLoc, ( viewMatrix * m_modelMatrix ).normalMatrix( ) ); // 是否啟用實時陰影 //s_program->setUniformValue( s_shadowTypeLoc, m_shadowType ); if ( m_shadowType != NoShadow ) { s_program->setUniformValue( s_lightPositionLoc, m_cube->m_view->lightPosition( ) ); s_program->setUniformValue( s_lightViewProjectionMatrixLoc, m_cube->m_view->lightViewProjectionMatrix( ) ); glActiveTexture( SHADOW_TEXTURE_UNIT ); glBindTexture( GL_TEXTURE_2D, m_cube->m_view->shadowTexture( ) ); } glActiveTexture( TEXTURE_UNIT ); m_texture.bind( ); glDrawArrays( GL_TRIANGLES, 0, VERTEX_COUNT ); m_texture.release( ); m_vertexBuffer.release( ); s_program->release( ); } void renderShadow( void ) { m_vertexBuffer.bind( ); QOpenGLShaderProgram* depthProgram = m_cube->m_view->depthProgram( ); depthProgram->enableAttributeArray( "position" ); depthProgram->setAttributeBuffer( "position", // 位置 GL_FLOAT, // 型別 0, // 偏移 3, // 元大小 sizeof( Vertex ) ); // 邁 depthProgram->setUniformValue( "modelMatrix", m_modelMatrix ); glDrawArrays( GL_TRIANGLES, 0, VERTEX_COUNT ); m_vertexBuffer.release( ); } void resize( qreal length ) { qreal semi = length / 2.0; m_vertexBuffer.bind( ); Vertex* v = (Vertex*)m_vertexBuffer.map( QOpenGLBuffer::WriteOnly ); for ( int i = 0; i < VERTEX_COUNT; ++i ) { canonicalPosition( v[i].position ); v[i].position *= semi; } m_vertexBuffer.unmap( ); m_vertexBuffer.release( ); } void setVertexAttribute( int attributeLocation, GLenum elementType, quint32 elementSize, quint32 offset ) { s_program->enableAttributeArray( attributeLocation ); s_program->setAttributeBuffer( attributeLocation, // 位置 elementType, // 型別 offset, // 偏移 elementSize, // 元大小 sizeof( Vertex ) ); // 邁 } void loadTextureFromSource( const QUrl& source ) { QString imagePath = QQmlFile::urlToLocalFileOrQrc( source ); m_texture.setData( QImage( imagePath ).mirrored( ) ); // 設定紋理濾波 m_texture.setMinificationFilter( QOpenGLTexture::LinearMipMapLinear ); m_texture.setMagnificationFilter( QOpenGLTexture::Linear ); } void translate( const QVector3D& translate ) { m_modelMatrix.setToIdentity( ); m_modelMatrix.translate( translate ); } protected: Cube* m_cube; QMatrix4x4 m_modelMatrix; ShadowType m_shadowType; QOpenGLBuffer m_vertexBuffer; QOpenGLTexture m_texture; Vertex* m_vertices; static QOpenGLShaderProgram* s_program; static int s_positionLoc, s_normalLoc, s_texCoordLoc, s_modelMatrixLoc, s_viewMatrixLoc, s_projectionMatrixLoc, s_lightViewProjectionMatrixLoc, s_lightPositionLoc, s_modelViewNormalMatrixLoc, s_shadowTypeLoc; static int s_count; // 計數 }; QOpenGLShaderProgram* CubeRenderer::s_program = Q_NULLPTR; int CubeRenderer::s_positionLoc, CubeRenderer::s_normalLoc, CubeRenderer::s_texCoordLoc, CubeRenderer::s_modelMatrixLoc, CubeRenderer::s_viewMatrixLoc, CubeRenderer::s_projectionMatrixLoc, CubeRenderer::s_lightViewProjectionMatrixLoc, CubeRenderer::s_lightPositionLoc, CubeRenderer::s_modelViewNormalMatrixLoc, CubeRenderer::s_shadowTypeLoc, CubeRenderer::s_count = 0; Cube::Cube( QObject* parent ): QObject( parent ) { m_length = CUBE_LENGTH; m_lengthIsDirty = false; m_sourceIsDirty = false; m_translateIsDirty = false; } void Cube::initialize( void ) { m_renderer = new CubeRenderer( this, CubeRenderer::SimpleShadow ); } void Cube::render( void ) { m_renderer->render( ); } void Cube::renderShadow( void ) { m_renderer->renderShadow( ); } void Cube::sync( void ) { if ( m_lengthIsDirty ) { m_renderer->resize( m_length ); m_lengthIsDirty = false; } if ( m_sourceIsDirty ) { m_renderer->loadTextureFromSource( m_source ); m_sourceIsDirty = false; } if ( m_translateIsDirty ) { m_renderer->translate( m_translate ); m_translateIsDirty = false; } } void Cube::release( void ) { delete m_renderer; } void Cube::setLength( qreal length ) { if ( m_length == length ) return; m_length = length; emit lengthChanged( ); m_lengthIsDirty = true; } void Cube::setSource( const QUrl& source ) { if ( m_source == source ) return; m_source = source; emit sourceChanged( ); m_sourceIsDirty = true; } void Cube::setTranslate( const QVector3D& translate ) { if ( m_translate == translate ) return; m_translate = translate; emit translateChanged( ); m_translateIsDirty = true; }
程式執行的截圖如下:
原始碼下載地址:這裡