diff --git a/meshroom/ui/qml/Viewer/MaterialSwitcher.qml b/meshroom/ui/qml/Viewer/MaterialSwitcher.qml index 781c9f6f..8972a257 100644 --- a/meshroom/ui/qml/Viewer/MaterialSwitcher.qml +++ b/meshroom/ui/qml/Viewer/MaterialSwitcher.qml @@ -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 + } + } diff --git a/meshroom/ui/qml/Viewer/Materials/WireframeEffect.qml b/meshroom/ui/qml/Viewer/Materials/WireframeEffect.qml new file mode 100644 index 00000000..abe3f32f --- /dev/null +++ b/meshroom/ui/qml/Viewer/Materials/WireframeEffect.qml @@ -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")) + } + } + ] + } + ] +} diff --git a/meshroom/ui/qml/Viewer/Materials/WireframeMaterial.qml b/meshroom/ui/qml/Viewer/Materials/WireframeMaterial.qml new file mode 100644 index 00000000..5d3ef7e1 --- /dev/null +++ b/meshroom/ui/qml/Viewer/Materials/WireframeMaterial.qml @@ -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 } + ] +} diff --git a/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.frag b/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.frag new file mode 100644 index 00000000..89d21e98 --- /dev/null +++ b/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.frag @@ -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 ); +} diff --git a/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.geom b/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.geom new file mode 100644 index 00000000..6eb0ecc7 --- /dev/null +++ b/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.geom @@ -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(); + } +} diff --git a/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.vert b/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.vert new file mode 100644 index 00000000..e53a2b59 --- /dev/null +++ b/meshroom/ui/qml/Viewer/Materials/shaders/robustwireframe.vert @@ -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 ); +} diff --git a/meshroom/ui/qml/Viewer/Viewer3D.qml b/meshroom/ui/qml/Viewer/Viewer3D.qml index 0d3e45c0..3de98931 100644 --- a/meshroom/ui/qml/Viewer/Viewer3D.qml +++ b/meshroom/ui/qml/Viewer/Viewer3D.qml @@ -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