[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.Core 2.0
import Qt3D.Render 2.9 import Qt3D.Render 2.9
import Qt3D.Input 2.0 import Qt3D.Input 2.0
import Qt3D.Extras 2.9 import Qt3D.Extras 2.10
import QtQuick 2.0 import QtQuick 2.0
import "Materials"
/** /**
* MaterialSwitcher is an Entity that can change its parent's material * MaterialSwitcher is an Entity that can change its parent's material
* according to a set of property it exposes. * by setting the 'mode' property.
* It can be used to toggle between a Phong and a DiffuseMapColor
* depending on 'showTextures' value.
*/ */
Entity { Entity {
id: root id: root
objectName: "MaterialSwitcher" objectName: "MaterialSwitcher"
property bool showTextures: true property int mode: 2
readonly property var modes: ["Solid", "Wireframe", "Textured"]
property string diffuseMap: "" property string diffuseMap: ""
property color ambient: "#AAA" property color ambient: "#AAA"
property real shininess property real shininess
property color specular property color specular
property color diffuseColor: "#AAA" property color diffuseColor: "#AAA"
property alias object: instantiator.object
NodeInstantiator { readonly property alias activeMaterial: m.material
id: instantiator
delegate: root.showTextures ? textured : colored QtObject {
id: m
property Material material
onMaterialChanged: {
// remove previous material(s)
removeComponentsByType(parent, "Material")
addComponent(root.parent, material)
}
}
// add the created Node delegate to the root's parent components function printComponents(entity)
onObjectAdded: { {
if(!root.parent) console.log("Components of Entity '" + entity + "'")
for(var i=0; i < entity.components.length; ++i)
{
console.log(" -- [" + i + "]: " + entity.components[i])
}
}
function addComponent(entity, component)
{
if(!entity)
return return
var comps = []; var comps = [];
for(var i=0; i < root.parent.components.length; ++i) comps.push(component);
for(var i=0; i < entity.components.length; ++i)
{ {
comps.push(root.parent.components[i]); comps.push(entity.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;
} }
entity.components = comps;
} }
Component { function removeComponentsByType(entity, type)
id: colored {
PhongMaterial { 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 parent: root.parent
objectName: "DiffuseColorMaterial" objectName: "SolidMaterial"
ambient: root.ambient
shininess: root.shininess
specular: root.specular
diffuse: root.diffuseColor
}
DiffuseSpecularMaterial {
id: textured
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 ambient: root.ambient
diffuse: root.diffuseColor diffuse: root.diffuseColor
shininess: root.shininess shininess: 0
specular: root.specular specular: root.specular
} }
}
Component {
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
}
}
}
} }

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 abcSource: modelLoader.abcSource
property alias depthMapSource: modelLoader.depthMapSource property alias depthMapSource: modelLoader.depthMapSource
property int renderMode: 2
readonly property alias loading: modelLoader.loading readonly property alias loading: modelLoader.loading
// Alembic optional support => won't be available if AlembicEntity plugin is not available // Alembic optional support => won't be available if AlembicEntity plugin is not available
@ -62,8 +63,11 @@ FocusScope {
}) })
entities.forEach(function(entity) { entities.forEach(function(entity) {
var comps = [];
var mats = [] 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) for(var i=0; i < entity.components.length; ++i)
{ {
var comp = entity.components[i] var comp = entity.components[i]
@ -76,32 +80,23 @@ FocusScope {
"shininess": comp.shininess, "shininess": comp.shininess,
"specular": comp.specular, "specular": comp.specular,
"ambient": comp.ambient, "ambient": comp.ambient,
"showTextures": texturesCheckBox.checked "mode": root.renderMode
} }
mats.push(m) mats.push(m)
// unparent previous material hasTextures = true
// and exclude it from the entity components
comp.parent = null
continue; // skip original component and continue
} }
// make default material brighter
if(comp.toString().indexOf("QPhongMaterial") > -1) { if(comp.toString().indexOf("QPhongMaterial") > -1) {
comp.diffuse = "#AAA" // create MaterialSwitcher with default colors
comp.ambient = "#AAA" mats.push({})
} }
comps.push(comp)
} }
entity.components = comps
modelLoader.meshHasTexture = mats.length > 0 modelLoader.meshHasTexture = mats.length > 0
mats.forEach(function(m){ mats.forEach(function(m){
// create a material switcher for each material definition // create a material switcher for each material definition
var matSwitcher = materialSwitcherComponent.createObject(entity, m) var matSwitcher = materialSwitcherComponent.createObject(entity, m)
// trigger showTextures update by inverting it matSwitcher.mode = Qt.binding(function(){ return root.renderMode })
// 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 })
}) })
}) })
} }
@ -395,9 +390,11 @@ FocusScope {
} }
} }
// Outliner
Pane { Pane {
background: Rectangle { color: palette.base; opacity: 0.5; radius: 2 }
anchors.right: parent.right anchors.right: parent.right
background: Rectangle { color: palette.base; opacity: 0.5; radius: 2 }
Column { Column {
Row { Row {
CheckBox { id: showSfMCheckBox; text: "SfM"; checked: true; visible: root.supportAlembic; opacity: root.abcSource ? 1.0 : 0.6 } 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 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: gridCheckBox; text: "Grid"; checked: true }
CheckBox { id: locatorCheckBox; text: "Locator"; 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
Menu { Menu {
id: contextMenu id: contextMenu