Qt 3D的一个很大的优势就是采用数据驱动的方式,将C++和GLSL使用QML来表示,动态语言的优势尽显。在上一次的研究中,我实现了一个非常简单的着色器,接下来,我们可以在此基础上,通过设定着色器的数据,制作出更加绚丽的着色效果。作为开始,我们先从几个非真实渲染(Non-Photorealistic Rendering,NPR)开始吧。
蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/44007495。欢迎同行前来探讨。
首先我们开始研究Gooch着色器。Gooch着色器是Phong着色器的一个变种,一般用于技术插图中,比如说下面这张插图的效果就可以使用Gooch着色器完成。
接下来我们将上一篇文章的QML代码稍微修改一下,以支持我们的Gooch着色器。下面是新增的代码:
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 ) } components: FrameGraph { ForwardRenderer { clearColor: Qt.rgba( 1.0, 0.8, 0.2, 1 ) camera: camera } } Entity { 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 } renderPasses: [ renderPass ] //! [5] parameters: [ Parameter { name: "lightPosition" value: camera.position }, Parameter { name: "surfaceColor" value: Qt.rgba( 0.9, 0.9, 0.9, 1 ) }, Parameter { name: "warmColor" value: Qt.rgba( 0.8, 0.3, 0.0, 1 ) }, Parameter { name: "coolColor" value: Qt.rgba( 0.0, 0.3, 0.2, 1 ) }, Parameter { name: "diffuseWarm" value: 0.4 }, Parameter { name: "diffuseCool" value: 0.6 } ] //! [5] RenderPass { id: renderPass shaderProgram: goochSP ShaderProgram { id: goochSP vertexShaderCode: loadSource( "qrc:/Gooch.vert" ) fragmentShaderCode: loadSource( "qrc:/Gooch.frag" ) } } } } } components: [ mesh, material ] } Configuration { controlledCamera: camera } }
在//! [5]中我们定义了一些需要传入的参数。它们是lightPosition、surfaceColor、warmColor、coolColor、diffuseWarm和diffuseCool。这些是要在着色器中定义的uniform参数,所以我们必须要在QML中显示地指定它。其实Parameter定义在哪里,Qt 3D是有讲究的。Qt 3D是这样一个规律:Parameter定义在外层的,会覆盖在内层的同名Parameter,也就是说,内层的Parameter作为一个默认的参数,可以通过外层的Parameter进行修改。目前,Qt3D的覆盖优先顺序是:Material→Effect→Technique→RenderPass→GLSL默认值。
介绍完了这些,让我们看看GLSL是如何的吧。
// Gooch.vert #version 100 // Qt 3D默认提供的参数 attribute vec3 vertexPosition; attribute vec3 vertexNormal; uniform mat4 modelView; uniform mat4 modelNormalMatrix; uniform mat4 mvp; // 自己提供的参数 uniform vec3 lightPosition; varying vec3 reflectVec; varying vec3 viewVec; varying float NdotL; void main( void ) { vec3 ecPos = ( modelView * vec4( vertexPosition, 1.0 ) ).xyz; vec3 normal = normalize( modelNormalMatrix * vec4( vertexNormal, 1.0 ) ).xyz; vec3 lightVec = normalize( lightPosition - ecPos ); reflectVec = normalize( reflect( -lightVec, normal ) ); viewVec = normalize( -ecPos ); NdotL = ( dot( lightVec, normal ) + 1.0 ) * 0.5; gl_Position = mvp * vec4( vertexPosition, 1.0 ); }
顶点着色器中我们仍然像Phong光照模型一样,求出NdotL,在片断着色器中使用;然后我们算出reflectVec,和viewVec,作为反射和视向量的单位向量,留到片断着色器中使用。
// Gooch.frag #version 100 // 自己提供的参数 uniform vec3 surfaceColor; uniform vec3 warmColor; uniform vec3 coolColor; uniform float diffuseWarm; uniform float diffuseCool; varying vec3 reflectVec; varying vec3 viewVec; varying float NdotL; void main( void ) { vec3 kcool = min( coolColor + diffuseCool * surfaceColor, 1.0 ); vec3 kwarm = min( warmColor + diffuseWarm * surfaceColor, 1.0 ); vec3 kfinal = mix( kcool, kwarm, NdotL ); float spec = max( dot( reflectVec, viewVec ), 0.0 ); spec = pow( spec, 32.0 ); gl_FragColor = vec4( min( kfinal + spec, 1.0 ), 1.0 ); }
在片断着色器中,我们定义了平面颜色、暖色调颜色、冷色调颜色以及他们相应的参数。这些参数需要我们通过mix函数(线性插值函数)最终得出基本的颜色,紧接着我们计算镜面反射光,让模型飞机具有光泽效果,最后将这些值添加起来,得到的是片断颜色。
演示程序截图如下:
嗯,通过调节参数来获取更好的效果,不过呢,更好的方法是定义一个颜色版纹理来得到一些更有趣的渲染效果:
方法是:首先我们定义一个颜色调色版的纹理:
然后在QML中添加这样一个参数以及相应的纹理对象:
parameters: [ Parameter { name: "lightPosition" value: camera.position }, Parameter { name: "surfaceColor" value: Qt.rgba( 0.9, 0.9, 0.9, 1 ) }, Parameter { name: "warmColor" value: Qt.rgba( 0.8, 0.3, 0.0, 1 ) }, Parameter { name: "coolColor" value: Qt.rgba( 0.0, 0.3, 0.2, 1 ) }, Parameter { name: "diffuseWarm" value: 0.4 }, Parameter { name: "diffuseCool" value: 0.6 }, //! [6] Parameter { name: "texPalette" value: texPalette } ] Texture2D { id: texPalette TextureImage { source: "qrc:/texturePalette.png" } } //! [6]
这里定义了Texture2D类以及TextureImage类。TextureImage类作为纹理的载入者,可以接受QImage能够载入的图片类型,接着作为Texture2D类的默认属性被生成一个纹理采样器(texture sampler2D),作为一个uniform变量载入到GLSL中。
GLSL中顶点着色器不变,片断着色器作如下的修改:
// Gooch.frag #version 100 // 自己提供的参数 uniform vec3 surfaceColor; uniform vec3 warmColor; uniform vec3 coolColor; uniform float diffuseWarm; uniform float diffuseCool; varying vec3 reflectVec; varying vec3 viewVec; varying float NdotL; uniform sampler2D texPalette; void main( void ) { vec3 kcool = min( coolColor + diffuseCool * surfaceColor, 1.0 ); vec3 kwarm = min( warmColor + diffuseWarm * surfaceColor, 1.0 ); //! [6] vec3 kfinal = texture2D( texPalette, vec2( NdotL, 1.0 ) ).xyz; //! [6] float spec = max( dot( reflectVec, viewVec ), 0.0 ); spec = pow( spec, 32.0 ); gl_FragColor = vec4( min( kfinal + spec, 1.0 ), 1.0 ); }
这里添加了texturePalette这个sampler2D变量,并且修改了kfinal的值。
参考文献:
http://www.jonatron.ca/tag/shader/
http://shiba.hpe.sh.cn/jiaoyanzu/wuli/showArticle.aspx?articleId=333&classId=4