[ui][3d] introduce Wireframe render mode

* add WireframeMaterial from Qt 3D example
* MaterialSwitcher now has 3 render modes: Solid, Wireframe, Textured
* Remove NodeInstantiator; always create the material and modify entity's components (avoid rendering issues when switching between materials)
* use DiffuseSpecularMaterial for Solid and Textured mode
* create a MaterialSwitcher for all types of meshes (with/without textures)
* add a render settings panel as part of 3D viewport overlay and remove "Textures" entry from outliner
This commit is contained in:
Yann Lanthony 2018-03-15 03:26:50 +01:00
parent f1d4291219
commit 74e80e70b5
7 changed files with 497 additions and 74 deletions

View file

@ -1,85 +1,149 @@
import Qt3D.Core 2.0
import Qt3D.Render 2.9
import Qt3D.Input 2.0
import Qt3D.Extras 2.9
import Qt3D.Extras 2.10
import QtQuick 2.0
import "Materials"
/**
* MaterialSwitcher is an Entity that can change its parent's material
* according to a set of property it exposes.
* It can be used to toggle between a Phong and a DiffuseMapColor
* depending on 'showTextures' value.
* by setting the 'mode' property.
*/
Entity {
id: root
objectName: "MaterialSwitcher"
property bool showTextures: true
property int mode: 2
readonly property var modes: ["Solid", "Wireframe", "Textured"]
property string diffuseMap: ""
property color ambient: "#AAA"
property real shininess
property color specular
property color diffuseColor: "#AAA"
property alias object: instantiator.object
NodeInstantiator {
id: instantiator
readonly property alias activeMaterial: m.material
delegate: root.showTextures ? textured : colored
// add the created Node delegate to the root's parent components
onObjectAdded: {
if(!root.parent)
return
var comps = [];
for(var i=0; i < root.parent.components.length; ++i)
{
comps.push(root.parent.components[i]);
}
comps.push(object);
root.parent.components = comps;
}
// remove the created Node delegate from the root's parent components
onObjectRemoved: {
if(!root.parent)
return
var comps = [];
for(var i=0; i < root.parent.components.length; ++i)
{
if(object != root.parent.components[i])
comps.push(root.parent.components[i]);
}
root.parent.components = comps;
QtObject {
id: m
property Material material
onMaterialChanged: {
// remove previous material(s)
removeComponentsByType(parent, "Material")
addComponent(root.parent, material)
}
}
Component {
id: colored
PhongMaterial {
parent: root.parent
objectName: "DiffuseColorMaterial"
ambient: root.ambient
diffuse: root.diffuseColor
shininess: root.shininess
specular: root.specular
function printComponents(entity)
{
console.log("Components of Entity '" + entity + "'")
for(var i=0; i < entity.components.length; ++i)
{
console.log(" -- [" + i + "]: " + entity.components[i])
}
}
Component {
function addComponent(entity, component)
{
if(!entity)
return
var comps = [];
comps.push(component);
for(var i=0; i < entity.components.length; ++i)
{
comps.push(entity.components[i]);
}
entity.components = comps;
}
function removeComponentsByType(entity, type)
{
if(!entity)
return
var comps = [];
for(var i=0; i < entity.components.length; ++i)
{
if(entity.components[i].toString().indexOf(type) == -1)
{
comps.push(entity.components[i]);
}
}
entity.components = comps;
}
function removeComponent(entity, component)
{
if(!entity)
return
var comps = [];
for(var i=0; i < entity.components.length; ++i)
{
if(entity.components[i] == component)
{
comps.push(entity.components[i]);
}
}
entity.components = comps;
}
StateGroup {
id: modeState
state: modes[mode]
states: [
State {
name: "Solid"
PropertyChanges { target: m; material: solid }
},
State {
name: "Wireframe"
PropertyChanges { target: m; material: wireframe }
},
State {
name: "Textured"
PropertyChanges { target: m; material: diffuseMap ? textured : solid }
}
]
}
// Solid and Textured modes could and should be using the same material
// but get random shader errors (No shader program found for DNA)
// when toggling between a color and a texture for the diffuse property
DiffuseSpecularMaterial {
id: solid
parent: root.parent
objectName: "SolidMaterial"
ambient: root.ambient
shininess: root.shininess
specular: root.specular
diffuse: root.diffuseColor
}
DiffuseSpecularMaterial {
id: textured
DiffuseMapMaterial {
parent: root.parent
objectName: "DiffuseMapMaterial"
ambient: root.ambient
shininess: root.shininess
specular: root.specular
diffuse: TextureLoader {
id: baseColorLoader
mirrored: false
source: root.diffuseMap
magnificationFilter: Texture.Linear
}
parent: root.parent
objectName: "SolidMaterial"
ambient: root.ambient
shininess: root.shininess
specular: root.specular
diffuse: TextureLoader {
magnificationFilter: Texture.Linear
mirrored: false
source: diffuseMap
}
}
WireframeMaterial {
id: wireframe
parent: root.parent
objectName: "WireframeMaterial"
effect: WireframeEffect {}
ambient: root.ambient
diffuse: root.diffuseColor
shininess: 0
specular: root.specular
}
}

View file

@ -0,0 +1,43 @@
import Qt3D.Core 2.0
import Qt3D.Render 2.0
Effect {
id: root
parameters: [
Parameter { name: "ka"; value: Qt.vector3d( 0.1, 0.1, 0.1 ) },
Parameter { name: "kd"; value: Qt.vector3d( 0.7, 0.7, 0.7 ) },
Parameter { name: "ks"; value: Qt.vector3d( 0.95, 0.95, 0.95 ) },
Parameter { name: "shininess"; value: 150.0 }
]
techniques: [
Technique {
graphicsApiFilter {
api: GraphicsApiFilter.OpenGL
profile: GraphicsApiFilter.CoreProfile
majorVersion: 3
minorVersion: 1
}
filterKeys: [ FilterKey { name: "renderingStyle"; value: "forward" } ]
parameters: [
Parameter { name: "light.position"; value: Qt.vector4d( 0.0, 0.0, 0.0, 1.0 ) },
Parameter { name: "light.intensity"; value: Qt.vector3d( 1.0, 1.0, 1.0 ) },
Parameter { name: "line.width"; value: 1.0 },
Parameter { name: "line.color"; value: Qt.vector4d( 1.0, 1.0, 1.0, 1.0 ) }
]
renderPasses: [
RenderPass {
shaderProgram: ShaderProgram {
vertexShaderCode: loadSource(Qt.resolvedUrl("shaders/robustwireframe.vert"))
geometryShaderCode: loadSource(Qt.resolvedUrl("shaders/robustwireframe.geom"))
fragmentShaderCode: loadSource(Qt.resolvedUrl("shaders/robustwireframe.frag"))
}
}
]
}
]
}

View file

@ -0,0 +1,28 @@
import Qt3D.Core 2.0
import Qt3D.Render 2.0
Material {
id: root
property color ambient: Qt.rgba( 0.05, 0.05, 0.05, 1.0 )
property color diffuse: Qt.rgba( 0.7, 0.7, 0.7, 1.0 )
property color specular: Qt.rgba( 0.95, 0.95, 0.95, 1.0 )
property real shininess: 1.0
property real lineWidth: 0.8
property color lineColor: Qt.rgba( 0.2, 0.2, 0.2, 1.0 )
property vector3d lightIntensity: Qt.vector3d(0.7, 0.7, 0.7)
property vector4d lightPosition: Qt.vector4d(0.0, 0.0, 0.0, 1.0)
effect: WireframeEffect {}
parameters: [
Parameter { name: "ka"; value: Qt.vector3d(root.ambient.r, root.ambient.g, root.ambient.b) },
Parameter { name: "kd"; value: Qt.vector3d(root.diffuse.r, root.diffuse.g, root.diffuse.b) },
Parameter { name: "ksp"; value: Qt.vector3d(root.specular.r, root.specular.g, root.specular.b) },
Parameter { name: "shininess"; value: root.shininess },
Parameter { name: "line.width"; value: root.lineWidth },
Parameter { name: "line.color"; value: root.lineColor },
Parameter { name: "light.intensity"; value: root.lightIntensity },
Parameter { name: "light.position"; value: root.lightPosition }
]
}

View file

@ -0,0 +1,112 @@
#version 330 core
uniform struct LightInfo {
vec4 position;
vec3 intensity;
} light;
uniform struct LineInfo {
float width;
vec4 color;
} line;
uniform vec3 ka; // Ambient reflectivity
uniform vec3 kd; // Diffuse reflectivity
uniform vec3 ks; // Specular reflectivity
uniform float shininess; // Specular shininess factor
in WireframeVertex {
vec3 position;
vec3 normal;
noperspective vec4 edgeA;
noperspective vec4 edgeB;
flat int configuration;
} fs_in;
out vec4 fragColor;
vec3 adsModel( const in vec3 pos, const in vec3 n )
{
// Calculate the vector from the light to the fragment
vec3 s = normalize( vec3( light.position ) - pos );
// Calculate the vector from the fragment to the eye position (the
// origin since this is in "eye" or "camera" space
vec3 v = normalize( -pos );
// Calculate the diffus component
vec3 diffuse = vec3( max( dot( s, n ), 0.0 ) );
// Calculate the specular component
vec3 specular = vec3(0.0, 0.0, 0.0);
if(shininess > 0)
{
// Refleft the light beam using the normal at this fragment
vec3 r = reflect( -s, n );
vec3( pow( max( dot( r, v ), 0.0 ), shininess ) );
}
// Combine the ambient, diffuse and specular contributions
return light.intensity * ( ka + kd * diffuse + ks * specular );
}
vec4 shadeLine( const in vec4 color )
{
// Find the smallest distance between the fragment and a triangle edge
float d;
if ( fs_in.configuration == 0 )
{
// Common configuration
d = min( fs_in.edgeA.x, fs_in.edgeA.y );
d = min( d, fs_in.edgeA.z );
}
else
{
// Handle configuration where screen space projection breaks down
// Compute and compare the squared distances
vec2 AF = gl_FragCoord.xy - fs_in.edgeA.xy;
float sqAF = dot( AF, AF );
float AFcosA = dot( AF, fs_in.edgeA.zw );
d = abs( sqAF - AFcosA * AFcosA );
vec2 BF = gl_FragCoord.xy - fs_in.edgeB.xy;
float sqBF = dot( BF, BF );
float BFcosB = dot( BF, fs_in.edgeB.zw );
d = min( d, abs( sqBF - BFcosB * BFcosB ) );
// Only need to care about the 3rd edge for some configurations.
if ( fs_in.configuration == 1 || fs_in.configuration == 2 || fs_in.configuration == 4 )
{
float AFcosA0 = dot( AF, normalize( fs_in.edgeB.xy - fs_in.edgeA.xy ) );
d = min( d, abs( sqAF - AFcosA0 * AFcosA0 ) );
}
d = sqrt( d );
}
// Blend between line color and phong color
float mixVal;
if ( d < line.width - 1.0 )
{
mixVal = 1.0;
}
else if ( d > line.width + 1.0 )
{
mixVal = 0.0;
}
else
{
float x = d - ( line.width - 1.0 );
mixVal = exp2( -2.0 * ( x * x ) );
}
return mix( color, line.color, mixVal );
}
void main()
{
// Calculate the color from the phong model
vec4 color = vec4( adsModel( fs_in.position, normalize( fs_in.normal ) ), 1.0 );
fragColor = shadeLine( color );
}

View file

@ -0,0 +1,131 @@
#version 330 core
layout( triangles ) in;
layout( triangle_strip, max_vertices = 3 ) out;
in EyeSpaceVertex {
vec3 position;
vec3 normal;
} gs_in[];
out WireframeVertex {
vec3 position;
vec3 normal;
noperspective vec4 edgeA;
noperspective vec4 edgeB;
flat int configuration;
} gs_out;
uniform mat4 viewportMatrix;
const int infoA[] = int[]( 0, 0, 0, 0, 1, 1, 2 );
const int infoB[] = int[]( 1, 1, 2, 0, 2, 1, 2 );
const int infoAd[] = int[]( 2, 2, 1, 1, 0, 0, 0 );
const int infoBd[] = int[]( 2, 2, 1, 2, 0, 2, 1 );
vec2 transformToViewport( const in vec4 p )
{
return vec2( viewportMatrix * ( p / p.w ) );
}
void main()
{
gs_out.configuration = int(gl_in[0].gl_Position.z < 0) * int(4)
+ int(gl_in[1].gl_Position.z < 0) * int(2)
+ int(gl_in[2].gl_Position.z < 0);
// If all vertices are behind us, cull the primitive
if (gs_out.configuration == 7)
return;
// Transform each vertex into viewport space
vec2 p[3];
p[0] = transformToViewport( gl_in[0].gl_Position );
p[1] = transformToViewport( gl_in[1].gl_Position );
p[2] = transformToViewport( gl_in[2].gl_Position );
if (gs_out.configuration == 0)
{
// Common configuration where all vertices are within the viewport
gs_out.edgeA = vec4(0.0);
gs_out.edgeB = vec4(0.0);
// Calculate lengths of 3 edges of triangle
float a = length( p[1] - p[2] );
float b = length( p[2] - p[0] );
float c = length( p[1] - p[0] );
// Calculate internal angles using the cosine rule
float alpha = acos( ( b * b + c * c - a * a ) / ( 2.0 * b * c ) );
float beta = acos( ( a * a + c * c - b * b ) / ( 2.0 * a * c ) );
// Calculate the perpendicular distance of each vertex from the opposing edge
float ha = abs( c * sin( beta ) );
float hb = abs( c * sin( alpha ) );
float hc = abs( b * sin( alpha ) );
// Now add this perpendicular distance as a per-vertex property in addition to
// the position and normal calculated in the vertex shader.
// Vertex 0 (a)
gs_out.edgeA = vec4( ha, 0.0, 0.0, 0.0 );
gs_out.normal = gs_in[0].normal;
gs_out.position = gs_in[0].position;
gl_Position = gl_in[0].gl_Position;
EmitVertex();
// Vertex 1 (b)
gs_out.edgeA = vec4( 0.0, hb, 0.0, 0.0 );
gs_out.normal = gs_in[1].normal;
gs_out.position = gs_in[1].position;
gl_Position = gl_in[1].gl_Position;
EmitVertex();
// Vertex 2 (c)
gs_out.edgeA = vec4( 0.0, 0.0, hc, 0.0 );
gs_out.normal = gs_in[2].normal;
gs_out.position = gs_in[2].position;
gl_Position = gl_in[2].gl_Position;
EmitVertex();
// Finish the primitive off
EndPrimitive();
}
else
{
// Viewport projection breaks down for one or two vertices.
// Caclulate what we can here and defer rest to fragment shader.
// Since this is coherent for the entire primitive the conditional
// in the fragment shader is still cheap as all concurrent
// fragment shader invocations will take the same code path.
// Copy across the viewport-space points for the (up to) two vertices
// in the viewport
gs_out.edgeA.xy = p[infoA[gs_out.configuration]];
gs_out.edgeB.xy = p[infoB[gs_out.configuration]];
// Copy across the viewport-space edge vectors for the (up to) two vertices
// in the viewport
gs_out.edgeA.zw = normalize( gs_out.edgeA.xy - p[ infoAd[gs_out.configuration] ] );
gs_out.edgeB.zw = normalize( gs_out.edgeB.xy - p[ infoBd[gs_out.configuration] ] );
// Pass through the other vertex attributes
gs_out.normal = gs_in[0].normal;
gs_out.position = gs_in[0].position;
gl_Position = gl_in[0].gl_Position;
EmitVertex();
gs_out.normal = gs_in[1].normal;
gs_out.position = gs_in[1].position;
gl_Position = gl_in[1].gl_Position;
EmitVertex();
gs_out.normal = gs_in[2].normal;
gs_out.position = gs_in[2].position;
gl_Position = gl_in[2].gl_Position;
EmitVertex();
// Finish the primitive off
EndPrimitive();
}
}

View file

@ -0,0 +1,21 @@
#version 330 core
in vec3 vertexPosition;
in vec3 vertexNormal;
out EyeSpaceVertex {
vec3 position;
vec3 normal;
} vs_out;
uniform mat4 modelView;
uniform mat3 modelViewNormal;
uniform mat4 mvp;
void main()
{
vs_out.normal = normalize( modelViewNormal * vertexNormal );
vs_out.position = vec3( modelView * vec4( vertexPosition, 1.0 ) );
gl_Position = mvp * vec4( vertexPosition, 1.0 );
}

View file

@ -14,6 +14,7 @@ FocusScope {
property alias abcSource: modelLoader.abcSource
property alias depthMapSource: modelLoader.depthMapSource
property int renderMode: 2
readonly property alias loading: modelLoader.loading
// Alembic optional support => won't be available if AlembicEntity plugin is not available
@ -62,8 +63,11 @@ FocusScope {
})
entities.forEach(function(entity) {
var comps = [];
var mats = []
var hasTextures = false
// Create as many MaterialSwitcher as individual materials for this entity
// NOTE: we let each MaterialSwitcher modify the components of the entity
// and therefore remove the default material spawned by the sceneLoader
for(var i=0; i < entity.components.length; ++i)
{
var comp = entity.components[i]
@ -76,32 +80,23 @@ FocusScope {
"shininess": comp.shininess,
"specular": comp.specular,
"ambient": comp.ambient,
"showTextures": texturesCheckBox.checked
"mode": root.renderMode
}
mats.push(m)
// unparent previous material
// and exclude it from the entity components
comp.parent = null
continue; // skip original component and continue
hasTextures = true
}
// make default material brighter
if(comp.toString().indexOf("QPhongMaterial") > -1) {
comp.diffuse = "#AAA"
comp.ambient = "#AAA"
// create MaterialSwitcher with default colors
mats.push({})
}
comps.push(comp)
}
entity.components = comps
modelLoader.meshHasTexture = mats.length > 0
mats.forEach(function(m){
// create a material switcher for each material definition
var matSwitcher = materialSwitcherComponent.createObject(entity, m)
// trigger showTextures update by inverting it
// and re-bind textures checkbox to texture switch property
// (this double update ensure the texture display is correct)
matSwitcher.showTextures = !matSwitcher.showTextures
matSwitcher.showTextures = Qt.binding(function(){ return texturesCheckBox.checked })
matSwitcher.mode = Qt.binding(function(){ return root.renderMode })
})
})
}
@ -395,9 +390,11 @@ FocusScope {
}
}
// Outliner
Pane {
background: Rectangle { color: palette.base; opacity: 0.5; radius: 2 }
anchors.right: parent.right
background: Rectangle { color: palette.base; opacity: 0.5; radius: 2 }
Column {
Row {
CheckBox { id: showSfMCheckBox; text: "SfM"; checked: true; visible: root.supportAlembic; opacity: root.abcSource ? 1.0 : 0.6 }
@ -427,12 +424,39 @@ FocusScope {
ToolTip.visible: hovered
}
}
CheckBox { id: texturesCheckBox; text: "Textures"; checked: true; opacity: modelLoader.meshHasTexture ? 1.0 : 0.6 }
CheckBox { id: gridCheckBox; text: "Grid"; checked: true }
CheckBox { id: locatorCheckBox; text: "Locator"; checked: true }
}
}
// Render Mode
Pane {
anchors.bottom: parent.bottom
background: Rectangle { color: palette.base; opacity: 0.5; radius: 2 }
Row {
anchors.verticalCenter: parent.verticalCenter
Repeater {
model: [ // Can't use ListModel because of MaterialIcons expressions
{"name": "Solid", "icon": MaterialIcons.crop_din},
{"name": "Wireframe", "icon": MaterialIcons.grid_on},
{"name": "Textured", "icon": MaterialIcons.texture },
]
delegate: ToolButton {
text: modelData["icon"]
ToolTip.text: modelData["name"]
ToolTip.visible: hovered
font.family: MaterialIcons.fontFamily
font.pointSize: 11
padding: 4
onClicked: root.renderMode = index
checkable: !checked // hack to disable check toggle on click
checked: renderMode === index
}
}
}
}
// Menu
Menu {
id: contextMenu