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