Qt 3D的研究(七):渲染至纹理
最近几天都没有怎么研究Qt 3D了,但是随着Qt5.5发布的日子一天天的靠近,我也不能懈怠,希望利用Qt 3D,将能够实现的功能进行实现,并且对Qt3D获取一个新的认识。两天多没有研究了,现在信心却是满满的!
蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/44156633。欢迎同行前来探讨。
Qt 3D还是在发展的过程中,也没有什么文档,所以有什么问题的话,只能去问库的作者了。我就在IRC频道上多次向Paul Lemire提问,并且得到了他的热情回答!这里表示深深的感谢!这几天的提问和回答,我了解了Qt 3D的使用方法,结合Qt 3D的一些例子,我实现了渲染到纹理的效果。
渲染到纹理的效果主要依赖于Frame Buffer Object(简称FBO)的支持。我花了一段时间研究FBO和PBO,发现他们还是不一样的,PBO感觉是为了暂存像素数据而提出的一个数据格式,FBO则类似于一个集线器,里面有很多插口,通过选用不同的插口,可以将渲染的结果存储至Render Target(连接屏幕)以及Texture(纹理)。这样一个早在OpenGL2.x就启用的功能,我也是只在Qt 3D上作第一次尝试,这次想要尝试的是渲染到纹理。
简而言之,就是对将要渲染的物体进行两遍渲染。第一遍是将模型渲染至纹理中,第二遍则是将该纹理显示在场景中。这样就达到了我们想要的效果。为什么需要两遍渲染而不是一遍就解决呢?因为如果我们需要对纹理做一些图像处理,并且显示出来,就有必要了。
下面看看我的运行结果吧!
这里使用了卡通渲染的方式进行渲染,并且为模型添加了边缘。其中卡通渲染这种方式渲染了两遍,第一遍保存在一个纹理中,第二遍则使用纹理提取边缘,并且将边缘和模型一起渲染。实现这样效果的qml代码如下所示:
import Qt3D 2.0 import Qt3D.Render 2.0 Entity { id: root Camera { id: camera position: Qt.vector3d( 0.0, 0.0, -40.0 ) projectionType: CameraLens.PerspectiveProjection fieldOfView: 45 aspectRatio: 16.0 / 9.0 nearPlane : 0.1 farPlane : 1000.0 upVector: Qt.vector3d( 0.0, 1.0, 0.0 ) viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 ) } //! [7] Texture2D { id: colorAttachTex width: 1024 height: 1024 format: Texture.RGB8_UNorm generateMipMaps: false magnificationFilter: Texture.Nearest minificationFilter: Texture.Nearest wrapMode { x: WrapMode.ClampToEdge y: WrapMode.ClampToEdge } } Texture2D { id : depthAttachTex width: 1024 height: 1024 format: Texture.D32F generateMipMaps: false magnificationFilter: Texture.Nearest minificationFilter: Texture.Nearest wrapMode { x: WrapMode.ClampToEdge y: WrapMode.ClampToEdge } } //! [7] //! [8] components: FrameGraph { Viewport { rect: Qt.rect( 0.0, 0.0, 1.0, 1.0 ) clearColor: Qt.rgba( 0.0, 0.4, 0.7, 1.0 ) LayerFilter { layers: "scene" CameraSelector { camera: camera ClearBuffer { buffers: ClearBuffer.ColorDepthBuffer RenderTargetSelector { target: RenderTarget { attachments: [ RenderAttachment { name: "colorAttachTex" type: RenderAttachment.ColorAttachment0 texture: colorAttachTex }, RenderAttachment { name : "depth" type : RenderAttachment.DepthAttachment texture : depthAttachTex } ] } } } } } ClearBuffer { buffers: ClearBuffer.ColorDepthBuffer CameraSelector { camera: camera } } } } //! [8] Entity { Layer { id: sceneLayer names: "scene" } Mesh { id: mesh source: "qrc:/toyplane.obj" } Material { id: material effect: effect Effect { id: effect techniques: [ technique ] Technique { id: technique openGLFilter { api: OpenGLFilter.Desktop profile: OpenGLFilter.None majorVersion: 2 minorVersion: 0 } parameters: [ Parameter { name: "lightPosition" value: camera.position }, Parameter { name: "texPalette" value: texPalette } ] Texture2D { id: texPalette TextureImage { source: "qrc:/discreetPalette.png" } } renderPasses: [ grayMapPass ] RenderPass { id: grayMapPass shaderProgram: toonSP ShaderProgram { id: toonSP vertexShaderCode: loadSource( "qrc:/Toon.vert" ) fragmentShaderCode: loadSource( "qrc:/Toon.frag" ) } // renderStates: // [ // PolygonOffset { factor: 4; units: 4 }, // DepthTest { func: DepthTest.LessOrEqual } // ] } } } } components: [ sceneLayer, mesh, material ] } //! [9] Entity { Layer { id: screenLayer names: "screenQuad" } PlaneMesh { id: planeMesh width: 2.0 height: 2.0 meshResolution: Qt.size( 2, 2 ) } Transform { id: planeTransform Rotate { axis : Qt.vector3d( 1.0, 0.0, 0.0 ) angle : 90 } } Material { id: planeMat effect: planeEffect Effect { id: planeEffect techniques: [ planeTech ] Technique { id: planeTech openGLFilter { api: OpenGLFilter.Desktop profile: OpenGLFilter.None majorVersion: 2 minorVersion: 0 } parameters: [ Parameter { name: "colorAttachTex" value: colorAttachTex }, Parameter { name: "texSize" value : Qt.size( window.width, window.height ) }, Parameter { name: "texOffsetX" value: 1.0 / colorAttachTex.width }, Parameter { name: "texOffsetY" value: 1.0 / colorAttachTex.height } ] renderPasses: [ outputPass ] RenderPass { id: outputPass shaderProgram: outputSP ShaderProgram { id: outputSP vertexShaderCode: loadSource( "qrc:/Output.vert" ) fragmentShaderCode: loadSource( "qrc:/Output.frag" ) } // renderStates: // [ // PolygonOffset { factor: 4; units: 4 }, // DepthTest { func: DepthTest.Less } // ] } } } } components: [ screenLayer, planeMesh, planeTransform, planeMat ] } //! [9] Configuration { controlledCamera: camera } }
我在代码的// ![7]部分,定义了两个纹理,这两个纹理表示了需要连接的部分,一部分是FBO连接的颜色部分,另外是FBO连接的深度部分。而在//![8]中,我们定义了一个含有Viewport、LayerFilter、CameraSelector、ClearBuffer、RenderTargetSelector的FrameGraph。FrameGraph是一个树状的组织结构,并且按照DFS的方式搜索,也就是说,代码块中最前面最深的部分最先得到执行。所以这个FrameGraph按照先设定render
attachment,接着清空缓冲区,再设定渲染层以及渲染的摄像机。在//! [9]的部分,我们再定义了一个纹理所在模型。这是一个矩形平面纹理,它覆盖我们摄像机的视口,这样我们第二遍渲染,就可以将我们提取的边缘覆盖到我们渲染的使用卡通渲染的模型上来了。