Text rendering not working correctly - c++

so I'm trying to render some text using FTGL, GLFW and GLEW libraries in my C++ engine following this tutorial: https://www.youtube.com/watch?v=Rsdc6I80aFQ.
But when I do so where the text should be instead of characters I get triangles of some kind (screenshot):
I wanted to use the same shader as when I'm rendering normal sprites, so I set the model matrix in shader to identity matrix and instead I'm passing character positions to the shader with VBO.
Here's font class:
class Font
{
private:
/* Variables */
ftgl::texture_atlas_t* m_atlas;
ftgl::texture_font_t* m_font;
public:
/* Constructors */
Font(const char* filename, const float size)
: m_atlas(ftgl::texture_atlas_new(512, 512, 1)),
m_font(ftgl::texture_font_new_from_file(m_atlas, size, filename))
{}
/* Functions */
void bindFontTexture() const
{
GLcall( glBindTexture(GL_TEXTURE_2D, m_atlas->id) );
}
// Getters
inline ftgl::texture_font_t* getFTGLFont() const { return m_font; }
};
And here is label class (labels are 2D renderables with some text in my engine):
class Label2D : public Renderable2D
{
protected:
/* Variables */
std::string m_text;
Color m_color;
Font* m_font;
public:
/* Constructors */
Label2D(const std::string& text, const vector2& pos, const vector2& size, Font* font)
: Renderable2D(pos, size), m_text(text), m_font(font)
{}
Label2D(const std::string& text, const vector2& pos, const vector2& size, Font* font, const Color& color)
: Renderable2D(pos, size), m_text(text), m_font(font), m_color(color)
{}
/* Functions */
void bindFontTexture() const
{
m_font->bindFontTexture();
}
// Setters
void setText(const std::string& text)
{
m_text = text;
}
void setColor(const Color& color)
{
m_color = color;
}
void setFont(Font* font)
{
m_font = font;
}
// Getters
inline const std::string& getText() const { return m_text; }
inline const Color& getColor() const { return m_color; }
inline ftgl::texture_font_t* getFTGLFont() const { return m_font->getFTGLFont(); }
};
Here's my text rendering code. Now I DO know this code is written terribly since I shouldn't create a VAO and VBO everytime I want to render some text, but I did it only for test purposes, I just want to be sure that library is working fine, then I will organize the code:
void Layer2D::m_renderLabels(const bool renderLighting)
{
// Uniforms Setup
m_textureShader.start();
m_textureShader.setUniformMatrix4f("u_modelMatrix", matrix4(1.0f));f));
// Rendering Labels from std::vector
for (Label2D* label : m_labels)
{
// Binding
m_textureVAO.bind();
label->bindFontTexture();
// Starting Shader
m_textureShader.start();
// Preparation Stuff
std::vector<float> positions;
std::vector<float> textureCoordinates;
float offsetX = label->getPosition().x;
// Loading Characters
for (unsigned int i = 0; i < label->getText().length(); i++)
{
// Loading Glyph
ftgl::texture_glyph_t* glyph = ftgl::texture_font_get_glyph(label->getFTGLFont(), &label->getText().at(i));
if (glyph != NULL)
{
if (i > 0)
{
float kerning = texture_glyph_get_kerning(glyph, &label->getText().at(i - 1));
offsetX += kerning;
}
// Glyph Position/Offset/Size Calculations
float x0 = offsetX + glyph->offset_x;
float y0 = label->getPosition().y + glyph->offset_y;
float x1 = x0 + glyph->width;
float y1 = y0 - glyph->height;
float u0 = glyph->s0;
float v0 = glyph->t0;
float u1 = glyph->s1;
float v1 = glyph->t1;
positions.push_back(x0);
positions.push_back(y0);
positions.push_back(x0);
positions.push_back(y1);
positions.push_back(x1);
positions.push_back(y1);
positions.push_back(x1);
positions.push_back(y0);
textureCoordinates.push_back(u0);
textureCoordinates.push_back(v0);
textureCoordinates.push_back(u0);
textureCoordinates.push_back(v1);
textureCoordinates.push_back(u1);
textureCoordinates.push_back(v1);
textureCoordinates.push_back(u1);
textureCoordinates.push_back(v0);
offsetX += glyph->advance_x;
}
}
VertexArray textVAO;
VertexBuffer* textPosVBO = new VertexBuffer(&positions[0], positions.size(), 2);
VertexBuffer* textTexCoordsVBO = new VertexBuffer(&textureCoordinates[0], textureCoordinates.size(), 2);
textVAO.addAttribute(textPosVBO, 0);
textVAO.addAttribute(textTexCoordsVBO, 1);
textVAO.bind();
// Rendering
GLcall( glDrawArrays(GL_TRIANGLE_STRIP, 0, 4 * label->getText().length()) );
}
}
And lastly that's the shader code:
Vertex:
#shader vertex
#version 330 core
layout(location = 0) in vec2 in_position;
layout(location = 1) in vec2 in_textureCoordinates;
out vec2 pass_textureCoordinates;
uniform mat4 u_projectionMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_modelMatrix;
void main()
{
pass_textureCoordinates = in_textureCoordinates;
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * vec4(in_position, 0.0, 1.0);
}
And fragment:
#shader fragment
#version 330 core
in vec2 pass_textureCoordinates;
out vec4 out_color;
uniform sampler2D u_textureSampler;
void main()
{
vec4 textureColor = texture2D(u_textureSampler, pass_textureCoordinates);
out_color = textureColor;
out_color = vec4(1.0, 0.0, 0.0, 1.0); //done for test purposes only (it still doesn't work when this line is removed)
}
I'm sorry if I wasn't specific enough, if I could provide any more information to help You help me, just please comment down below.
Thanks in advance!
EDIT:
If You were wondering, I do set projection and view matrices in other parts of code:
m_textureShader.start();
m_textureShader.setUniformMatrix4f("u_projectionMatrix", m_projectionMatrix);
m_textureShader.setUniformMatrix4f("u_viewMatrix", viewMatrix);
EDIT: After swapping the last 2 vertices I get quads now, but still not a text (screenshot) The quads aren't red because I removed the debugging line from fragment shader.

Related

Custom shaders/material not working in a custom QQuickItem object

I have tried following the tutorial given by QT's online documentation (QtQuick Scenegraph CustomMaterial Example), but when I debugged my program, the object does not show, appearing to be transparent. However, when I tried replacing my custom shader class with QSGFlatColorMaterial, and set a color, the object does show without any issues. I double checked my implementation of the tutorial with the example code in the example git repo.
Here is my code in question
/* MapDisplay Class */
MapDisplay::MapDisplay(QQuickItem* parent) : QQuickItem(parent) {
this->setFlag(ItemHasContents, true);
};
QSGNode* MapDisplay::updatePaintNode(QSGNode* old, UpdatePaintNodeData*) {
auto* node = static_cast<MapNode*>(old);
if (!node) {
node = new MapNode();
}
if (this->flag_geo_changed) {
node->ChangeRectBounds(this->boundingRect());
this->flag_geo_changed = false;
}
const QRectF rect = this->boundingRect();
auto* vertices = node->geometry()->vertexDataAsPoint2D();
vertices[0].set(rect.bottomLeft().x(), 1);
vertices[1].set(200, 0);
vertices[2].set(0, 200);
vertices[3].set(200, 200);
node->markDirty(QSGNode::DirtyGeometry);
return node;
}
void MapDisplay::geometryChange(const QRectF& new_geo, const QRectF& old_geo) {
this->flag_geo_changed = false;
this->update();
QQuickItem::geometryChange(new_geo, old_geo);
}
/* MapShader Class */
MapShader::MapShader() {
this->setShaderFileName(VertexStage, ":/geo/shader/map.vert.qsb");
this->setShaderFileName(FragmentStage, ":/geo/shader/map.frag.qsb");
};
/* MapMaterial Class */
MapMaterial::MapMaterial(){};
MapMaterial::~MapMaterial(){};
QSGMaterialType* MapMaterial::type() const {
static QSGMaterialType type;
return &type;
}
int MapMaterial::compare(const QSGMaterial* o) const {
Q_ASSERT(o && this->type() == o->type());
const auto* other = static_cast<const MapMaterial*>(o);
return other == this ? 0 : 1;
}
QSGMaterialShader* MapMaterial::createShader(
QSGRendererInterface::RenderMode) const {
return new MapShader();
}
/* MapNode Class */
MapNode::MapNode() {
// Material
auto* mat = new MapMaterial();
this->setMaterial(mat);
this->setFlag(QSGGeometryNode::OwnsMaterial, true);
// Geometry
auto* geo = get_geo_data::GetRectShape();
this->setGeometry(geo);
this->setFlag(QSGGeometryNode::OwnsGeometry, true);
}
void MapNode::ChangeRectBounds(const QRectF& bounds) {
QSGGeometry::updateTexturedRectGeometry(this->geometry(), bounds,
QRectF(0, 0, 0, 0));
this->markDirty(QSGNode::DirtyGeometry);
}
And here is the link to the example code I cross checked with customitem.cpp
Here are also my shaders too,
#version 460
// map.vert
layout(location = 0) in vec4 vertex_object;
layout(location = 1) in vec2 atex_coord;
layout(location = 0) out vec2 vtex_coord;
void main() {
gl_Position = vertex_object;
vtex_coord = atex_coord;
}
#version 460
// map.frag
layout(location = 0) out vec4 frag_color;
void main() {
frag_color = vec4(0.0, 1.0, 0.0, 1.0);
}
And here are also some screenshots too:
If I use QSGFlatColorMaterial
If I use my custom material and shaders
Ok, so after prodding through the example repo, I did not multiply the vertex_object in the vert shader with the matrix of the object.
So after implementing the methods and glsl code from the documention into mine's, I got the shader to show properly. Turns out that I did not properly set the shader position
map.cpp
bool MapShader::updateUniformData(RenderState& state, QSGMaterial* new_material,
QSGMaterial* old_material) {
bool changed = false;
QByteArray* buf = state.uniformData();
Q_ASSERT(buf->size() >= 64);
if (state.isMatrixDirty()) {
const QMatrix4x4 m = state.combinedMatrix();
std::memcpy(buf->data(), m.constData(), 64);
changed = true;
}
auto* cus_masterial = static_cast<MapMaterial*>(new_material);
if (old_material != new_material || cus_masterial->uniform.dirty) {
cus_masterial->uniform.dirty = false;
changed = true;
}
return changed;
}
map.vert
#version 460
layout(location = 0) in vec4 vertex_object;
layout(location = 1) in vec2 atex_coord;
layout(location = 0) out vec2 vtex_coord;
layout(std140, binding=0) uniform buf {
mat4 qt_matrix; // offset 0
} ubuf;
void main() {
gl_Position = ubuf.qt_matrix /* Very important */ * vertex_object;
vtex_coord = atex_coord;
}

GLSL black screen, shader doesn't do anything

I am writing a basic Sphere-Tracer in a fragment shader, everything is doing fine if I just color points according to their surface normals, but as soon as I try to implement the reflecting algorithm, it takes a bit longer to compile, and just blackscreens. The fps starts out at 1 but quickly goes up to 1000, which is the code limit I put, telling me the shader is actually not doing anything and just ignores my code, which should be much slower than that. I initially thought I was hitting the instruction limit of my GPU, but I don't think that's the problem, as using the normal-shading code, I can set MAX_MARCH to high values, like 1000, but using reflections, even with MAX_REFLECTIONS and MAX_AA to one, which should be similar to the amount of instructions of normal-shading(maybe ~50 more, not significant I don't think). But I need to set MAX_MARCH to 1 for it to render, even setting it to two causes the bug.
Vertex shader:
//Draw a quad on the whole display and calculates sky color
#version 400 core
in vec3 position;
out vec2 uvPos;
out vec4 backColor;
void main(void){
gl_Position = vec4(position, 1.0);
uvPos = position.xy*0.5+0.5;
backColor = mix(vec4(1, 1, 1, 1), vec4(0.5, 0.7, 1, 1), uvPos.y);
}
Fragment shader:
#version 400 core
#define FLT_MAX 3.402823466e+38
#define FLT_MIN 1.175494351e-38
#define DBL_MAX 1.7976931348623158e+308
#define DBL_MIN 2.2250738585072014e-308
#define PI 3.141592653589793115997963468544185161590576171875
#define MAX_AA 1
#define MAX_MARCH 1000
#define MAX_REFLECTIONS 10
#define MAX_DIST 10
in vec2 uvPos;
in vec4 backColor;
out vec4 outColor;
int randomIterator = 0;
//############################################################ Structure definitions #########################################################################
struct Material{
int type;
vec3 albedo;
};
struct Object{
int type; //1:Sphere, 2:Box
vec3 center;
float radius;
vec3 size;
Material material;
};
struct Scene{
Object objects[3];
};
struct Ray{
vec3 origin;
vec3 dir;
};
struct HitRecord{
vec3 p;
vec3 n;
Object o;
Material mat;
float closest;
};
struct Camera{
vec3 origin;
vec3 lowerLeftCorner;
vec3 horizontal;
vec3 vertical;
};
//############################################################ Uniforms ####################################################################################
uniform float random[2048];
uniform vec2 resolution;
uniform Camera cam;
uniform Scene scene;
uniform int objectAmount;
//############################################################ Tools
float randf(){
return random[randomIterator++];
}
Ray getRay(Camera cam, vec2 v){
return Ray(cam.origin, normalize(cam.lowerLeftCorner+cam.horizontal*v.s+cam.vertical*v.t-cam.origin));
}
vec3 randOnBall(){
vec3 p;
do{
p = vec3(randf(), randf(), randf())*2-1;
}while(p.length() >= 1);
return p;
}
//############################################################ Signed Distance Functions
float sphereSDF(vec3 p, Object o){
return length(p-o.center)-o.radius;
}
float boxSDF(vec3 p, Object o){
vec3 q = abs(p-o.center) - o.size;
return (length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0));
}
float sceneSDF(vec3 p, Scene s){
float dist = FLT_MAX;
for(int i = 0; i < objectAmount; i++){
switch(s.objects[i].type){
case 1:
dist = min(dist, sphereSDF(p, s.objects[i]));
break;
case 2:
dist = min(dist, boxSDF(p, s.objects[i]));
break;
default:
break;
}
}
return dist;
}
float sceneSDF(vec3 p, Scene s, inout HitRecord rec){
float dist = FLT_MAX;
for(int i = 0; i < objectAmount; i++){
float tmpDist=FLT_MAX;
switch(s.objects[i].type){
case 1:
tmpDist = sphereSDF(p, s.objects[i]);
break;
case 2:
tmpDist = boxSDF(p, s.objects[i]);
break;
default:
break;
}
if(tmpDist<dist){
dist = tmpDist;
rec.o = s.objects[i];
rec.mat = s.objects[i].material;
}
}
return dist;
}
//############################################################ Material Scatter Function
bool scatterDiffuse(Ray r, HitRecord rec, inout vec3 tmpAtt, inout Ray scattered){
tmpAtt = vec3(rec.mat.albedo);
scattered = Ray(rec.p, rec.n+randOnBall());
return true;
}
bool scatter(Ray r, HitRecord rec, inout vec3 tmpAtt, inout Ray scattered){
return scatterDiffuse(r, rec, tmpAtt, scattered); //Starting out with diffuse materials, planned to
add switch-case for different materials
}
//############################################################ Main
vec3 findSceneNormal(Scene s, vec3 p){
const float h = 0.0001; // replace by an appropriate value
const vec2 k = vec2(1,-1);
return normalize( k.xyy*sceneSDF( p + k.xyy*h, s ) +
k.yyx*sceneSDF( p + k.yyx*h, s ) +
k.yxy*sceneSDF( p + k.yxy*h, s ) +
k.xxx*sceneSDF( p + k.xxx*h, s ) );
}
float findSceneIntersect(Ray r, Scene scene, inout HitRecord rec){
float t = 0.005;
vec3 p;
for(int i = 0; i < MAX_MARCH; i++){
p = r.origin+t*r.dir;
float dist = abs(sceneSDF(p, scene, rec));
if(dist < 0.001){
rec.n = findSceneNormal(scene, p);
rec.p = p;
return t;
}else{
t += dist;
if(t >= MAX_DIST){
rec.p = r.origin+t*r.dir;
rec.n = vec3(0, 0, 0);
return -1;
}
}
}
return -1;
}
vec3 calcColor(Ray r){
vec3 color;
Material emptyMat = Material(0, vec3(0));
Object emptyO = Object(0, vec3(0), 0, vec3(0), emptyMat);
HitRecord rec = HitRecord(vec3(0), vec3(0), emptyO, emptyMat, 0);
float t = findSceneIntersect(r, scene, rec);
int reflections = 0;
vec3 att = vec3(1, 1, 1);
for(int ref = 0; ref < MAX_REFLECTIONS; ref++){
if(t != -1){
vec3 tmpAtt = vec3(0);
if(scatter(r, rec, tmpAtt, r)){
att *= tmpAtt;
t = findSceneIntersect(r, scene, rec);
reflections++;
}else {
att *= tmpAtt;
t = -1;
}
}else {
color = backColor.xyz*att;
break;
}
}
return color;
}
void main(void){
HitRecord rec = HitRecord(vec3(0), vec3(0), Object(-1, vec3(0), 0, vec3(0), Material(-1, vec3(0))), Material(-1, vec3(1, 1, 1)), 0);
#if 1 //Reflection rendering
vec3 color = vec3(0);
for(int s = 0; s < MAX_AA; s++){
vec2 uv = uvPos+(vec2(randf(), randf())*2-1)/resolution;
color += calcColor(getRay(cam, uv));
}
outColor = vec4(color/MAX_AA, 1);
#else //Coloring based on normals
Ray r = getRay(cam, uvPos);
float t = findSceneIntersect(r, scene, rec);
if(t == -1){
outColor = backColor;
}else {
outColor = vec4(rec.n*0.5+0.5, 1);
}
#endif
}
Java code where I load and compile the shader (Using LWJGL):
private static int loadShader(String file, int type) {
System.out.println("Loading shader at path: " + file);
StringBuilder shaderSource = new StringBuilder();
try{
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while((line = reader.readLine())!=null){
shaderSource.append(line).append("//\n");
}
reader.close();
}catch(IOException e){
e.printStackTrace();
System.exit(-1);
}
int shaderID = glCreateShader(type);
glShaderSource(shaderID, shaderSource);
glCompileShader(shaderID);
if(glGetShaderi(shaderID, GL_COMPILE_STATUS )== GL_FALSE){
System.out.println(glGetShaderInfoLog(shaderID, 500));
System.err.println("Could not compile shader!");
System.exit(-1);
}
return shaderID;
}
The function loadShader in my code does not give me any error as it does with syntax errors and doesn't exit the program, thus GL_COMPILE_STATUS is not false.
I am fully aware nested for loops and my use of conditionals is far from efficient performance-wise, but I would expect it to be slow, not completely broken. I am running this on an Intel UHD 630, which according to https://www.intel.ca/content/www/ca/en/support/products/98909/graphics/graphics-for-7th-generation-intel-processors/intel-hd-graphics-630.html supports openGL 4.4. Therefore, according to GLSL maximum number of instructions, I should have access to 65536 instructions in my frag shader and fully dynamic branching. For these reasons, I don't think instruction limit is the problem and any help would be greatly appreciated. If you need any more information, I'll add it as soon as possible. If the CPU code is necessary, I can add it too, but I don't think it's the issue, as changing only the shader can trigger this bug.
Edit 1:
Both glGetShaderInfoLog and glGetProgramInfoLog return nothing when called after the program has been validated and linked.

Trying to port a GLSL glass shader to Processing 3.0

EDITED
I am beginner on Processing language and GLSL shaders. I am trying to port a fresnel+cubemap shader for a glass material. But as result my shape ever disappear, instead... :-(
My vertex shader is:
const float Air = 1.0;
const float Glass = 1.51714;
const float Eta = Air / Glass;
const float R0 = ((Air - Glass) * (Air - Glass)) / ((Air + Glass) * (Air + Glass));
uniform mat4 transform;
uniform mat4 modelview;
uniform mat3 normalMatrix;
attribute vec4 vertex;
attribute vec3 normal;
varying vec3 v_reflection;
varying vec3 v_refraction;
varying float v_fresnel;
void main(void){
vec4 t_vertex = modelview * vertex;
vec3 incident = normalize(vec3(t_vertex));
vec3 t_normal = normalMatrix * normal;
v_refraction = refract(incident, t_normal, Eta);
v_reflection = reflect(incident, t_normal);
v_fresnel = R0 + (1.0 - R0) * pow((1.0 - dot(-incident, t_normal)), 5.0);
gl_Position = transform * t_vertex;
}
And the fragment shader:
#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif
uniform samplerCube cubemap;
varying vec3 v_refraction;
varying vec3 v_reflection;
varying float v_fresnel;
void main(void){
vec4 refractionColor = textureCube(cubemap, normalize(v_refraction));
vec4 reflectionColor = textureCube(cubemap, normalize(v_reflection));
gl_FragColor = mix(refractionColor, reflectionColor, v_fresnel);
}
I am testing this shader with the Processing 3.0 sketch bellow (edited), on Android Mode:
PShader shader;
PShape sphere;
void setup() {
fullScreen(P3D);
noStroke();
shader = loadShader("glass.frag.glsl", "glass.vert.glsl");
openCubeMap("posx.png", "negx.png", "posy.png", "negy.png", "posz.png", "negz.png");
shader.set("cubemap", 1);
sphere = createShape(SPHERE, 120);
sphere.setFill(color(-1, 50));
}
void draw() {
background(0);
directionalLight(102, 102, 102, 0, 0, -1);
lightSpecular(204, 204, 204);
directionalLight(102, 102, 102, 0, 1, -1);
specular(100, 150, 150);
translate(width / 2, height / 2);
shader(shader);
shape(sphere);
}
void openCubeMap(String posX, String negX, String posY, String negY, String posZ, String negZ) {
PGL pgl = beginPGL();
// create the OpenGL-based cubeMap
IntBuffer envMapTextureID = IntBuffer.allocate(1);
pgl.genTextures(1, envMapTextureID);
pgl.activeTexture(PGL.TEXTURE1);
pgl.enable(PGL.TEXTURE_CUBE_MAP);
pgl.bindTexture(PGL.TEXTURE_CUBE_MAP, envMapTextureID.get(0));
pgl.texParameteri(PGL.TEXTURE_CUBE_MAP, PGL.TEXTURE_WRAP_S, PGL.CLAMP_TO_EDGE);
pgl.texParameteri(PGL.TEXTURE_CUBE_MAP, PGL.TEXTURE_WRAP_T, PGL.CLAMP_TO_EDGE);
pgl.texParameteri(PGL.TEXTURE_CUBE_MAP, PGL.TEXTURE_WRAP_R, PGL.CLAMP_TO_EDGE);
pgl.texParameteri(PGL.TEXTURE_CUBE_MAP, PGL.TEXTURE_MIN_FILTER, PGL.LINEAR);
pgl.texParameteri(PGL.TEXTURE_CUBE_MAP, PGL.TEXTURE_MAG_FILTER, PGL.LINEAR);
//Load in textures
String[] textureNames = { posX, negX, posY, negY, posZ, negZ };
for (int i=0; i<textureNames.length; i++) {
PImage texture = loadImage(textureNames[i]);
int w = texture.width;
int h = texture.height;
texture.loadPixels();
pgl.texImage2D(PGL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, PGL.RGBA, w, h, 0, PGL.RGBA, PGL.UNSIGNED_BYTE, IntBuffer.wrap(texture.pixels));
}
endPGL();
}
And I am using this images to build the cubemap.
Someone know how I can make this work?
Problem is not in your code but in your data.
OpenGL requires that all textures used by the cubemap have the same dimensions, and that the textures be square otherwise it will refuse to load it.
I checked your PNGs and this is not the case, they all have the same dimension but they are not square (255x230).
Also for Android it may be required that the texture dimensions be a power of 2 (128, 256, 512 ...)
So I tested resizing all the textures to 256x256 pixels and now your sample works :

How to smooth the painting of a custom QML element?

I try now to create custom QML element, derived from QQuickItem. So I overrided QQuickItem::updatePaintNode and want now do draw a line. My code:
QSGNode *StrikeLine::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
QSGGeometryNode *node = 0;
QSGGeometry *geometry;
QSGFlatColorMaterial *material;
node = static_cast<QSGGeometryNode *>(oldNode);
if(!node) {
node = new QSGGeometryNode;
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2);
geometry->setDrawingMode(GL_LINES);
geometry->setLineWidth(3);
material = new QSGFlatColorMaterial;
material->setColor(QColor(255, 0, 0));
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
getColor();
} else {
geometry = node->geometry();
material = static_cast<QSGFlatColorMaterial *>(node->material());
}
geometry->vertexDataAsPoint2D()[0].set(p_startPoint.x(), p_startPoint.y());
geometry->vertexDataAsPoint2D()[1].set(p_endPoint.x(), p_endPoint.y());
material->setColor(getColor());
node->markDirty(QSGNode::DirtyGeometry);
return node;
}
But my line looks so ugly. The edges are rough and it looks like DOS graphics at all. So my question - how can I apply smooth painting? I now it may be some shader or something but I cannot find any documentation.
The scene graph supports two types of antialiasing. Primitives such as rectangles and images will be antialiased by adding more vertices along the edge of the primitives so that the edges fade to transparent. This method called vertex antialiasing. If you requests a multisampled OpenGL context, the scene graph will prefer multisample based antialiasing (MSAA).
Vertex antialiasing can produce seams between edges of adjacent primitives, even when the two edges are mathmatically the same. Multisample antialiasing does not.
Multisample Antialiasing
Multisample antialiasing is a hardware feature where the hardware calculates a coverage value per pixel in the primitive. Some hardware can multisample at a very low cost, while other hardware may need both more memory and more GPU cycles to render a frame.
To enable multisample antialiasing you should set QSurfaceFormat with samples greater than 0 using QQuickWindow::setFormat()
QQuickView view;
QSurfaceFormat format = view.format();
format.setSamples(16);
view.setFormat(format);
view.show();
Vertex Antialiasing
Vertex antialiasing can be enabled and disabled on a per-item basis using the Item::antialiasing property. It will work regardless of what the underlying hardware supports and produces higher quality antialiasing, both for normally rendered primitives and also for primitives captured into framebuffer objects.
The downside to using vertex antialiasing is that each primitive with antialiasing enabled will have to be blended. In terms of batching, this means that the renderer needs to do more work to figure out if the primitive can be batched or not and due to overlaps with other elements in the scene, it may also result in less batching, which could impact performance.
To apply vertex antialiasing to custom QML element, derived from QQuickItem, follow next steps:
1) Create custom material and OpenGL shader program.
smoothcolormaterial.h
#include <QSGMaterial>
#include <QSGMaterialShader>
//----------------------------------------------------------------------
class QSGSmoothColorMaterial : public QSGMaterial
{
public:
QSGSmoothColorMaterial();
int compare(const QSGMaterial *other) const;
protected:
virtual QSGMaterialType *type() const;
virtual QSGMaterialShader *createShader() const;
};
//----------------------------------------------------------------------
class QSGSmoothColorMaterialShader : public QSGMaterialShader
{
public:
QSGSmoothColorMaterialShader();
virtual void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect);
virtual char const *const *attributeNames() const;
private:
void initialize();
int m_matrixLoc;
int m_opacityLoc;
int m_pixelSizeLoc;
};
smoothcolormaterial.cpp
QSGSmoothColorMaterial::QSGSmoothColorMaterial()
{
setFlag(RequiresFullMatrixExceptTranslate, true);
setFlag(Blending, true);
}
int QSGSmoothColorMaterial::compare(const QSGMaterial *other) const
{
Q_UNUSED(other)
return 0;
}
QSGMaterialType *QSGSmoothColorMaterial::type() const
{
static QSGMaterialType type;
return &type;
}
QSGMaterialShader *QSGSmoothColorMaterial::createShader() const
{
return new QSGSmoothColorMaterialShader();
}
//----------------------------------------------------------------------
QSGSmoothColorMaterialShader::QSGSmoothColorMaterialShader()
: QSGMaterialShader()
{
setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/shaders/smoothcolor.vert"));
setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/shaders/smoothcolor.frag"));
}
void QSGSmoothColorMaterialShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
{
Q_UNUSED(newEffect)
if (state.isOpacityDirty())
program()->setUniformValue(m_opacityLoc, state.opacity());
if (state.isMatrixDirty())
program()->setUniformValue(m_matrixLoc, state.combinedMatrix());
if (oldEffect == 0) {
// The viewport is constant, so set the pixel size uniform only once.
QRect r = state.viewportRect();
program()->setUniformValue(m_pixelSizeLoc, 2.0f / r.width(), 2.0f / r.height());
}
}
const char * const *QSGSmoothColorMaterialShader::attributeNames() const
{
static char const *const attributes[] = {
"vertex",
"vertexColor",
"vertexOffset",
0
};
return attributes;
}
void QSGSmoothColorMaterialShader::initialize()
{
m_matrixLoc = program()->uniformLocation("matrix");
m_opacityLoc = program()->uniformLocation("opacity");
m_pixelSizeLoc = program()->uniformLocation("pixelSize");
}
Fragment Shader
varying lowp vec4 color;
void main()
{
gl_FragColor = color;
}
Vertex Shader
uniform highp vec2 pixelSize;
uniform highp mat4 matrix;
uniform lowp float opacity;
attribute highp vec4 vertex;
attribute lowp vec4 vertexColor;
attribute highp vec2 vertexOffset;
varying lowp vec4 color;
void main()
{
highp vec4 pos = matrix * vertex;
gl_Position = pos;
if (vertexOffset.x != 0.) {
highp vec4 delta = matrix[0] * vertexOffset.x;
highp vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
highp vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
highp float numerator = dot(dir, ndir * pos.w * pos.w);
highp float scale = 0.0;
if (numerator < 0.0)
scale = 1.0;
else
scale = min(1.0, numerator / dot(dir, dir));
gl_Position += scale * delta;
}
if (vertexOffset.y != 0.) {
highp vec4 delta = matrix[1] * vertexOffset.y;
highp vec2 dir = delta.xy * pos.w - pos.xy * delta.w;
highp vec2 ndir = .5 * pixelSize * normalize(dir / pixelSize);
dir -= ndir * delta.w * pos.w;
highp float numerator = dot(dir, ndir * pos.w * pos.w);
highp float scale = 0.0;
if (numerator < 0.0)
scale = 1.0;
else
scale = min(1.0, numerator / dot(dir, dir));
gl_Position += scale * delta;
}
color = vertexColor * opacity;
}
2) Create custom AttributeSet for QSGGeometry.
myquickitem.cpp
namespace
{
struct Color4ub
{
unsigned char r, g, b, a;
};
inline Color4ub colorToColor4ub(const QColor &c)
{
Color4ub color = { uchar(c.redF() * c.alphaF() * 255),
uchar(c.greenF() * c.alphaF() * 255),
uchar(c.blueF() * c.alphaF() * 255),
uchar(c.alphaF() * 255)
};
return color;
}
struct SmoothVertex
{
float x, y;
Color4ub color;
float dx, dy;
void set(float nx, float ny, Color4ub ncolor, float ndx, float ndy)
{
x = nx; y = ny; color = ncolor;
dx = ndx; dy = ndy;
}
};
const QSGGeometry::AttributeSet &smoothAttributeSet()
{
static QSGGeometry::Attribute data[] = {
QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true),
QSGGeometry::Attribute::create(1, 4, GL_UNSIGNED_BYTE, false),
QSGGeometry::Attribute::create(2, 2, GL_FLOAT, false)
};
static QSGGeometry::AttributeSet attrs = { 3, sizeof(SmoothVertex), data };
return attrs;
}
}
3) Apply custom material and custom geometry to QSGGeometryNode.
myquickitem.cpp
QSGNode *MyQuickItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data)
{
QSGGeometryNode *node = 0;
QSGGeometry *geometry;
QSGSmoothColorMaterial *material;
node = static_cast<QSGGeometryNode *>(oldNode);
if(!node) {
node = new QSGGeometryNode;
geometry = new QSGGeometry(smoothAttributeSet(), 0);
geometry->setDrawingMode(GL_TRIANGLE_STRIP);
material = new QSGSmoothColorMaterial();
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
} else {
geometry = node->geometry();
material = static_cast<QSGSmoothColorMaterial *>(node->material());
}
4) Get pointer to vertex data.
int vertexStride = geometry->sizeOfVertex();
int vertexCount = 8;
geometry->allocate(vertexCount, 0);
SmoothVertex *smoothVertices = reinterpret_cast<SmoothVertex *>(geometry->vertexData());
memset(smoothVertices, 0, vertexCount * vertexStride);
5) Set vertex data.
You need 4 points.
float lineWidth = 4;
float tlX = 0; float tlY = 0; //top-left
float blX = 0; float blY = 0 + lineWidth; //bottom-left
float trX = 500; float trY = 100; //top-right
float brX = 500; float brY = 100 + lineWidth; //bottom-right
float delta = lineWidth * 0.5f;
Color4ub fillColor = colorToColor4ub(QColor(255,0,0,255));
Color4ub transparent = { 0, 0, 0, 0 };
To draw antialiased line you should set 8 vertices to draw 6 triangles(2 for line, 4 for antialiasing). Vertices 0 and 2, 1 and 3, 4 and 6, 5 and 7 have the same coordinates, but different color and opposite vertex offset.
smoothVertices[0].set(trX, trY, transparent, delta, -delta);
smoothVertices[1].set(tlX, tlY, transparent, -delta, -delta);
smoothVertices[2].set(trX, trY, fillColor, -delta, delta);
smoothVertices[3].set(tlX, tlY, fillColor, delta, delta);
smoothVertices[4].set(brX, brY, fillColor, -delta, -delta);
smoothVertices[5].set(blX, blY, fillColor, delta, -delta);
smoothVertices[6].set(brX, brY, transparent, delta, delta);
smoothVertices[7].set(blX, blY, transparent, -delta, delta);
node->markDirty(QSGNode::DirtyGeometry);
return node;
}

Using OpenGL Toon Shader in GLSL

I'm interested in learning how to write toon shaders in OpenGL Shading Language. I found a demo, but haven't been able to get the demo running on my computer. The trouble I'm having is with writing an application which will use this shader. Could somebody please show me how to write a simple application which would use this shader? I'm using GLSL 1.2 (OpenGL 2.1) on Linux.
Here is the main sketch:
/*
* Use keys 1 - 8 to play with different GLUT Solids
* mouse affects light position
* Toon Shader by Philip Rideout:
* http://www.lighthouse3d.com/opengl/glsl/index.php?toon2
*/
import processing.opengl.*;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.util.*;
PGraphicsOpenGL pgl;
GL gl;
GLSL toon;
GLU glu;
GLUT glut;
boolean glInit;
int glutSolidIndex = 7;
void setup()
{
size(600, 500, OPENGL);
glu = new GLU();
glut = new GLUT();
pgl = (PGraphicsOpenGL) g;
gl = pgl.gl;
}
void draw()
{
background(0);
PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
GL gl = pgl.beginGL();
if(!glInit){
toon=new GLSL();
toon.loadVertexShader("toon.vs");
toon.loadFragmentShader("toon.fs");
toon.useShaders();
glInit = true;
}
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
//TRS
gl.glTranslatef(width * .5, height * .5,0.0f);
gl.glRotatef(160,1,0,0);
gl.glRotatef(frameCount * .5,0,1,0);
gl.glRotatef(frameCount * .5,0,0,1);
gl.glScalef(80,80,80);
// draw
toon.startShader();
toon.uniform3f(toon.getUniformLocation("LightPosition"), mouseX-width*.5, -(mouseY-height*.5), 20.0f);
gl.glColor3f(1.0f, 0.5f, 0.0f);
glutSolid();
toon.endShader();
pgl.endGL();
}
void glutSolid(){
switch(glutSolidIndex){
case 0:
glut.glutSolidCube(1);
break;
case 1:
glut.glutSolidTetrahedron();
break;
case 2:
glut.glutSolidOctahedron();
break;
case 3:
glut.glutSolidDodecahedron();
break;
case 4:
glut.glutSolidIcosahedron();
break;
case 5:
glut.glutSolidSphere(1,16,8);
break;
case 6:
glut.glutSolidTorus(.5,1,32,24);
break;
case 7:
glut.glutSolidTeapot(1);
break;
}
}
void keyPressed(){
if((int)key >= 49 && (int)key <= 56) glutSolidIndex = (int)(key) - 49;
}
Here is the GLSL class used:
/*
* Class posted by JohnG on the Processing forums:
* http://processing.org/discourse/yabb2/YaBB.pl?board=OpenGL;action=display;num=1159494801
* check it out for more details
*/
import processing.opengl.*;
import javax.media.opengl.*;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;
import com.sun.opengl.util.BufferUtil;
class GLSL
{
int programObject;
GL gl;
boolean vertexShaderEnabled;
boolean vertexShaderSupported;
int vs;
int fs;
GLSL()
{
PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
gl = pgl.gl;
//gl=((PGraphicsGL)g).gl;
String extensions = gl.glGetString(GL.GL_EXTENSIONS);
vertexShaderSupported = extensions.indexOf("GL_ARB_vertex_shader") != -1;
vertexShaderEnabled = true;
programObject = gl.glCreateProgramObjectARB();
vs=-1;
fs=-1;
}
void loadVertexShader(String file)
{
String shaderSource=join(loadStrings(file),"\n");
vs = gl.glCreateShaderObjectARB(GL.GL_VERTEX_SHADER_ARB);
gl.glShaderSourceARB(vs, 1, new String[]{
shaderSource }
,(int[]) null, 0);
gl.glCompileShaderARB(vs);
checkLogInfo(gl, vs);
gl.glAttachObjectARB(programObject, vs);
}
void loadFragmentShader(String file)
{
String shaderSource=join(loadStrings(file),"\n");
fs = gl.glCreateShaderObjectARB(GL.GL_FRAGMENT_SHADER_ARB);
gl.glShaderSourceARB(fs, 1, new String[]{
shaderSource }
,(int[]) null, 0);
gl.glCompileShaderARB(fs);
checkLogInfo(gl, fs);
gl.glAttachObjectARB(programObject, fs);
}
int getAttribLocation(String name)
{
return(gl.glGetAttribLocationARB(programObject,name));
}
int getUniformLocation(String name)
{
return(gl.glGetUniformLocationARB(programObject,name));
}
void useShaders()
{
gl.glLinkProgramARB(programObject);
gl.glValidateProgramARB(programObject);
checkLogInfo(gl, programObject);
}
void startShader()
{
gl.glUseProgramObjectARB(programObject);
}
void endShader()
{
gl.glUseProgramObjectARB(0);
}
void checkLogInfo(GL gl, int obj)
{
IntBuffer iVal = BufferUtil.newIntBuffer(1);
gl.glGetObjectParameterivARB(obj, GL.GL_OBJECT_INFO_LOG_LENGTH_ARB, iVal);
int length = iVal.get();
if (length <= 1)
{
return;
}
ByteBuffer infoLog = BufferUtil.newByteBuffer(length);
iVal.flip();
gl.glGetInfoLogARB(obj, length, iVal, infoLog);
byte[] infoBytes = new byte[length];
infoLog.get(infoBytes);
println("GLSL Validation >> " + new String(infoBytes));
}
void uniform3f(int location, float v0, float v1, float v2)
{
gl.glUniform3fARB(location, v0, v1, v2);
}
void uniform1i(int location, int v0)
{
gl.glUniform1iARB(location, v0);
}
}
And the GLSL code,
the vertex shader: toon.vs
//
// Vertex shader for cartoon-style shading
//
// Author: Philip Rideout
//
// Copyright (c) 2005-2006 3Dlabs Inc. Ltd.
//
// See 3Dlabs-License.txt for license information
//
varying vec3 Normal;
void main(void)
{
Normal = normalize(gl_NormalMatrix * gl_Normal);
#ifdef __GLSL_CG_DATA_TYPES // Fix clipping for Nvidia and ATI
gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;
#endif
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
And the fragment shader: toon.fs
/* http://www.lighthouse3d.com/opengl/glsl/index.php?toon2 */
varying vec3 Normal;
uniform vec3 LightPosition;// = vec3(10.0, 10.0, 20.0);
void main()
{
vec4 color1 = gl_FrontMaterial.diffuse;
vec4 color2;
float intensity = dot(normalize(LightPosition),Normal);
if (intensity > 0.95) color2 = vec4(1.0, 1.0, 1.0, 1.0);
else if (intensity > 0.75) color2 = vec4(0.8, 0.8, 0.8, 1.0);
else if (intensity > 0.50) color2 = vec4(0.6, 0.6, 0.6, 1.0);
else if (intensity > 0.25) color2 = vec4(0.4, 0.4, 0.4, 1.0);
else color2 = vec4(0.2, 0.2, 0.2, 1.0);
gl_FragColor = color1 * color2;
}
If it helps, here is the zipped Processing project. Once you've installed Processing, unzip the file into the default Processing folder(~/Documents/Processing) and run Processing > it should show under File > Sketchbook
And here's a screenshot:
HTH
Update
Processing now provides a nice PShader class and comprehensive tutorial.
It incluses a Toon shader:
PShader toon;
void setup() {
size(640, 360, P3D);
noStroke();
fill(204);
toon = loadShader("ToonFrag.glsl", "ToonVert.glsl");
toon.set("fraction", 1.0);
}
void draw() {
shader(toon);
background(0);
float dirY = (mouseY / float(height) - 0.5) * 2;
float dirX = (mouseX / float(width) - 0.5) * 2;
directionalLight(204, 204, 204, -dirX, -dirY, -1);
translate(width/2, height/2);
sphere(120);
}
ToonVert.glsl:
uniform mat4 transform;
uniform mat3 normalMatrix;
uniform vec3 lightNormal;
attribute vec4 vertex;
attribute vec4 color;
attribute vec3 normal;
varying vec4 vertColor;
varying vec3 vertNormal;
varying vec3 vertLightDir;
void main() {
gl_Position = transform * vertex;
vertColor = color;
vertNormal = normalize(normalMatrix * normal);
vertLightDir = -lightNormal;
}
ToonFrag.glsl:
#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif
#define PROCESSING_LIGHT_SHADER
uniform float fraction;
varying vec4 vertColor;
varying vec3 vertNormal;
varying vec3 vertLightDir;
void main() {
float intensity;
vec4 color;
intensity = max(0.0, dot(vertLightDir, vertNormal));
if (intensity > pow(0.95, fraction)) {
color = vec4(vec3(1.0), 1.0);
} else if (intensity > pow(0.5, fraction)) {
color = vec4(vec3(0.6), 1.0);
} else if (intensity > pow(0.25, fraction)) {
color = vec4(vec3(0.4), 1.0);
} else {
color = vec4(vec3(0.2), 1.0);
}
gl_FragColor = color * vertColor;
}