Bad Performance with VBO and shaders - opengl

I try to render 2048 quads in modern OpenGL with VBOs and shaders. But the performance is horrible. I've just about 50 FPS by using the following code. I think, I call something every frame, what slows down the application, but I don't know what it is.
Also I think, that it is very slow to send the position, view and projection matrix every frame to the shader, if only the objects position changed.
What can I do to get this code more faster?
Model Code:
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.Arrays;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
public abstract class Model
{
private TexturedVertex[] vertices;
private byte[] indices;
private FloatBuffer verticesBuffer;
private ByteBuffer indicesBuffer;
private Vector3f position;
private Vector3f angle;
private Vector3f scale;
int shaderProgram;
int indicesCount;
private int projectionMatrixLocation;
private int viewMatrixLocation;
private int modelMatrixLocation;
int vaoID;
int vboID;
int vboiID;
public void load (TexturedVertex[] vertices, byte[] indices, Shader shader)
{
this.position = new Vector3f(0, 0, -1);
this.angle = new Vector3f(0, 0, 0);
this.scale = new Vector3f(1, 1, 1);
this.vertices = vertices;
this.indices = indices;
this.shaderProgram = shader.getProgramID();
this.generateBuffer();
this.generateArrayBufferObjects();
this.configureShader();
}
private void generateBuffer()
{
// === Vertices Buffer === //
this.verticesBuffer = BufferUtils.createFloatBuffer(vertices.length * TexturedVertex.elementCount);
for (int i = 0; i < vertices.length; i++)
{
verticesBuffer.put(vertices[i].getElements());
System.out.print ("XYZW: " + Arrays.toString(vertices[i].getXYZW()));
System.out.print (" RGBA: " + Arrays.toString(vertices[i].getRGBA()));
System.out.println (" ST: " + Arrays.toString(vertices[i].getST()));
}
verticesBuffer.flip();
// === Generate Indices Buffer === //
this.indicesCount = indices.length;
this.indicesBuffer = BufferUtils.createByteBuffer(indicesCount);
indicesBuffer.put(indices);
indicesBuffer.flip();
}
private void generateArrayBufferObjects()
{
// === Generate VAO & VBO === //
this.vaoID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoID);
this.vboID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
this.vboiID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiID);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
System.out.println ();
System.out.println ("VAO-ID: #" + vaoID);
System.out.println ("VBO-ID: #" + vboID);
System.out.println ("VBOI-ID: #" + vboiID);
// === Put informations to shader === //
GL20.glVertexAttribPointer(0, TexturedVertex.positionElementCount, GL11.GL_FLOAT, false, TexturedVertex.stride, TexturedVertex.positionByteOffset);
GL20.glVertexAttribPointer(1, TexturedVertex.colorElementCount, GL11.GL_FLOAT, false, TexturedVertex.stride, TexturedVertex.colorByteOffset);
GL20.glVertexAttribPointer(2, TexturedVertex.textureElementCount, GL11.GL_FLOAT, false, TexturedVertex.stride, TexturedVertex.textureByteOffset);
// === Unbind Buffers === //
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
}
private void configureShader()
{
// === Bind shader === //
GL20.glUseProgram(shaderProgram);
// === Get matrix location === //
this.projectionMatrixLocation = GL20.glGetUniformLocation(shaderProgram, "projectionMatrix");
this.viewMatrixLocation = GL20.glGetUniformLocation(shaderProgram, "viewMatrix");
this.modelMatrixLocation = GL20.glGetUniformLocation(shaderProgram, "modelMatrix");
// === Update the matrix === //
this.updateMatrix();
// === Unbind shader === //
GL20.glUseProgram(0);
}
void updateMatrix()
{
// === Bind shader === //
GL20.glUseProgram(shaderProgram);
// === Load matrix === //
Matrix4f projectionMatrix = GameManager.camera.getProjectionMatrix();
Matrix4f viewMatrix = GameManager.camera.getViewMatrix();
Matrix4f modelMatrix = new Matrix4f();
// === Scale, translate and rotate matrix === //
Matrix4f.scale(scale, modelMatrix, modelMatrix);
Matrix4f.translate(position, modelMatrix, modelMatrix);
Matrix4f.rotate(Util.degreesToRadians(angle.z), new Vector3f(0, 0, 1), modelMatrix, modelMatrix);
Matrix4f.rotate(Util.degreesToRadians(angle.y), new Vector3f(0, 1, 0), modelMatrix, modelMatrix);
Matrix4f.rotate(Util.degreesToRadians(angle.x), new Vector3f(1, 0, 0), modelMatrix, modelMatrix);
// === Apply uniform matrix to shader === //
FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16);
projectionMatrix.store(matrixBuffer);
matrixBuffer.flip();
GL20.glUniformMatrix4(projectionMatrixLocation, false, matrixBuffer);
viewMatrix.store(matrixBuffer);
matrixBuffer.flip();
GL20.glUniformMatrix4(viewMatrixLocation, false, matrixBuffer);
modelMatrix.store(matrixBuffer);
matrixBuffer.flip();
GL20.glUniformMatrix4(modelMatrixLocation, false, matrixBuffer);
// === Unbind shader === //
GL20.glUseProgram(0);
}
public void setPosition (Vector3f newPosition)
{
this.position = newPosition;
this.updateMatrix();
}
public void setAngle (Vector3f newAngle)
{
this.angle = newAngle;
this.updateMatrix();
}
public void setScale (Vector3f newAngle)
{
this.scale = newAngle;
this.updateMatrix();
}
}
Render Code:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.vector.Vector3f;
import de.matthiasmann.twl.utils.PNGDecoder;
import de.matthiasmann.twl.utils.PNGDecoder.Format;
public class GameManager
{
private Logger logger;
private Profiler profiler;
private Window window;
public static Camera camera;
public GameManager()
{
init();
ModelChest[] chests = new ModelChest[2048];
for (int i = 0; i < 2048; i++)
{
chests[i] = new ModelChest();
}
ModelChest chest = new ModelChest();
ModelChest chestB = new ModelChest();
int textureID = loadPNGTexture ("Getigerte-Katze-Baby-Decke.png", GL13.GL_TEXTURE0);
while (!window.isCloseRequested())
{
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
{
chest.setPosition(new Vector3f(1, 0, -0.5F));
GL20.glUseProgram(chest.shaderProgram);
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
GL30.glBindVertexArray(chest.vaoID);
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, chest.vboiID);
{
GL11.glDrawElements(GL11.GL_TRIANGLES, chest.indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
}
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL30.glBindVertexArray(0);
//GL20.glUseProgram(0);
for (float i = 0; i < 2048; i++)
{
chests[(int)i].setPosition(new Vector3f(-1F + ((float)i / 30F), 0, -0.5F));
GL20.glUseProgram(chests[(int)i].shaderProgram);
GL30.glBindVertexArray(chests[(int)i].vaoID);
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, chests[(int)i].vboiID);
{
GL11.glDrawElements(GL11.GL_TRIANGLE_STRIP , chests[(int)i].indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
}
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL30.glBindVertexArray(0);
GL20.glUseProgram(0);
}
}
Display.update();
}
destroy();
}
public void init()
{
logger = new Logger();
logger.init();
profiler = new Profiler();
profiler.init();
window = new Window();
window.init();
camera = new Camera();
camera.init();
}
public void destroy()
{
logger.destroy();
profiler.destroy();
window.destroy();
camera.destroy();
System.exit(0);
}
private int loadPNGTexture(String filename, int textureUnit) {
ByteBuffer buf = null;
int tWidth = 0;
int tHeight = 0;
try {
// Open the PNG file as an InputStream
InputStream in = new FileInputStream(filename);
// Link the PNG decoder to this stream
PNGDecoder decoder = new PNGDecoder(in);
// Get the width and height of the texture
tWidth = decoder.getWidth();
tHeight = decoder.getHeight();
// Decode the PNG file in a ByteBuffer
buf = ByteBuffer.allocateDirect(
4 * decoder.getWidth() * decoder.getHeight());
decoder.decode(buf, decoder.getWidth() * 4, Format.RGBA);
buf.flip();
in.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
// Create a new texture object in memory and bind it
int texId = GL11.glGenTextures();
GL13.glActiveTexture(textureUnit);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
// All RGB bytes are aligned to each other and each component is 1 byte
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
// Upload the texture data and generate mip maps (for scaling)
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, tWidth, tHeight, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
// Setup the ST coordinate system
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
// Setup what to do when the texture has to be scaled
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR_MIPMAP_LINEAR);
return texId;
}
}
Camera:
import org.lwjgl.opengl.Display;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
public class Camera extends Worker
{
private Matrix4f projectionMatrix;
private Matrix4f viewMatrix;
private Vector3f position;
private Vector3f angle;
private float fieldOfView;
private float aspectRatio;
private float nearPlane;
private float farPlane;
private float xScale;
private float yScale;
private float frustumLength;
#Override
protected void onInitialize()
{
// Apply default settings
this.fieldOfView = 60f;
this.aspectRatio = (float) Display.getWidth() / (float) Display.getHeight();
this.nearPlane = 0.1f;
this.farPlane = 100f;
this.position = new Vector3f(0, 0, -1);
this.angle = new Vector3f(0, 0, 0);
// Calculate scale and furstum length
this.yScale = Util.coTangent(Util.degreesToRadians(fieldOfView / 2f));
this.xScale = yScale / aspectRatio;
this.frustumLength = farPlane - nearPlane;
// Projection Matrix
projectionMatrix = new Matrix4f();
projectionMatrix.m00 = xScale;
projectionMatrix.m11 = yScale;
projectionMatrix.m22 = -((farPlane + nearPlane) / frustumLength);
projectionMatrix.m23 = -1;
projectionMatrix.m32 = -((2 * nearPlane * farPlane) / frustumLength);
projectionMatrix.m33 = 0;
// View Matrix
viewMatrix = new Matrix4f();
Matrix4f.translate(position, viewMatrix, viewMatrix);
Matrix4f.rotate(Util.degreesToRadians(angle.z), new Vector3f(0, 0, 1),
viewMatrix, viewMatrix);
Matrix4f.rotate(Util.degreesToRadians(angle.y), new Vector3f(0, 1, 0),
viewMatrix, viewMatrix);
Matrix4f.rotate(Util.degreesToRadians(angle.x), new Vector3f(1, 0, 0),
viewMatrix, viewMatrix);
}
public Matrix4f getProjectionMatrix()
{
return this.projectionMatrix;
}
public Matrix4f getViewMatrix()
{
return this.viewMatrix;
}
public void setPosition (Vector3f newPosition)
{
Matrix4f.translate(newPosition, viewMatrix, viewMatrix);
}
public void setPosition (float x, float y, float z)
{
setPosition(new Vector3f (x, y, z));
}
public void setAngle (Vector3f newAngle)
{
Matrix4f.rotate(Util.degreesToRadians(newAngle.z), new Vector3f(0, 0, 1),
viewMatrix, viewMatrix);
Matrix4f.rotate(Util.degreesToRadians(newAngle.y), new Vector3f(0, 1, 0),
viewMatrix, viewMatrix);
Matrix4f.rotate(Util.degreesToRadians(newAngle.x), new Vector3f(1, 0, 0),
viewMatrix, viewMatrix);
}
public void setAngle (float x, float y, float z)
{
setAngle(new Vector3f (x, y, z));
}
#Override
protected void onDestroy()
{
;
}
#Override
protected void onTick()
{
;
}
}
Shader:
#version 150 core
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
in vec4 in_Position;
in vec4 in_Color;
in vec2 in_TextureCoord;
out vec4 pass_Color;
out vec2 pass_TextureCoord;
void main(void) {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * in_Position;
pass_Color = in_Color;
pass_TextureCoord = in_TextureCoord;
}

Related

Using bullet physics car and heightfield, car get stuck how can I fix?

I've been working on implementing a car in my game using bullet physics. The physics of the car uses btRaycastVehicle and the code is mostly based on the ForkLift Demo.
At this point, the vehicle seems to work properly on a flat ground but then I've started working on a non flat terrain and I saw the class btHeightfieldTerrainShape which take an array of heights to construct a shape.
So I've managed to use this class and the vehicle can be used on it but it sometimes get stuck on terrain's hollows even if the height to climb is really small.
I'm using MLCP constraint solver and tested PGS solver but does not help.
Here is the concerned code :
Vehicle.hpp
#define USE_MLCP_SOLVER
// I removed other #define because all were just floats
class Vehicle {
public:
// theses bools are set to true when the corresponding key is pressed
bool m_foward = false, m_backward = false, m_leftward = false, m_rightward = false;
bool m_reset = false;
Vehicle(Vao *chassisVao, Vao *wheelVao, const float *heights, uint32_t gridsize, float amplitude);
~Vehicle();
// this function runs the logic of the physics simulation, it gets executed each frame
void stepSimulation(uint32_t frameTime);
// this function instantiate objects to rendered, it gets executed each frame
// not including definition, not revalent
void prepareRendering(std::vector<const Entity *> &entities);
private:
// members are declared here ---> <---
// create physics world and vehicle
void initPhysics(const float *heights, uint32_t gridsize, float amplitude);
// cleanup things
// not including definition, not revalent
void exitPhysics(void);
// reset vehicle position, rotation, momentum, etc..
// not including definition, not revalent
void resetVehicle(void);
// helper function to create rigid body
// not including definition, not revalent
btRigidBody* localCreateRigidBody(btScalar mass, const btTransform& startTransform, btCollisionShape* shape);
};
Vehicle.cpp
#include "Vehicle.hpp"
Vehicle::Vehicle(Vao *chassisVao, Vao *wheelVao, const float *heights, uint32_t gridsize, float amplitude) {
initPhysics(heights, gridsize, amplitude);
if (chassisVao) {
m_chassisEntity = new Entity(chassisVao);
}
for (int i = 0; i < 4; ++i) {
m_wheelEntities.push_back(Entity(wheelVao));
}
}
Vehicle::~Vehicle() {
exitPhysics();
if (m_chassisEntity) {
delete m_chassisEntity;
}
m_wheelEntities.clear();
}
void Vehicle::initPhysics(const float *heights, uint32_t gridsize, float amplitude) {
// setup dynamics world
m_collisionConfiguration = new btDefaultCollisionConfiguration();
m_dispatcher = new btCollisionDispatcher(m_collisionConfiguration);
btVector3 worldMin(-1000, -1000, -1000);
btVector3 worldMax(1000, 1000, 1000);
m_overlappingPairCache = new btAxisSweep3(worldMin, worldMax);
#ifdef USE_MLCP_SOLVER
btDantzigSolver* mlcp = new btDantzigSolver();
// btSolveProjectedGaussSeidel* mlcp = new btSolveProjectedGaussSeidel();
btMLCPSolver* sol = new btMLCPSolver(mlcp);
m_solver = sol;
#else
m_solver = new btSequentialImpulseConstraintSolver();
#endif
m_world = new btDiscreteDynamicsWorld(m_dispatcher, m_overlappingPairCache, m_solver, m_collisionConfiguration);
#ifdef USE_MLCP_SOLVER
m_world->getSolverInfo().m_minimumSolverBatchSize = 1;
#else
m_world->getSolverInfo().m_minimumSolverBatchSize = 128;
#endif
m_world->getSolverInfo().m_globalCfm = 0.00001;
// create ground object
// btVector3 groundExtents(100, 3, 100);
// btCollisionShape* groundShape = new btBoxShape(groundExtents);
btCollisionShape* groundShape = new btHeightfieldTerrainShape(gridsize + 1, gridsize + 1, heights, 0.0f, amplitude, 1, false);
m_collisionShapes.push_back(groundShape);
btTransform tr;
tr.setIdentity();
tr.setOrigin(btVector3(gridsize * 0.5f, WHEEL_RADIUS, gridsize * 0.5f));
localCreateRigidBody(0, tr, groundShape);
// create vehicle
// BEGIN - create chassis shape
btVector3 vehicleExtents(1.76f, 1.1f, 4.0f);
btCollisionShape* chassisShape = new btBoxShape(vehicleExtents);
m_collisionShapes.push_back(chassisShape);
btCompoundShape* compound = new btCompoundShape();
m_collisionShapes.push_back(compound);
btTransform localTrans;
localTrans.setIdentity();
//localTrans effectively shifts the center of mass with respect to the chassis
localTrans.setOrigin(btVector3(0, 1, 0));
compound->addChildShape(localTrans, chassisShape);
tr.setOrigin(btVector3(0, 0, 0));
m_carChassis = localCreateRigidBody(800, tr, compound);
// END - create chassis shape
// BEGIN - create vehicle
m_vehicleRayCaster = new btDefaultVehicleRaycaster(m_world);
m_vehicle = new btRaycastVehicle(m_tuning, m_carChassis, m_vehicleRayCaster);
m_carChassis->setActivationState(DISABLE_DEACTIVATION); // never deactivate the vehicle
m_world->addVehicle(m_vehicle);
// choose coordinate system
m_vehicle->setCoordinateSystem(0, 1, 2);
btVector3 wheelDirection(0, -1, 0);
btVector3 wheelAxis(-1, 0, 0);
btVector3 connectionPoint(0.5f * vehicleExtents.x(), WHEEL_RADIUS, 0.5f * vehicleExtents.z() - WHEEL_RADIUS);
m_vehicle->addWheel(connectionPoint, wheelDirection, wheelAxis, SUSPENSION_REST_LENGTH, WHEEL_RADIUS, m_tuning, true);
connectionPoint = btVector3(-0.5f * vehicleExtents.x(), WHEEL_RADIUS, 0.5f * vehicleExtents.z() - WHEEL_RADIUS);
m_vehicle->addWheel(connectionPoint, wheelDirection, wheelAxis, SUSPENSION_REST_LENGTH, WHEEL_RADIUS, m_tuning, true);
connectionPoint = btVector3(0.5f * vehicleExtents.x(), WHEEL_RADIUS, -0.5f * vehicleExtents.z() + WHEEL_RADIUS);
m_vehicle->addWheel(connectionPoint, wheelDirection, wheelAxis, SUSPENSION_REST_LENGTH, WHEEL_RADIUS, m_tuning, false);
connectionPoint = btVector3(-0.5f * vehicleExtents.x(), WHEEL_RADIUS, -0.5f * vehicleExtents.z() + WHEEL_RADIUS);
m_vehicle->addWheel(connectionPoint, wheelDirection, wheelAxis, SUSPENSION_REST_LENGTH, WHEEL_RADIUS, m_tuning, false);
for (int i = 0; i < m_vehicle->getNumWheels(); i++) {
btWheelInfo& wheel = m_vehicle->getWheelInfo(i);
wheel.m_suspensionStiffness = SUSPENSION_STIFFNESS;
wheel.m_wheelsDampingRelaxation = SUSPENSION_DAMPING;
wheel.m_wheelsDampingCompression = SUSPENSION_COMPRESSION;
wheel.m_frictionSlip = WHEEL_FRICTION;
wheel.m_rollInfluence = ROLL_IN_INFLUENCE;
}
resetVehicle();
}
void Vehicle::stepSimulation(uint32_t frameTime) {
float speed = m_vehicle->getCurrentSpeedKmHour();
m_vehicleEngineForce = 0.0f;
m_vehicleBreakingForce = 0.0f;
/* --->
Processing input sets m_vehicleEngineForce, m_vehicleBreakingForce, m_vehicleSteering
<--- */
m_vehicle->applyEngineForce(m_vehicleEngineForce, 2);
m_vehicle->setBrake(m_vehicleBreakingForce, 2);
m_vehicle->applyEngineForce(m_vehicleEngineForce, 3);
m_vehicle->setBrake(m_vehicleBreakingForce, 3);
m_vehicle->setSteeringValue(m_vehicleSteering, 0);
m_vehicle->setSteeringValue(m_vehicleSteering, 1);
m_world->stepSimulation(frameTime * 0.001f, 2);
btMLCPSolver *solver = (btMLCPSolver *) m_world->getConstraintSolver();
int numFallbacks = solver->getNumFallbacks();
if (numFallbacks) {
std::cerr << "MLCP solver failed " << numFallbacks << " times, falling back to btSequentialImpulseSolver" << std::endl;
}
solver->setNumFallbacks(0);
}
And here is a video to illustrate : link
Thank you
I finally solve this issue, I used bullet physics debug drawer to view bounding boxes. The problem was the chassis shape colliding on the terrain because btBoxShape takes the half extent, so I multiplied everything by 0.5 and it works well now.
Here is the debugger code written in C++ for modern OpenGL, based on this forum thread :
BulletDebugDrawer.hpp
#ifndef BULLET_DEBUG_DRAWER_H
#define BULLET_DEBUG_DRAWER_H
#include <bullet/LinearMath/btIDebugDraw.h>
#include <vector>
class BulletDebugDrawer : public btIDebugDraw {
private:
int m_debugMode;
std::vector<float> m_lines;
public:
BulletDebugDrawer();
virtual void drawLine(const btVector3& from,const btVector3& to,const btVector3& color);
virtual void reportErrorWarning(const char* warningString);
virtual void setDebugMode(int debugMode);
virtual int getDebugMode(void) const;
virtual void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color) {
}
virtual void draw3dText(const btVector3& location, const char* textString) {
}
void glfw3_device_create(void);
void glfw3_device_render(const float *matrix);
void glfw3_device_destroy(void);
};
#endif
BulletDebugDrawer.cpp
#include "BulletDebugDrawer.hpp"
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <glad/gl.h>
#define MAX_LINES_DRAWCALL 1000
GLuint dev_program;
GLint dev_uniform_proj;
GLint dev_uniform_col;
GLint dev_attrib_pos;
GLuint dev_vao;
GLuint dev_vbo;
BulletDebugDrawer::BulletDebugDrawer() : m_debugMode(0) {
}
void BulletDebugDrawer::drawLine(const btVector3& from,const btVector3& to, const btVector3& color) {
m_lines.push_back(from.getX());
m_lines.push_back(from.getY());
m_lines.push_back(from.getZ());
m_lines.push_back(to.getX());
m_lines.push_back(to.getY());
m_lines.push_back(to.getZ());
}
void BulletDebugDrawer::setDebugMode(int debugMode) {
m_debugMode = debugMode;
}
int BulletDebugDrawer::getDebugMode() const {
return m_debugMode;
}
void BulletDebugDrawer::reportErrorWarning(const char* warningString) {
std::cout << warningString << std::endl;
}
void BulletDebugDrawer::glfw3_device_create(void) {
GLint status;
static const GLchar *vertex_shader =
"#version 150\n"
"uniform mat4 ProjMtx;\n"
"in vec3 Position;\n"
"void main() {\n"
" gl_Position = ProjMtx * vec4(Position, 1);\n"
"}\n";
static const GLchar *fragment_shader =
"#version 150\n"
"uniform vec3 Color;\n"
"out vec4 Out_Color;\n"
"void main(){\n"
" Out_Color = vec4(Color, 1);\n"
"}\n";
dev_program = glCreateProgram();
GLuint vert_shdr = glCreateShader(GL_VERTEX_SHADER);
GLuint frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vert_shdr, 1, &vertex_shader, 0);
glShaderSource(frag_shdr, 1, &fragment_shader, 0);
glCompileShader(vert_shdr);
glCompileShader(frag_shdr);
glGetShaderiv(vert_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glGetShaderiv(frag_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glAttachShader(dev_program, vert_shdr);
glAttachShader(dev_program, frag_shdr);
glLinkProgram(dev_program);
glGetProgramiv(dev_program, GL_LINK_STATUS, &status);
assert(status == GL_TRUE);
glDetachShader(dev_program, vert_shdr);
glDetachShader(dev_program, frag_shdr);
glDeleteShader(vert_shdr);
glDeleteShader(frag_shdr);
dev_uniform_proj = glGetUniformLocation(dev_program, "ProjMtx");
dev_uniform_col = glGetUniformLocation(dev_program, "Color");
dev_attrib_pos = glGetAttribLocation(dev_program, "Position");
{
/* buffer setup */
glGenBuffers(1, &dev_vbo);
glGenVertexArrays(1, &dev_vao);
glBindVertexArray(dev_vao);
glBindBuffer(GL_ARRAY_BUFFER, dev_vbo);
glBufferData(GL_ARRAY_BUFFER, MAX_LINES_DRAWCALL * 24, nullptr, GL_STREAM_DRAW);
glEnableVertexAttribArray(dev_attrib_pos);
glVertexAttribPointer(dev_attrib_pos, 3, GL_FLOAT, GL_FALSE, 12, 0);
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void BulletDebugDrawer::glfw3_device_render(const float *matrix) {
glUseProgram(dev_program);
glUniformMatrix4fv(dev_uniform_proj, 1, GL_FALSE, matrix);
glUniform3f(dev_uniform_col, 1.0f, 0.0f, 0.0f);
glBindVertexArray(dev_vao);
glBindBuffer(GL_ARRAY_BUFFER, dev_vbo);
for (int i = 0; i < m_lines.size(); i += 2 * MAX_LINES_DRAWCALL) {
int batchVertexCount = std::min<int>(m_lines.size() - i, 2 * MAX_LINES_DRAWCALL);
glBufferSubData(GL_ARRAY_BUFFER, 0, batchVertexCount * 12, reinterpret_cast<void *>(m_lines.data() + i));
glDrawArrays(GL_LINES, 0, batchVertexCount);
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(0);
m_lines.clear();
}
void BulletDebugDrawer::glfw3_device_destroy(void) {
glDeleteProgram(dev_program);
glDeleteBuffers(1, &dev_vbo);
glDeleteVertexArrays(1, &dev_vao);
}

How to implement interactive rotation operations in a decent way

Recently, I want to achieve interactive rotation operations as can be done in meshlab:
Basically, it achieves rotation of three degrees of freedom. I visualize these operations as following codes with the help of GLFW:
static void mouse_move_callback(GLFWwindow* window, double xpos, double ypos){
...
do{
//perform rotation operations only if keeping the right mouse key pressed
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_RELEASE) {
g_clr_right_mouse = true;
break;
}
/*clear mouse state once transferred from release state
to pressed state to prevent from a instant flicker*/
if(g_clr_right_mouse){
g_lastX = xpos;
g_lastY = ypos;
g_clr_right_mouse = false;
}
float xoffset = xpos - g_lastX; //let movement from down to top positive
float yoffset = g_lastY - ypos;
g_lastX = xpos;
g_lastY = ypos;
//do counterclockwise rotation around x-asis with movement in y direction
glm::mat4 r1 = glm::rotate(glm::mat4(), glm::radians(-yoffset * 0.5f), glm::vec3(1.0f,0.0f,0.0f));
//do counterclockwise rotation around y-asis with movement in x direction
glm::mat4 r2 = glm::rotate(glm::mat4(), glm::radians( xoffset * 0.5f), glm::vec3(0.0f,1.0f,0.0f));
glm::mat4 tmp = r2 * r1 * g_model;
for(int i=0; i<3; i++)
g_model[i] = tmp[i];
return ;
}while(false);
}
These codes are located here, and the whole project can be found here which can be downloaded and built. Finally, it performs as follows:
However, my implementation can only achieve rotation operations of 2 DOF, I add a keyboard callback to achieve rotation around the z axis:
void keyboard_callback(GLFWwindow* window, int key, int scancode, int action, int mod){
if(glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS){
glm::mat4 r3 = glm::rotate(glm::mat4(), glm::radians(3.0f), glm::vec3(0,0,1.0f));
glm::mat4 tmp = r3 * g_model;
for(int i=0; i<3; i++)
g_model[i] = tmp[i];
}else if(glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS){
glm::mat4 r3 = glm::rotate(glm::mat4(), glm::radians(-3.0f), glm::vec3(0,0,1.0f));
glm::mat4 tmp = r3 * g_model;
for(int i=0; i<3; i++)
g_model[i] = tmp[i];
}
}
So my question is how to decently achieve interactive rotation operations of 3 DOF only with mouse movement?
When dragging the mouse, the object must be rotated around an axis that is perpendicular to the direction of movement of the mouse. The pivot is the origin of the model.
Rotate the mouse movement vector by 90 ° in the XY plane of the view. Since this is a vector in view space, the vector must be transformed from view space into world space. The matrix that transforms a vector from view space to world space is the inverse matrix of the upper left 3x3 of the view matrix:
vec2 drag_start;
vec2 drag_end;
glm::mat3 to_world = glm::inverse(glm::mat3(view_matrix));
glm::vec2 drag_vec = glm::vec2(drag_end.x - drag_start.x, drag_start.y - drag_end.y);
glm::vec3 axis_vec = glm::normalize(to_world * glm::vec3(-drag_vec.y, drag_vec.x, 0));
Create a rotation matrix around the axis. The angle depends on the length of the vector (height is the height of the viewport in pixels):
GLfloat angle = glm::length(drag_vec) / height / 2 * M_PI;
drag_rotation = glm::rotate(glm::mat4(1.0f), angle, axis_vec);
Compute a rotation matrix while dragging the mouse. Concatenate the rotation matrix and the model matrix after the drag ends:
glm::mat4 view_matrix(1.0f);
glm::mat4 model_rotation(1.0f);
glm::mat4 drag_rotation(1.0f);
glm::vec2 drag_start(0.0f);
bool drag = false;
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button != GLFW_MOUSE_BUTTON_LEFT)
return;
if (action == GLFW_PRESS)
{
drag = true;
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
drag_start = glm::vec2(xpos, ypos);
}
else if (action == GLFW_RELEASE)
{
drag = false;
model_rotation = drag_rotation * model_rotation;
drag_rotation = glm::mat4(1.0f);
}
}
void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
if (!drag)
return;
glm::mat3 to_world = glm::inverse(glm::mat3(view_matrix));
glm::vec2 drag_vec = glm::vec2(xpos - drag_start.x, drag_start.y - ypos);
glm::vec3 axis_vec = glm::normalize(to_world * glm::vec3(-drag_vec.y, drag_vec.x, 0));
GLfloat angle = glm::length(drag_vec) / height / 2 * M_PI;
drag_rotation = glm::rotate(glm::mat4(1.0f), angle, axis_vec);
}
The model matrix is the concatenation of drag_rotation and model_rotation:
glm::mat4 model = drag_rotation * model_rotation;
See also Orbit
Complete example:
#include <GL/glew.h>
#include <GL/gl.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <GLFW/glfw3.h>
#include <vector>
#include <string>
#include <stdexcept>
#include <iostream>
#define _USE_MATH_DEFINES
#include <cmath>
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
std::string sh_vert = R"(
#version 460 core
layout (location = 0) in vec4 a_position;
layout (location = 1) in vec3 a_uvw;
out vec3 v_uvw;
layout (location = 0) uniform mat4 u_projection;
layout (location = 1) uniform mat4 u_view;
layout (location = 2) uniform mat4 u_model;
void main()
{
v_uvw = a_uvw;
gl_Position = u_projection * u_view * u_model * a_position;
}
)";
std::string sh_frag = R"(
#version 460 core
out vec4 frag_color;
in vec3 v_uvw;
vec3 HUEtoRGB(in float H)
{
float R = abs(H * 6.0 - 3.0) - 1.0;
float G = 2.0 - abs(H * 6.0 - 2.0);
float B = 2.0 - abs(H * 6.0 - 4.0);
return clamp(vec3(R, G, B), 0.0, 1.0);
}
void main()
{
frag_color = vec4(HUEtoRGB(v_uvw.z), 1.0);
}
)";
class ShaderProgram
{
public:
GLuint programObject;
static ShaderProgram newProgram(const std::string& vsh, const std::string& fsh);
private:
GLuint compileShader(const std::string& sourceCode, GLenum shaderType);
void linkProgram(std::vector<GLuint> shObjs);
void compileStatus(GLuint shader);
void linkStatus();
};
class VertexArrayObject
{
public:
GLuint vaoObject = 0;
GLsizei noOfVertices = 0;
GLsizei noOfIndices = 0;
static VertexArrayObject newCube();
static VertexArrayObject newCircles();
static VertexArrayObject newVAO(const std::vector<GLfloat>& varray, const std::vector<GLuint>& iarray);
};
int width = 800, height = 600;
glm::mat4 view_matrix(1.0f);
glm::mat4 model_rotation(1.0f);
glm::mat4 drag_rotation(1.0f);
glm::vec2 drag_start(0.0f);
bool drag = false;
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button != GLFW_MOUSE_BUTTON_LEFT)
return;
if (action == GLFW_PRESS)
{
drag = true;
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
drag_start = glm::vec2(xpos, ypos);
}
else if (action == GLFW_RELEASE)
{
drag = false;
model_rotation = drag_rotation * model_rotation;
drag_rotation = glm::mat4(1.0f);
}
}
void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
if (!drag)
return;
glm::mat3 to_world = glm::inverse(glm::mat3(view_matrix));
glm::vec2 drag_vec = glm::vec2(xpos - drag_start.x, drag_start.y - ypos);
glm::vec3 axis_vec = glm::normalize(to_world * glm::vec3(-drag_vec.y, drag_vec.x, 0));
GLfloat angle = glm::length(drag_vec) / height / 2 * M_PI;
drag_rotation = glm::rotate(glm::mat4(1.0f), angle, axis_vec);
}
int main(void)
{
if (glfwInit() == GLFW_FALSE)
throw std::runtime_error( "error initializing glfw" );
glfwWindowHint(GLFW_SAMPLES, 8);
GLFWwindow * window = glfwCreateWindow(width, height, "OGL window", nullptr, nullptr);
if (window == nullptr)
{
glfwTerminate();
throw std::runtime_error( "error initializing window" );
}
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetCursorPosCallback(window, cursor_position_callback);
glfwMakeContextCurrent(window);
if ( glewInit() != GLEW_OK )
throw std::runtime_error( "error initializing glew" );
auto progam = ShaderProgram::newProgram(sh_vert, sh_frag);
auto cube = VertexArrayObject::newCube();
auto circles = VertexArrayObject::newCircles();
glUseProgram(progam.programObject);
glEnable( GL_DEPTH_TEST );
glClearColor(0.1f, 0.3f, 0.2f, 0.0f);
view_matrix = glm::lookAt(glm::vec3(0.0f, 0.0f, 7.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glUniformMatrix4fv(1, 1, GL_FALSE, glm::value_ptr(view_matrix));
while (!glfwWindowShouldClose(window))
{
glfwGetFramebufferSize(window, &width, &height);
float ascpect = (float)width / (float)height;
glm::mat4 project = glm::perspective(glm::radians(60.0f), ascpect, 0.1f, 20.0f);
glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(project));
glm::mat4 model = drag_rotation * model_rotation;
glViewport(0, 0, width, height);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glUniformMatrix4fv(2, 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(cube.vaoObject);
glDrawElements(GL_TRIANGLES, cube.noOfIndices, GL_UNSIGNED_INT, nullptr);
glUniformMatrix4fv(2, 1, GL_FALSE, glm::value_ptr(glm::scale(model, glm::vec3(2.5f))));
glBindVertexArray(circles.vaoObject);
glDrawElements(GL_LINES, circles.noOfIndices, GL_UNSIGNED_INT, nullptr);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
ShaderProgram ShaderProgram::newProgram(const std::string& vsh, const std::string& fsh)
{
ShaderProgram program;
auto shObjs = std::vector<GLuint>
{
program.compileShader(vsh, GL_VERTEX_SHADER),
program.compileShader(fsh, GL_FRAGMENT_SHADER),
};
for (auto shObj : shObjs)
program.compileStatus(shObj);
program.linkProgram(shObjs);
for (auto shObj : shObjs)
glDeleteShader(shObj);
return program;
}
GLuint ShaderProgram::compileShader(const std::string& sourceCode, GLenum shaderType)
{
auto shaderObj = glCreateShader(shaderType);
const char* srcCodePtr = sourceCode.c_str();
glShaderSource(shaderObj, 1, &srcCodePtr, nullptr);
glCompileShader(shaderObj);
return shaderObj;
}
void ShaderProgram::linkProgram(std::vector<GLuint> shObjs)
{
programObject = glCreateProgram();
for (auto shObj : shObjs)
glAttachShader(programObject, shObj);
glLinkProgram(programObject);
linkStatus();
}
void ShaderProgram::compileStatus(GLuint shader)
{
GLint status = GL_TRUE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
GLint logLen;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);
std::vector< char >log(logLen);
GLsizei written;
glGetShaderInfoLog(shader, logLen, &written, log.data());
std::cout << "compile error:" << std::endl << log.data() << std::endl;
}
}
void ShaderProgram::linkStatus()
{
GLint status = GL_TRUE;
glGetProgramiv(programObject, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
GLint logLen;
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &logLen);
std::vector< char >log(logLen);
GLsizei written;
glGetProgramInfoLog(programObject, logLen, &written, log.data());
std::cout << "link error:" << std::endl << log.data() << std::endl;
}
}
VertexArrayObject VertexArrayObject::newCube()
{
static const std::vector<GLfloat> vertices{ -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1 };
static const std::vector<GLfloat> uv{ 0, 0, 1, 0, 1, 1, 0, 1 };
static const std::vector<size_t> faces{ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 };
std::vector<GLfloat> varray;
std::vector<GLuint> iarray;
for (auto si = 0; si < faces.size() / 4; si++)
{
for (auto qi = 0; qi < 4; qi++)
{
varray.insert(varray.end(), vertices.begin() + faces[si * 4 + qi] * 3, vertices.begin() + faces[si * 4 + qi] * 3 + 3);
std::vector<GLfloat> uvw{ 0, 0, (GLfloat)si * 4.0f / (GLfloat)faces.size() };
varray.insert(varray.end(), uvw.begin(), uvw.end());
}
std::vector<GLuint> indices{ 4u * si, 4u * si + 1, 4u * si + 2, 4u * si, 4u * si + 2, 4u * si + 3 };
iarray.insert(iarray.end(), indices.begin(), indices.end());
}
return newVAO(varray, iarray);
}
VertexArrayObject VertexArrayObject::newCircles()
{
const GLuint noC = 360;
std::vector<GLfloat> varray;
std::vector<GLuint> iarray;
for (int i = 0; i <= noC; i++)
{
GLfloat angle = static_cast<GLfloat>(i * 2 * M_PI / noC);
GLfloat c = cos(angle), s = sin(angle);
std::vector<GLfloat> va{ 0, c, s, 0, 0, 0, s, 0, c, 0, 0, 1.0f / 3.0f, c, s, 0, 0, 0, 2.0f / 3.0f };
varray.insert(varray.end(), va.begin(), va.end());
}
for (GLuint ci = 0; ci < 3; ci++)
{
for (GLuint i = 0; i <= noC; i++)
{
std::vector<GLuint> ia{ i * 3 + ci, ((i + 1) % noC) * 3 + ci };
iarray.insert(iarray.end(), ia.begin(), ia.end());
}
}
return newVAO(varray, iarray);
}
VertexArrayObject VertexArrayObject::newVAO(const std::vector<GLfloat>& varray, const std::vector<GLuint>& iarray)
{
VertexArrayObject vao;
vao.noOfIndices = static_cast<GLsizei>(iarray.size());
vao.noOfVertices = static_cast<GLsizei>(varray.size() / 6);
GLuint bufferObjects[2];
glGenBuffers(2, bufferObjects);;
glGenVertexArrays(1, &vao.vaoObject);
glBindVertexArray(vao.vaoObject);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, bufferObjects[0]);
glBufferData(GL_ARRAY_BUFFER, varray.size() * sizeof(*varray.data()), varray.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(*varray.data()), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(*varray.data()), (void*)(3 * sizeof(*varray.data())));
if (vao.noOfIndices > 0)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObjects[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, iarray.size() * sizeof(*iarray.data()), iarray.data(), GL_STATIC_DRAW);
}
glBindVertexArray(0);
glDeleteBuffers(2, bufferObjects);
return vao;
}
You kinda need to draw the ball to make it intuitive.
On mouse down, you put an anchor on the ball directly under the mouse pointer. If the click is outside the ball, then you use the closest point on the ball.
As the mouse moves, you rotate the ball so that the anchor point follows the shortest path so that it remains directly under the mouse pointer. If the mouse pointer is off the ball, then the closest point on the ball is used.
Maybe this will help.

Pen-like behaviour when setting model matrix for freetype in OpenGL

So here's the thing. I'm trying to create a Text class using Freetype, which inherits from a Model class, which in turn contains stuff like setting VAO, VBOs, programs, textures, position, rotation and scaling. Here's the code for Model.cpp:
// Model.cpp
#include "Model.h"
using namespace OpenGL::Rendering;
Model::Model() {
model_matrix = glm::mat4(1.0f);
position = glm::vec3(0.0, 0.0, 0.0);
scale = glm::vec3(1.0, 1.0, 1.0);
rotation = glm::vec3(1.0, 0.0, 0.0);
rotation_angle = 0.0;
}
Model::~Model() { destroy(); }
void Model::draw() {}
void Model::set_program(GLuint program) { this->program = program; }
GLuint Model::get_vao() const { return vao; }
const std::vector<GLuint>& Model::get_vbos() const { return vbos; }
GLuint Model::get_texture(std::string texture_name) const {
if (textures.size() > 0) {
return textures.at(texture_name);
} else {
Log()->critical("No textures to get. Requested {}", texture_name);
return 0;
}
}
GLuint Model::get_texture() const {
if (textures.size() > 0) {
return textures.begin()->second;
} else {
Log()->critical("No textures to get.");
return 0;
}
}
void Model::set_texture(std::string texture_name, GLuint texture) {
if (texture == 0) {
Log()->critical("Texture {} is empty.", texture_name);
return;
} else {
textures[texture_name] = texture;
}
}
void Model::destroy() {
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(vbos.size(), &vbos[0]);
vbos.clear();
if (textures.size() > 0) {
for (auto& t : textures) {
glDeleteTextures(1, &t.second);
}
textures.clear();
}
}
const glm::vec3& Model::get_position() const { return position; }
void Model::set_position(float pos_x, float pos_y, float pos_z) {
position = glm::vec3(pos_x, pos_y, pos_z);
update_model_matrix();
}
void Model::set_rotation(float angle_deg, float x, float y, float z) {
rotation_angle = angle_deg * M_PI / 180.0;
rotation = glm::vec3(x, y, z);
update_model_matrix();
}
void Model::set_scale(float sca_x, float sca_y, float sca_z) {
scale = glm::vec3(sca_x, sca_y, sca_z);
update_model_matrix();
}
void Model::update_model_matrix() {
model_matrix = glm::mat4(1.0f);
auto pos = glm::vec3(this->position.x, -this->position.y, this->position.z);
glm::mat4 translate_mat = glm::translate(glm::mat4(1.0f), pos);
glm::mat4 rotate_mat = glm::mat4(1.0f);
if (this->rotation.x != 0.0 || this->rotation.y != 0.0 || this->rotation.z != 0.0) {
rotate_mat = glm::rotate(glm::mat4(1.0f), this->rotation_angle, this->rotation);
} else {
rotate_mat =
glm::rotate(glm::mat4(1.0f), 0.0f, glm::vec3(1.0, 0.0, 0.0));
}
glm::mat4 scale_mat = glm::scale(glm::mat4(1.0f), this->scale);
this->model_matrix = translate_mat * rotate_mat * scale_mat;
glUniformMatrix4fv(glGetUniformLocation(this->program, "model_matrix"), 1,
false, &this->model_matrix[0][0]);
}
Then I have my Text class, which uses Freetype to load fonts and stuff. I know it's not optimised really, so look past that. Note that for Freetype, I'm using GL_DYNAMIC_DRAW, instead of GL_STATIC_DRAW
// Text.cpp
#include "Text.h"
using namespace OpenGL::Rendering::Models;
Text::Text(const std::string& text, OpenGL::Container::Position position,
int font_size, OpenGL::Container::Color color) {
m_font_size = font_size;
m_scale = 1.0;
m_text = text;
float angle = 0;
this->color.r = color.r;
this->color.g = color.g;
this->color.b = color.b;
this->color.a = color.a;
matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L);
matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L);
matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L);
this->position.x = position.x;
this->position.y = position.y;
this->position.z = position.z;
if (FT_Init_FreeType(&font)) {
Log()->critical("Could not initalize Freetype library for fonts.");
}
if (FT_New_Face(font, "/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf", 0,
&face)) {
Log()->critical("Could not load font. File is missing maybe?");
}
FT_Set_Char_Size(face, 0, m_font_size * 64, 300, 300);
FT_Set_Pixel_Sizes(face, 0, m_font_size);
if (FT_Load_Char(face, 'X', FT_LOAD_RENDER)) {
Log()->critical(
"Could not load a test glyph. The font is corrupted maybe?");
}
for (GLubyte c = 0; c < 128; ++c) {
FT_Set_Transform(face, &matrix, 0);
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
Log()->critical("Could not load glyph \"{}\"", c);
continue;
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width,
face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x};
characters.insert(std::pair<GLchar, Character>(c, character));
}
FT_Done_Face(face);
FT_Done_FreeType(font);
}
void Text::create() {
GLuint vao;
GLuint vbo;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL,
GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat),
(void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
this->vao = vao;
this->vbos.push_back(vbo);
this->set_program(OpenGL::Managers::ShaderManager::get_program("text"));
// this->set_position(position.x, position.y, position.z);
Log()->warn("Pos {0}, {1}, {2}", position.x, position.y, position.z);
}
void Text::draw() {
GLfloat temp_x = 0;
GLfloat temp_y = 0;
glUseProgram(this->program);
glUniform4f(glGetUniformLocation(this->program, "text_color"), color.r,
color.g, color.b, color.a);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(this->vao);
std::string::const_iterator c;
for (c = m_text.begin(); c != m_text.end(); c++) {
Character ch = characters[*c];
GLfloat xpos = temp_x + ch.bearing.x * m_scale;
GLfloat ypos = temp_y - (ch.size.y - ch.bearing.y) * m_scale;
GLfloat w = ch.size.x * m_scale;
GLfloat h = ch.size.y * m_scale;
GLfloat vertices[6][4] = {
{xpos, ypos + h, 0.0, 0.0}, /**/
{xpos, ypos, 0.0, 1.0}, /**/
{xpos + w, ypos, 1.0, 1.0}, /**/
{xpos, ypos + h, 0.0, 0.0}, /**/
{xpos + w, ypos, 1.0, 1.0}, /**/
{xpos + w, ypos + h, 1.0, 0.0} /**/
};
glBindTexture(GL_TEXTURE_2D, ch.texture_id);
glBindBuffer(GL_ARRAY_BUFFER, this->vbos[0]);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
temp_x += (ch.advance >> 6) * m_scale;
}
glUniformMatrix4fv(glGetUniformLocation(this->program, "model_matrix"), 1,
false, &model_matrix[0][0]);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Text::set_text(const std::string& a_text) {
if (!a_text.empty()) {
m_text = a_text;
} else {
Log()->info("Cannot set the text. Input seems to be empty.");
}
}
std::string Text::get_text() { return m_text; }
void Text::set_color(const Container::Color a_color) {
color.r = a_color.r;
color.g = a_color.g;
color.b = a_color.b;
color.a = a_color.a;
}
So the problem is, drawing a text behaves like a pen. So when I try to set the position of text1 object, text2 object moves. text2 affects text3 and so on.
// main.cpp
// This one moves to 200, 100 as defined in text3
auto text1 = new OpenGL::Rendering::Models::Text(
"Text1", Container::Position(pos_x - 5, pos_y - radius, 0), 18,
Container::Color::YELLOW);
text1->create();
text1->set_scale(3, 3, 3);
text1->set_position(500, 500, 0);
// This line actually affects text1, as expected
text1->set_text("Blah");
// This one scales to 3 times bigger, as stated in the previous object
auto text2 = new OpenGL::Rendering::Models::Text(
"Text2", Container::Position(pos_x - radius - 10, pos_y + radius/1.5, 0), 18,
Container::Color::YELLOW);
text2->create();
text2->set_rotation(25, 0, 0, 1);
// And this one gets rotated
auto text3 = new OpenGL::Rendering::Models::Text(
"Text3", Container::Position(0, 0, 0), 125,
Container::Color::YELLOW);
text3->create();
text3->set_position(200, 100, 0);
Here's how it looks like:
Can somebody please explain this weird behaviour? The problem is, all other 2D shapes work just fine with my functions, except this Text class, which kinda affects the next object. Thank you very much in advance. If you need more information to diagnose the problem, I'll add it right away.
As mentioned in comments by #Rabbid76, moving the below line to the beginning of Text::draw() fixed the problem:
glUniformMatrix4fv(glGetUniformLocation(this->program, "model_matrix"), 1,
false, &model_matrix[0][0]);

LWJGL texture with shaders produced skewed image

I'm trying to do 2D graphics in orthogonal mode. The code loads a picture of a cat and 2 simple shaders, which just pass through their input, unmodified. I expect the program to display the picture of the cat (or at least a part of it) in the middle of the screen, without any rotation or skew.
The program executes successfully, but I can't figure out why the result looks like this:
An OpenGL guru might spot the problem quickly, but I can't find it. I have the feeling that the problem might be at the "Create buffer for vertex and texture coordinates" part, but everything looked okay.
The cat image:
Vertex shader:
#version 150 core
in vec4 in_Position;
in vec2 in_TextureCoord;
out vec2 pass_TextureCoord;
void main(void) {
gl_Position = in_Position;
pass_TextureCoord = in_TextureCoord;
}
Pixel shader:
#version 150 core
uniform sampler2D texture_diffuse;
in vec2 pass_TextureCoord;
out vec4 out_Color;
void main(void) {
out_Color = texture(texture_diffuse, pass_TextureCoord);
}
Java (LWJGL) code:
package lwjgl_test1;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.*;
import javax.imageio.ImageIO;
import org.lwjgl.*;
import org.lwjgl.opengl.*;
import static org.lwjgl.glfw.GLFW.*;
import java.util.concurrent.TimeUnit;
import static org.lwjgl.opengl.GL11.*;
public class Main {
public static void main(String[] args) {
try {
if (!glfwInit()) {
throw(new Exception("Can't init glfw."));
}
/*
* Create Window
*/
glfwWindowHint(GLFW_RESIZABLE, 0);
long windowGlID = glfwCreateWindow(1024, 768, "Example OpenGL App", 0, 0);
glfwSetWindowPos(windowGlID, 50, 50);
glfwMakeContextCurrent(windowGlID);
glfwShowWindow(windowGlID);
/*
* Initialize OpenGL
*/
GL.createCapabilities();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 1024, 768, 0, 0, 1);
glMatrixMode(GL_MODELVIEW);
/*
* Load texture
*/
int cat = loadTexture("cat.png");
/*
* Load shaders
*/
int vertexShader = loadShader("vertex_shader.txt", GL20.GL_VERTEX_SHADER);
int pixelShader = loadShader("pixel_shader.txt", GL20.GL_FRAGMENT_SHADER);
int pId = GL20.glCreateProgram();
GL20.glAttachShader(pId, vertexShader);
GL20.glAttachShader(pId, pixelShader);
// Position information will be attribute 0
GL20.glBindAttribLocation(pId, 0, "in_Position");
// Textute information will be attribute 1
GL20.glBindAttribLocation(pId, 1, "in_TextureCoord");
GL20.glLinkProgram(pId);
GL20.glValidateProgram(pId);
exitOnGLError("Compiling shaders failed.");
/*
* Create buffer for vertex and texture coordinates
*/
float size = 120.0f;
FloatBuffer vertex_data = BufferUtils.createFloatBuffer(20);
vertex_data.put(new float[] { -size, -size, 0f, 0f, 0f }); // (Vx, Vy, Vz, Tx, Ty)
vertex_data.put(new float[] { size, -size, 0f, 0f, 1f });
vertex_data.put(new float[] { size, size, 0f, 1f, 1f });
vertex_data.put(new float[] { -size, size, 0f, 1f, 0f });
vertex_data.flip();
int vbo_vertex_handle = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo_vertex_handle);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertex_data, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 2 * 4, 0); // mark vertex coordinates
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 3 * 4, 3 * 4); // mark texture coordinates
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
exitOnGLError("Creating buffers failed.");
/*
* Main rendering loop
*/
while(true) {
/*
* Clear screen
*/
glClearColor(0.0f, 1.0f, 1.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/*
* Apply shader program
*/
GL20.glUseProgram(pId);
// Bind the texture
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, cat);
/*
* Draw (use buffers)
*/
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo_vertex_handle);
GL11.glDrawArrays(GL11.GL_QUADS, 0, 4); // Draw an entity with 4 vertices
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
exitOnGLError("Draw failed.");
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GL20.glUseProgram(0); // deselect
/*
* Swap buffers
*/
glfwSwapBuffers(windowGlID);
/*
* Events
*/
glfwPollEvents();
if (glfwWindowShouldClose(windowGlID)) {
break;
}
TimeUnit.MILLISECONDS.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static int loadTexture(String path) throws Exception {
int[] pixels = null;
BufferedImage image = null;
image = ImageIO.read(new FileInputStream(path));
int width = image.getWidth();
int height = image.getHeight();
pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);
int[] data = new int[width * height];
for (int i = 0; i < width * height; i++) {
int a = (pixels[i] & 0xff000000) >> 24;
int r = (pixels[i] & 0xff0000) >> 16;
int g = (pixels[i] & 0xff00) >> 8;
int b = (pixels[i] & 0xff);
data[i] = a << 24 | b << 16 | g << 8 | r;
}
IntBuffer intBuffer1 = ByteBuffer.allocateDirect(data.length << 2).order(ByteOrder.nativeOrder()).asIntBuffer();
intBuffer1.put(data).flip();
int result = glGenTextures();
glBindTexture(GL_TEXTURE_2D, result);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, intBuffer1);
glBindTexture(GL_TEXTURE_2D, 0);
exitOnGLError("Loading texture '"+ path +"' failed.");
return result;
}
private static int loadShader(String filename, int type) {
StringBuilder shaderSource = new StringBuilder();
int shaderID = 0;
try {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line;
while ((line = reader.readLine()) != null) {
shaderSource.append(line).append("\n");
}
reader.close();
} catch (IOException e) {
System.err.println("Could not read file.");
e.printStackTrace();
System.exit(-1);
}
shaderID = GL20.glCreateShader(type);
GL20.glShaderSource(shaderID, shaderSource);
GL20.glCompileShader(shaderID);
if (GL20.glGetShaderi(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
System.err.println("Could not compile shader.");
System.exit(-1);
}
return shaderID;
}
private static void exitOnGLError(String errorMessage) throws Exception {
int errorValue = GL11.glGetError();
if (errorValue != GL11.GL_NO_ERROR) {
throw new Exception(errorMessage);
}
}
}
The problem lies in the stride parameter in this lines:
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 2 * 4, 0);
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 3 * 4, 3 * 4);
Stride tells OpenGL how many bytes apart from each other the begin of two consecutive entries are. Since you are using 5 floats per vertex, this has to be 5 * 4 in both lines:
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 5 * 4, 0);
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 5 * 4, 3 * 4);

lwjgl and render to texture

I need to be able to render to a texture, then draw that texture to a full screen quad. By blatantly copying lwjgl tutorials I was able to make a textured and moving quad, but I can't make it render to a texture:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
import de.matthiasmann.twl.utils.PNGDecoder;
import de.matthiasmann.twl.utils.PNGDecoder.Format;
public class TheQuadExampleMovingFBO {
// Entry point for the application
public static void main(String[] args) {
new TheQuadExampleMovingFBO();
}
// Setup variables
private final String WINDOW_TITLE = "The Quad: Moving";
private final int WIDTH = 320;
private final int HEIGHT = 240;
private final double PI = 3.14159265358979323846;
// Frame Buffer variables
private int fboID = 0;
private int colID = 0;
private int depID = 0;
private IntBuffer drawBuffs;
// Quad variables
private int vaoId = 0;
private int vboId = 0;
private int vboiId = 0;
private int indicesCount = 0;
private VertexData[] vertices = null;
private ByteBuffer verticesByteBuffer = null;
// Shader variables
private int vsId = 0;
private int fsId = 0;
private int pId = 0;
// Texture variables
private int[] texIds = new int[] {0, 0};
private int textureSelector = 0;
// Moving variables
private int projectionMatrixLocation = 0;
private int viewMatrixLocation = 0;
private int modelMatrixLocation = 0;
private Matrix4f projectionMatrix = null;
private Matrix4f viewMatrix = null;
private Matrix4f modelMatrix = null;
private Vector3f modelPos = null;
private Vector3f modelAngle = null;
private Vector3f modelScale = null;
private Vector3f cameraPos = null;
private FloatBuffer matrix44Buffer = null;
public TheQuadExampleMovingFBO() {
// Initialize OpenGL (Display)
this.setupOpenGL();
this.setupQuad();
this.setupShaders();
this.setupTextures();
this.setupMatrices();
while (!Display.isCloseRequested()) {
// Do a single loop (logic/render)
this.loopCycle();
// Force a maximum FPS of about 60
Display.sync(60);
// Let the CPU synchronize with the GPU if GPU is tagging behind
Display.update();
}
// Destroy OpenGL (Display)
this.destroyOpenGL();
}
private void setupMatrices() {
// Setup projection matrix
projectionMatrix = new Matrix4f();
float fieldOfView = 60f;
float aspectRatio = (float)WIDTH / (float)HEIGHT;
float near_plane = 0.1f;
float far_plane = 100f;
float y_scale = this.coTangent(this.degreesToRadians(fieldOfView / 2f));
float x_scale = y_scale / aspectRatio;
float frustum_length = far_plane - near_plane;
projectionMatrix.m00 = x_scale;
projectionMatrix.m11 = y_scale;
projectionMatrix.m22 = -((far_plane + near_plane) / frustum_length);
projectionMatrix.m23 = -1;
projectionMatrix.m32 = -((2 * near_plane * far_plane) / frustum_length);
// Setup view matrix
viewMatrix = new Matrix4f();
// Setup model matrix
modelMatrix = new Matrix4f();
// Create a FloatBuffer with the proper size to store our matrices later
matrix44Buffer = BufferUtils.createFloatBuffer(16);
}
private void setupTextures() {
texIds[0] = this.loadPNGTexture("assets/images/stGrid1.png", GL13.GL_TEXTURE0);
texIds[1] = this.loadPNGTexture("assets/images/stGrid2.png", GL13.GL_TEXTURE0);
this.exitOnGLError("setupTexture");
}
private void setupOpenGL() {
// Setup an OpenGL context with API version 3.2
try {
PixelFormat pixelFormat = new PixelFormat();
ContextAttribs contextAtrributes = new ContextAttribs(3, 2);
contextAtrributes.withForwardCompatible(true);
contextAtrributes.withProfileCore(true);
Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
Display.setTitle(WINDOW_TITLE);
Display.create(pixelFormat, contextAtrributes);
GL11.glViewport(0, 0, WIDTH, HEIGHT);
} catch (LWJGLException e) {
e.printStackTrace();
System.exit(-1);
}
// Setup an XNA like background color
GL11.glClearColor(0.4f, 0.6f, 0.9f, 0f);
// Map the internal OpenGL coordinate system to the entire screen
GL11.glViewport(0, 0, WIDTH, HEIGHT);
System.out.println(GL11.glGetString(GL11.GL_VERSION));
this.exitOnGLError("setupOpenGL");
}
private void setupQuad() {
// We'll define our quad using 4 vertices of the custom 'TexturedVertex' class
VertexData v0 = new VertexData();
v0.setXYZ(-0.5f, 0.5f, 0); v0.setRGB(1, 0, 0); v0.setST(0, 0);
VertexData v1 = new VertexData();
v1.setXYZ(-0.5f, -0.5f, 0); v1.setRGB(0, 1, 0); v1.setST(0, 1);
VertexData v2 = new VertexData();
v2.setXYZ(0.5f, -0.5f, 0); v2.setRGB(0, 0, 1); v2.setST(1, 1);
VertexData v3 = new VertexData();
v3.setXYZ(0.5f, 0.5f, 0); v3.setRGB(1, 1, 1); v3.setST(1, 0);
vertices = new VertexData[] {v0, v1, v2, v3};
// Put each 'Vertex' in one FloatBuffer
verticesByteBuffer = BufferUtils.createByteBuffer(vertices.length *
VertexData.stride);
FloatBuffer verticesFloatBuffer = verticesByteBuffer.asFloatBuffer();
for (int i = 0; i < vertices.length; i++) {
// Add position, color and texture floats to the buffer
verticesFloatBuffer.put(vertices[i].getElements());
}
verticesFloatBuffer.flip();
// OpenGL expects to draw vertices in counter clockwise order by default
byte[] indices = {
0, 1, 2,
2, 3, 0
};
indicesCount = indices.length;
ByteBuffer indicesBuffer = BufferUtils.createByteBuffer(indicesCount);
indicesBuffer.put(indices);
indicesBuffer.flip();
// Create a new Vertex Array Object in memory and select it (bind)
vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);
// Create a new Vertex Buffer Object in memory and select it (bind)
vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesFloatBuffer, GL15.GL_STREAM_DRAW);
// Put the position coordinates in attribute list 0
GL20.glVertexAttribPointer(0, VertexData.positionElementCount, GL11.GL_FLOAT,
false, VertexData.stride, VertexData.positionByteOffset);
// Put the color components in attribute list 1
GL20.glVertexAttribPointer(1, VertexData.colorElementCount, GL11.GL_FLOAT,
false, VertexData.stride, VertexData.colorByteOffset);
// Put the texture coordinates in attribute list 2
GL20.glVertexAttribPointer(2, VertexData.textureElementCount, GL11.GL_FLOAT,
false, VertexData.stride, VertexData.textureByteOffset);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// Deselect (bind to 0) the VAO
GL30.glBindVertexArray(0);
// Create a new VBO for the indices and select it (bind) - INDICES
vboiId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer,
GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
// Set the default quad rotation, scale and position values
modelPos = new Vector3f(0, 0, 0);
modelAngle = new Vector3f(0, 0, 0);
modelScale = new Vector3f(1, 1, 1);
cameraPos = new Vector3f(0, 0, 0);
////////// THIS IS WHERE I ADDED CODE FOR THE FRAMEBUFFER STUFF //////////////
//render to texture attempt
fboID = GL30.glGenFramebuffers();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fboID);
colID = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, colID);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, WIDTH, HEIGHT, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
depID = GL30.glGenRenderbuffers();
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depID);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_DEPTH_COMPONENT, WIDTH, HEIGHT);
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, depID);
GL32.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, colID, 0);
drawBuffs = BufferUtils.createIntBuffer(1);
drawBuffs.put(0, GL30.GL_COLOR_ATTACHMENT0);
GL20.glDrawBuffers(drawBuffs);
if(GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) != GL30.GL_FRAMEBUFFER_COMPLETE){
System.out.println("Framebuffer not complete!");
}else{ System.out.println("Framebuffer is complete!");}
//////// END OF NEW CODE HERE, LOOK IN RENDER CYCLE FOR MORE NEW CODE ///////////
this.exitOnGLError("setupQuad");
}
private void setupShaders() {
// Load the vertex shader
vsId = this.loadShader("assets/shaders/moving/vertex.glsl",
GL20.GL_VERTEX_SHADER);
// Load the fragment shader
fsId = this.loadShader("assets/shaders/moving/fragment.glsl",
GL20.GL_FRAGMENT_SHADER);
// Create a new shader program that links both shaders
pId = GL20.glCreateProgram();
GL20.glAttachShader(pId, vsId);
GL20.glAttachShader(pId, fsId);
GL20.glLinkProgram(pId);
// Position information will be attribute 0
GL20.glBindAttribLocation(pId, 0, "in_Position");
// Color information will be attribute 1
GL20.glBindAttribLocation(pId, 1, "in_Color");
// Textute information will be attribute 2
GL20.glBindAttribLocation(pId, 2, "in_TextureCoord");
// Get matrices uniform locations
projectionMatrixLocation = GL20.glGetUniformLocation(pId, "projectionMatrix");
viewMatrixLocation = GL20.glGetUniformLocation(pId, "viewMatrix");
modelMatrixLocation = GL20.glGetUniformLocation(pId, "modelMatrix");
GL20.glValidateProgram(pId);
this.exitOnGLError("setupShaders");
}
private void logicCycle() {
//-- Input processing
float rotationDelta = 15f;
float scaleDelta = 0.1f;
float posDelta = 0.1f;
Vector3f scaleAddResolution = new Vector3f(scaleDelta, scaleDelta, scaleDelta);
Vector3f scaleMinusResolution = new Vector3f(-scaleDelta, -scaleDelta,
-scaleDelta);
while(Keyboard.next()) {
// Only listen to events where the key was pressed (down event)
if (!Keyboard.getEventKeyState()) continue;
// Switch textures depending on the key released
switch (Keyboard.getEventKey()) {
case Keyboard.KEY_1:
textureSelector = 0;
break;
case Keyboard.KEY_2:
textureSelector = 1;
break;
}
// Change model scale, rotation and translation values
switch (Keyboard.getEventKey()) {
// Move
case Keyboard.KEY_UP:
modelPos.y += posDelta;
break;
case Keyboard.KEY_DOWN:
modelPos.y -= posDelta;
break;
// Scale
case Keyboard.KEY_P:
Vector3f.add(modelScale, scaleAddResolution, modelScale);
break;
case Keyboard.KEY_M:
Vector3f.add(modelScale, scaleMinusResolution, modelScale);
break;
// Rotation
case Keyboard.KEY_LEFT:
modelAngle.z += rotationDelta;
break;
case Keyboard.KEY_RIGHT:
modelAngle.z -= rotationDelta;
break;
}
}
//-- Update matrices
// Reset view and model matrices
viewMatrix = new Matrix4f();
modelMatrix = new Matrix4f();
// Translate camera
Matrix4f.translate(cameraPos, viewMatrix, viewMatrix);
// Scale, translate and rotate model
Matrix4f.scale(modelScale, modelMatrix, modelMatrix);
Matrix4f.translate(modelPos, modelMatrix, modelMatrix);
Matrix4f.rotate(this.degreesToRadians(modelAngle.z), new Vector3f(0, 0, 1),
modelMatrix, modelMatrix);
Matrix4f.rotate(this.degreesToRadians(modelAngle.y), new Vector3f(0, 1, 0),
modelMatrix, modelMatrix);
Matrix4f.rotate(this.degreesToRadians(modelAngle.x), new Vector3f(1, 0, 0),
modelMatrix, modelMatrix);
// Upload matrices to the uniform variables
GL20.glUseProgram(pId);
projectionMatrix.store(matrix44Buffer); matrix44Buffer.flip();
GL20.glUniformMatrix4(projectionMatrixLocation, false, matrix44Buffer);
viewMatrix.store(matrix44Buffer); matrix44Buffer.flip();
GL20.glUniformMatrix4(viewMatrixLocation, false, matrix44Buffer);
modelMatrix.store(matrix44Buffer); matrix44Buffer.flip();
GL20.glUniformMatrix4(modelMatrixLocation, false, matrix44Buffer);
GL20.glUseProgram(0);
this.exitOnGLError("logicCycle");
}
private void renderCycle() {
////////// THESE LINES ARE NEW, ADDED TO RENDER TO THE FRAME BUFFER /////////
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fboID);
GL11.glViewport(0, 0, WIDTH, HEIGHT);
///////// THIS IS THE ORIGINAL RENDER CYCLE CODE, I WANT THIS TO GO TO THE FBO ///////
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
GL20.glUseProgram(pId);
// Bind the texture
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texIds[textureSelector]);
// Bind to the VAO that has all the information about the vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
// Bind to the index VBO that has all the information about the order of the vertices
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
// Draw the vertices
GL11.glDrawElements(GL11.GL_TRIANGLES, indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
// Put everything back to default (deselect)
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL30.glBindVertexArray(0);
GL20.glUseProgram(0);
//////// NEW CODE, I WANT TO DRAW A FULL SCREEN QUAD WITH THE TEXTURE FROM THE FBO ///////
// draw the texture from the FBO
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GL11.glViewport(0, 0, WIDTH, HEIGHT);
GL11.glBegin(GL11.GL_QUADS);
GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f(0.0f, 0.0f, 0.0f);
GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f((float)WIDTH, 0.0f, 0.0f);
GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f((float)WIDTH, (float)HEIGHT, 0.0f);
GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f(0.0f, (float)HEIGHT, 0.0f);
GL11.glEnd();
///////// END OF NEW CODE ///////////
this.exitOnGLError("renderCycle");
}
private void loopCycle() {
// Update logic
this.logicCycle();
// Update rendered frame
this.renderCycle();
this.exitOnGLError("loopCycle");
}
private void destroyOpenGL() {
// Delete the texture
GL11.glDeleteTextures(texIds[0]);
GL11.glDeleteTextures(texIds[1]);
// Delete the shaders
GL20.glUseProgram(0);
GL20.glDetachShader(pId, vsId);
GL20.glDetachShader(pId, fsId);
GL20.glDeleteShader(vsId);
GL20.glDeleteShader(fsId);
GL20.glDeleteProgram(pId);
// Select the VAO
GL30.glBindVertexArray(vaoId);
// Disable the VBO index from the VAO attributes list
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
// Delete the vertex VBO
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboId);
// Delete the index VBO
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboiId);
// Delete the VAO
GL30.glBindVertexArray(0);
GL30.glDeleteVertexArrays(vaoId);
this.exitOnGLError("destroyOpenGL");
Display.destroy();
}
private int loadShader(String filename, int type) {
StringBuilder shaderSource = new StringBuilder();
int shaderID = 0;
try {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line;
while ((line = reader.readLine()) != null) {
shaderSource.append(line).append("\n");
}
reader.close();
} catch (IOException e) {
System.err.println("Could not read file.");
e.printStackTrace();
System.exit(-1);
}
shaderID = GL20.glCreateShader(type);
GL20.glShaderSource(shaderID, shaderSource);
GL20.glCompileShader(shaderID);
if (GL20.glGetShader(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
System.err.println("Could not compile shader.");
System.exit(-1);
}
this.exitOnGLError("loadShader");
return shaderID;
}
private int loadPNGTexture(String filename, int textureUnit) {
ByteBuffer buf = null;
int tWidth = 0;
int tHeight = 0;
try {
// Open the PNG file as an InputStream
InputStream in = new FileInputStream(filename);
// Link the PNG decoder to this stream
PNGDecoder decoder = new PNGDecoder(in);
// Get the width and height of the texture
tWidth = decoder.getWidth();
tHeight = decoder.getHeight();
// Decode the PNG file in a ByteBuffer
buf = ByteBuffer.allocateDirect(
4 * decoder.getWidth() * decoder.getHeight());
decoder.decode(buf, decoder.getWidth() * 4, Format.RGBA);
buf.flip();
in.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
// Create a new texture object in memory and bind it
int texId = GL11.glGenTextures();
GL13.glActiveTexture(textureUnit);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
// All RGB bytes are aligned to each other and each component is 1 byte
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
// Upload the texture data and generate mip maps (for scaling)
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, tWidth, tHeight, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
// Setup the ST coordinate system
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
// Setup what to do when the texture has to be scaled
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR_MIPMAP_LINEAR);
this.exitOnGLError("loadPNGTexture");
return texId;
}
private float coTangent(float angle) {
return (float)(1f / Math.tan(angle));
}
private float degreesToRadians(float degrees) {
return degrees * (float)(PI / 180d);
}
private void exitOnGLError(String errorMessage) {
int errorValue = GL11.glGetError();
if (errorValue != GL11.GL_NO_ERROR) {
String errorString = GLU.gluErrorString(errorValue);
System.err.println("ERROR - " + errorMessage + ": " + errorString);
if (Display.isCreated()) Display.destroy();
System.exit(-1);
}
}
}
Here are the sections of the code where I added lines for render to texture:
Setting up the FBO
////////// THIS IS WHERE I ADDED CODE FOR THE FRAMEBUFFER STUFF //////////////
//render to texture attempt
fboID = GL30.glGenFramebuffers();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fboID);
colID = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, colID);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, WIDTH, HEIGHT, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
depID = GL30.glGenRenderbuffers();
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depID);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_DEPTH_COMPONENT, WIDTH, HEIGHT);
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, depID);
GL32.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, colID, 0);
drawBuffs = BufferUtils.createIntBuffer(1);
drawBuffs.put(0, GL30.GL_COLOR_ATTACHMENT0);
GL20.glDrawBuffers(drawBuffs);
if(GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) != GL30.GL_FRAMEBUFFER_COMPLETE){
System.out.println("Framebuffer not complete!");
}else{ System.out.println("Framebuffer is complete!");}
//////// END OF NEW CODE HERE, LOOK IN RENDER CYCLE FOR MORE NEW CODE ///////////
and here is the render code:
////////// THESE LINES ARE NEW, ADDED TO RENDER TO THE FRAME BUFFER /////////
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fboID);
GL11.glViewport(0, 0, WIDTH, HEIGHT);
///////// THIS IS THE ORIGINAL RENDER CYCLE CODE, I WANT THIS TO GO TO THE FBO ///////
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
GL20.glUseProgram(pId);
// Bind the texture
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texIds[textureSelector]);
// Bind to the VAO that has all the information about the vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
// Bind to the index VBO that has all the information about the order of the vertices
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
// Draw the vertices
GL11.glDrawElements(GL11.GL_TRIANGLES, indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
// Put everything back to default (deselect)
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL30.glBindVertexArray(0);
GL20.glUseProgram(0);
//////// NEW CODE, I WANT TO DRAW A FULL SCREEN QUAD WITH THE TEXTURE FROM THE FBO ///////
// draw the texture from the FBO
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GL11.glViewport(0, 0, WIDTH, HEIGHT);
GL11.glBegin(GL11.GL_QUADS);
GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f(0.0f, 0.0f, 0.0f);
GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f((float)WIDTH, 0.0f, 0.0f);
GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f((float)WIDTH, (float)HEIGHT, 0.0f);
GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f(0.0f, (float)HEIGHT, 0.0f);
GL11.glEnd();
///////// END OF NEW CODE ///////////
When I run this code I get a black rectangle for about a second, the console tells me that the frame buffer is complete, then I get an openGL error about an invalid operation in my render cycle and it crashes. I assume the issue is somewhere in my full screen quad code, but I don't know how to do it right. Do I need to create new shader program for the render to screen? Do I need to drop the glBegin(GL_QUADS) and use vertex buffers?
You must unbind the colID texture before binding the FBO for drawing (otherwise you'd possibly read from and write to it at the same time).