I have the following 3D object:
The material of my 3D object is coded like this with Qt3D:
void MyClass::addMaterial(Qt3DCore::QEntity *entity)
{
Qt3DExtras::QPhongMaterial * material = new Qt3DExtras::QPhongMaterial();
material->setAmbient(QColor(245-30, 245-15, 245));
material->setDiffuse(QColor(125-30, 125-15, 125));
material->setSpecular(QColor(215-30, 255-15, 255));
entity->addComponent(material);
}
The above code gives the same colors to 3D object entity uniformly. How can I give different colors to different sections of my entity? I want to highlight some section on my 3D object, is there a way to do that with Qt3D?
If it isn't possible with Qt3D, what is the best practice to do it with OpenGL?
I found a discussion which I feel like it would be very helpful.
After doing some research, the best approach might be to use OpenGL shading language or GLSL. I'm not sure.
In Qt I would imagine they are using "materials" as an abstraction of their predefined shaders. You create a material such as QPhongMaterial that you want to render an entity with and set things for that material which will be passed in as arguments to the shader (uniforms) like the ambient, specular and diffuse lighting color.
So you need a shader that will support vertex colors for your model. So each vertex of your model will have a color associated with it and these values get passed in to the vertex shader and forwarded to the fragment shader. It's fairly straightforward to do this with OpenGL but you're using Qt3D so it might be easier to use the infrastructure that's already there.
Qt3D already has some material classes that you can use to create some of these shaders to apply to your entity. You could try using the QPerVertexColorMaterial instead.
https://doc.qt.io/qt-5/qt3dextras-qpervertexcolormaterial.html
Qt3DExtras::QPerVertexColorMaterial Default implementation for
rendering the color properties set for each vertex
Obviously you will need to provide the list of vertex colors in order to color parts of the model differently. In addition to this, you will need to provide vertex colors for each vertex, even if you want to color each vertex of the triangle all the same color (face colors).
Alternatively you could create your own shader and give it to Qt3D to bind to the pipeline:
(from the forum)
Qt3D does not generate shaders at runtime. For its default pipeline it comes with predefined default shaders. But you are free to change the pipeline as you wish (in C++ and also in QML) and you can use your own shaders.
For custom materials the following example look promising:
https://doc.qt.io/qt-5/qt3d-simplecustommaterial-example.html
And for information on Qt3D shaders:
https://doc.qt.io/qt-5/qml-qt3d-render-shaderprogram.html
ShaderProgram class encapsulates a shader program. A shader program consists of several different shaders, such as vertex and fragment shaders.
Qt3D will automatically populate a set of default uniforms if they are encountered during the shader instrospection phase.
If you've never written a shader in GLSL and compiled it in an OpenGL program you might want to first get up to speed on how that is done as you need to understand the parts that the vertex, geometry and fragment shaders all play as well as the role of attributes and uniforms. I suppose you could still get the job done without doing that but I would imagine it would be significantly harder.
There is pages and pages out there on shaders but just briefly...
A shader generally comprises of three parts; the vertex shader, the geometry shader and the fragment shader, these are written using GLSL and usually saved as three files. At a minimum to compile a shader you need the vertex shader source and the fragment shader source.
Generally people like to save these files with the respective extension such as .frag, .vert or .vs/ .fs but ultimately they are just text files.
To compile a shader and bind it to the render pipeline you need to load the source from the respective files and link them to create your shader program which you can then use by binding it to the render pipeline and rendering your geometry. Lazy Foo has an awesome tutorial on how that is done in OpenGL: http://lazyfoo.net/tutorials/SDL/51_SDL_and_modern_opengl/index.php
If you were just using OpenGL, you would first write a vertex shader then a fragment shader with the correct inputs/outputs for rendering geometry with vertex colors, then you would go through that process to create your shader program.
As for the shader implementation itself, here is a quick implementation of what your vertex and fragment shader would look like:
Vertex Shader (Color.vs)
#version 330
in vec3 position;
in vec4 vertexColor;
uniform mat4 WORLD_VIEW_PROJECTION_MATRIX;
out vec4 fragColor;
void main()
{
fragColor.x = vertexColor.x;
fragColor.y = vertexColor.y;
fragColor.z = vertexColor.z;
fragColor.w = vertexColor.w; //alpha
gl_Position = WORLD_VIEW_PROJECTION_MATRIX * vec4( position, 1 );
}
Fragment Shader (Color.fs)
#version 330
out vec4 LFragment;
in vec4 fragColor;
void main()
{
LFragment = fragColor;
}
With the great help of @AdaRaider I managed to implement a custom effect with modifiable vertex shader and fragment shader. Custom effect can be used like this:
#include "customeffect.h"
static const QColor ambientColor("#576675"); // Shader input
static const QColor diffuseColor("#5F6E7D"); // Shader input
static const QColor SpecularColor("#61707F"); // Shader input
static const float shininess(0.0); // Shader input
void MyClass::addMaterial(Qt3DCore::QEntity *entity)
{
Qt3DRender::QMaterial * material = new Qt3DRender::QMaterial();
material->setEffect(new CustomEffect());
material->addParameter(new Qt3DRender::QParameter(QStringLiteral("ka"), ambientColor));
material->addParameter(new Qt3DRender::QParameter(QStringLiteral("kd"), diffuseColor));
material->addParameter(new Qt3DRender::QParameter(QStringLiteral("ks"), SpecularColor));
material->addParameter(new Qt3DRender::QParameter(QStringLiteral("shininess"), shininess));
entity->addComponent(material);
}
Custom effect header is:
#ifndef CUSTOMEFFECT_H
#define CUSTOMEFFECT_H
#include <Qt3DRender/QEffect>
class CustomEffect : public Qt3DRender::QEffect
{
public:
explicit CustomEffect(Qt3DCore::QNode *parent = nullptr);
};
#endif // CUSTOMEFFECT_H
The implementation of custom effect is with two versions of OpenGL in mind, OpenGL ES 2.0 and OpenGL 3.1:
#include "customeffect.h"
#include <Qt3DRender/QTechnique>
#include <Qt3DRender/QGraphicsApiFilter>
#include <QtCore/QUrl>
CustomEffect::CustomEffect(Qt3DCore::QNode *parent)
: Qt3DRender::QEffect(parent)
{
Qt3DRender::QTechnique *techniqueES20 = new Qt3DRender::QTechnique();
techniqueES20->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile);
techniqueES20->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGLES);
techniqueES20->graphicsApiFilter()->setMajorVersion(2);
techniqueES20->graphicsApiFilter()->setMinorVersion(0);
Qt3DRender::QTechnique *techniqueGL31 = new Qt3DRender::QTechnique();
techniqueGL31->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::CoreProfile);
techniqueGL31->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL);
techniqueGL31->graphicsApiFilter()->setMajorVersion(3);
techniqueGL31->graphicsApiFilter()->setMinorVersion(1);
Qt3DRender::QFilterKey *filterkey = new Qt3DRender::QFilterKey(this);
filterkey->setName(QStringLiteral("renderingStyle"));
filterkey->setValue(QStringLiteral("forward"));
techniqueES20->addFilterKey(filterkey);
techniqueGL31->addFilterKey(filterkey);
Qt3DRender::QShaderProgram *shader2 = new Qt3DRender::QShaderProgram();
shader2->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl(QStringLiteral("qrc:/qt3deditorlib/shaders/es2/custom-shader.vert"))));
shader2->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl(QStringLiteral("qrc:/qt3deditorlib/shaders/es2/custom-shader.frag"))));
Qt3DRender::QShaderProgram *shader3 = new Qt3DRender::QShaderProgram();
shader3->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl(QStringLiteral("qrc:/qt3deditorlib/shaders/gl3/custom-shader.vert"))));
shader3->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl(QStringLiteral("qrc:/qt3deditorlib/shaders/gl3/custom-shader.frag"))));
Qt3DRender::QRenderPass *renderPass2 = new Qt3DRender::QRenderPass();
renderPass2->setShaderProgram(shader2);
Qt3DRender::QRenderPass *renderPass3 = new Qt3DRender::QRenderPass();
renderPass3->setShaderProgram(shader3);
techniqueES20->addRenderPass(renderPass2);
techniqueGL31->addRenderPass(renderPass3);
addTechnique(techniqueES20);
addTechnique(techniqueGL31);
}
When I copy Phong shader default code inside my custom-shader.vert
and custom-shader.frag
files, it works exactly like Phong material, which shows the custom effect works fine.
Now that the above custom effect is implemented, its shaders can be modified by the methods described by @AdaRaider to produce any desired effect.