Rotating a shader

Started by _gjkf_, August 16, 2016, 15:51:16

Previous topic - Next topic

_gjkf_

Hello everyone, I'm struggling doing a simple action as rotating a shader.

This is what I currently have:
public static void drawImageRegion(Image image, float x1, float y1, float x2, float y2, float s1, float t1, float s2, float t2, Color3f color, int angle){
        glPushMatrix();

        long window = GLFW.glfwGetCurrentContext();
        IntBuffer widthBuffer = BufferUtils.createIntBuffer(1);
        IntBuffer heightBuffer = BufferUtils.createIntBuffer(1);
        GLFW.glfwGetFramebufferSize(window, widthBuffer, heightBuffer);
        int w = widthBuffer.get();
        int h = heightBuffer.get();

        glBindTexture(GL_TEXTURE_2D, image.getID());

        int vao = glGenVertexArrays();
        glBindVertexArray(vao);

        float r = color.r;
        float g = color.g;
        float b = color.b;

        FloatBuffer vertices = BufferUtils.createFloatBuffer(4 * 7);
        vertices.put(x1).put(y1).put(r).put(g).put(b).put(s1).put(t1);
        vertices.put(x2).put(y1).put(r).put(g).put(b).put(s2).put(s1);
        vertices.put(x2).put(y2).put(r).put(g).put(b).put(s2).put(t2);
        vertices.put(x1).put(y2).put(r).put(g).put(b).put(s1).put(t2);
        vertices.flip();

        int vbo = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);

        int ebo = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

        IntBuffer elements = BufferUtils.createIntBuffer(2 * 3);
        elements.put(0).put(1).put(2);
        elements.put(2).put(3).put(0);
        elements.flip();

        glBufferData(GL_ELEMENT_ARRAY_BUFFER, elements, GL_STATIC_DRAW);

        ShaderProgram program = new ShaderProgram();
        Shader v = Shader.loadShader(GL_VERTEX_SHADER, "shaders/texVertex.glsl");
        program.attachShader(v);
        Shader f = Shader.loadShader(GL_FRAGMENT_SHADER, "shaders/texFrag.glsl");
        program.attachShader(f);
        program.bindFragmentDataLocation(0, "fragColor");
        program.link();
        program.use();

        program = specifyVertexAttributes(program);

        // Set texture uniform
        int uniTex = program.getUniformLocation("texImage");
        program.setUniform(uniTex, 0);

        // Set model matrix to identity matrix
        Matrix4f model = Matrix4f.rotate(angle, 0, 0, 1);
        int uniModel = program.getUniformLocation("model");
        program.setUniform(uniModel, model);

        // Set view matrix to identity matrix 
        Matrix4f view = new Matrix4f();
        int uniView = program.getUniformLocation("view");
        program.setUniform(uniView, view);

        // Set projection matrix to an orthographic projection
        Matrix4f projection = Matrix4f.orthographic(0f, w, h, 0f, -1f, 1f);
        int uniProjection = program.getUniformLocation("projection");
        program.setUniform(uniProjection, projection);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glDeleteTextures(image.getID());
        v.delete();
        f.delete();
        program.delete();
        glUseProgram(0);

        glPopMatrix();
    }
/**
   * Specifies the vertex pointers.
   */
 private static ShaderProgram specifyVertexAttributes(ShaderProgram program) {
        /* Specify Vertex Pointer */
        int posAttrib = program.getAttributeLocation("position");
        program.enableVertexAttribute(posAttrib);
        program.pointVertexAttribute(posAttrib, 2, 7 * Float.BYTES, 0);

        /* Specify Color3f Pointer */
        int colAttrib = program.getAttributeLocation("color");
        program.enableVertexAttribute(colAttrib);
        program.pointVertexAttribute(colAttrib, 3, 7 * Float.BYTES, 2 * Float.BYTES);

        /* Specify Texture Pointer */
        int texAttrib = program.getAttributeLocation("texcoord");
        program.enableVertexAttribute(texAttrib);
        program.pointVertexAttribute(texAttrib, 2, 7 * Float.BYTES, 5 * Float.BYTES);

        return program;
 }


And these are my shaders:
Vertex:
#version 150 core

in vec2 position;
in vec3 color;
in vec2 texcoord;

out vec3 vertexColor;
out vec2 textureCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
    vertexColor = color;
    textureCoord = texcoord;
    mat4 mvp = projection * view * model;
    gl_Position = mvp * vec4(position, 0.0, 1.0);
}


Fragment:
#version 150 core

in vec3 vertexColor;
in vec2 textureCoord;

out vec4 fragColor;

uniform sampler2D texImage;

void main() {
    vec4 textureColor = texture(texImage, textureCoord);
    fragColor = vec4(vertexColor, 1.0) * textureColor;
}


Doing some research I have found that I should use the model matrix to perform rotation with shaders. In fact line 56 works but not how intended. I have a small 32x32 image at coordinates (500; 500) and I rotate it using the method shown. Here's the relevant code:
image = Image.loadImage("textures/lwjgl32.png");
Renderer.drawImageRegion(image, 500, 500, 0, 0, 32, 32, Colors.WHITE.color, angle++);


The problem is that the texture rotates with center (0; 0) (top-left corner) instead of rotating around itself. I have tried translating the model matrix to the X and Y coordinate of the texture but seems to just move the origin down to (500; 500) which is the location of the texture.

I might be missing something obvious but it has been a while and I still not have found a way around this. Please excuse me for my noobiness and if you can explain in detail how and why the solution works.

Thanks a bunch.

quew8

Your basic rotation matrix will always rotate around the origin. Solution: translate your object to the origin, rotate it then translate it back to where it is supposed to be.

I'm not familiar with JOML syntax (so much so that you might not event be using JOML) but essentially what you want is to change line 57 in the first snippet to something like:

Matrix4f model = Matrix4f.translate(-(x1 + x2) / 2, -(y1 + y2) / 2, 0);
model.rotate(angle, 0, 0, 1);
model.translate((x1 + x2) / 2, (y1 + y2) / 2, 0);


That would rotate about the centre of the quad.

(Just a note. I wouldn't be specifying angle as an integer. If JOML uses radians as I would assume then a full revolution is only about 6.28. You'll have all sorts of problems trying to do angles as integers.)

_gjkf_

Thanks for the answer.
I'm not using JOML, I'm using SliverTiger's class (I followed his tutuorials on Github and snagged that class).The angle is passed in degrees, then it's changed in radians.

The snippet now looks:

/* Set model matrix to identity matrix */
Matrix4f model = Matrix4f.translateStatic(-(x1 + x2) / 2, -(y1 + y2) / 2, 0);
model.rotate(angle, 0, 0, 1);
model.translate((x1 + x2) / 2, (y1 + y2) / 2, 0);
int uniModel = program.getUniformLocation("model");
program.setUniform(uniModel, model);


And the Matrixx4f methods are:
public static Matrix4f translateStatic(float x, float y, float z) {
        Matrix4f translation = new Matrix4f();

        translation.m03 = x;
        translation.m13 = y;
        translation.m23 = z;

        return translation;
    }

public void translate(float x, float y, float z) {
        m03 = x;
        m13 = y;
        m23 = z;
    }

public void rotate(float angle, float x, float y, float z) {
        float c = (float) Math.cos(Math.toRadians(angle));
        float s = (float) Math.sin(Math.toRadians(angle));
        Vector3f vec = new Vector3f(x, y, z);
        if (vec.length() != 1f) {
            vec = vec.normalize();
            x = vec.x;
            y = vec.y;
            z = vec.z;
        }

        m00 = x * x * (1f - c) + c;
        m10 = y * x * (1f - c) + z * s;
        m20 = x * z * (1f - c) - y * s;
        m01 = x * y * (1f - c) - z * s;
        m11 = y * y * (1f - c) + c;
        m21 = y * z * (1f - c) + x * s;
        m02 = x * z * (1f - c) + y * s;
        m12 = y * z * (1f - c) - x * s;
        m22 = z * z * (1f - c) + c;

    }


Once I run it I see nothing. Just an empty screen. Even if the coordinates are (500;500) there's no image on the screen. Looks like it is outside the window size.

Should I use JOML? I didn't because I needed a method in Matrices and Vectors that is not by default in: making out of the object a buffer.

Thanks again for your help.

quew8

Ah okay. So those methods aren't actually combining the transformations, they're just sort of setting the matrix to that transform. That means you're getting a dodgy matrix which is creating meaningless vertex data hence black screen.

Solution. I guess create each matrix separately and multiply them together yourself. A proper maths library would normally provide methods to do this more efficiently than that and maybe SilverTiger's one does. Don't know. So basic code.
Matrix4f m1 = Matrix4f.translateStatic(-(x1 + x2) / 2, -(y1 + y2) / 2, 0);
Matrix4f m2 = Matrix4f.rotate(angle, 0, 0, 1);
Matrix4f m3 = Matrix4f.translateStatic((x1 + x2) / 2, (y1 + y2) / 2, 0);
Matrix4f model = m1.times(m2).times(m3);

Will probably look something like that. You might need to mess around with the order of multiplication a bit depending on if it's premultiplying or postmultiplying but don't worry about that (I never do). Just mess till it works.

As for JOML, I don't use it but I kind of plan on moving to it. Initially I rolled my own but frankly JOML has more features, will make things easier in the long run and more efficient to boot. I think JOML is pretty much the unofficial official maths library with LWJGL so it's a good idea.

EDIT: Obligatory link to my favourite vector maths tutorial for games. http://www.wildbunny.co.uk/blog/vector-maths-a-primer-for-games-programmers/. Very very useful if you want to get into graphics.

_gjkf_

Thanks for the link, will definitely read it all!

Thanks a bunch! Now works. The right order I found is:
Matrix4f m1 = Matrix4f.translateStatic(-(x1 + x2) / 2, -(y1 + y2) / 2, 0);
Matrix4f m2 = Matrix4f.rotateStatic(angle, 0, 0, 1);
Matrix4f m3 = Matrix4f.translateStatic((x1 + x2) / 2, (y1 + y2) / 2, 0);
Matrix4f model = m3.multiply(m2).multiply(m1);


I'll definitely look into a better way of doing so and if I find it I'll share it here for anybody to see. Once again, thanks for your time!

SilverTiger

The math classes provided in my tutorial are just merely the basics.
@quew8 is right; the methods create new matrices and you need to multiply them to get the desired transformation (make sure to create the transformation in reverse order!).

If you want to combine the matrix without multiplying, your instance method should look something like this:
public void translate(float x, float y, float z) {
    this.m03 += x;
    this.m13 += y;
    this.m23 += z;
}


But as my math classes are just the basics that were needed for my tutorial I would recommend everyone to use a more fleshed out math library like JOML ;)

Kai

Just for the record, here is what it would look like using JOML:
float tx = (x1 + x2) / 2;
float ty = (y1 + y2) / 2;
Matrix4f m = new Matrix4f()
  .translate(tx, ty, 0)
  .rotateZ(angle) // <- angle is in radians!
  .translate(-tx, -ty, 0);


Quote from: _gjkf_ on August 18, 2016, 10:40:49
Should I use JOML? I didn't because I needed a method in Matrices and Vectors that is not by default in: making out of the object a buffer.
See Matrix4f.get(ByteBuffer) and Matrix4f.get(FloatBuffer).
Also, please read this section of JOML's README.