I'm trying to apply a square texture to a trapezoid-like shape in OpenGL but I get some distortion. I've been reading a lot on possible solutions and the one that seems most convenient requires modifying the "q" texture coordinates. This is done using GlTexCoord functions in the solution; however, I'm using vertex buffers and I don't know how I can use them to change this coordinate this way. The texture init in GLSL takes a vec2; so I have no idea how I would pass anything but two-dimensional texture coordinates to it.
main.c
//C libs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
//GL libs (Need to have set up to run this)
#include <glad/glad.h>
#include <GLFW/glfw3.h>
//Libs in folder
#include "shader.h"
#include "image.h"
//Window consts
#define WINDOW_HEIGHT 500
#define WINDOW_WIDTH 500
#define WINDOW_NAME "ForStackOverflow"
//Shader consts
#define VERTEX_SHADER_FILE "vertex_shader.glsl"
#define FRAGMENT_SHADER_FILE "fragment_shader.glsl"
//Vertex constants
#define POSITION_ATTRIBUTE_LOC 0
#define TEXTURE_COORD_ATTRIBUTE_LOC 1
#define POSITION_SIZE 2
#define TEXTURE_COORD_SIZE 2
#define VERTEX_SIZE (POSITION_SIZE + TEXTURE_COORD_SIZE) //Amount of floats per vertex
#define POSITION_OFFSET 0
#define TEXTURE_COORD_OFFSET (POSITION_SIZE * sizeof(float))
#define STRIDE (sizeof(float) * VERTEX_SIZE)
//Functions
static void framebuffer_size_callback(GLFWwindow*, int, int);
static unsigned int load_bmp_texture(const char* name);
int main()
{
printf("Running!\n");
//*GLFW
if (!glfwInit())
{
printf("GLFW init fail\n");
return -1;
}
//3.3 core
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//*Window object
GLFWwindow* window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_NAME, NULL, NULL);
if (window == NULL)
{
printf("GLFW window fail\n");
return -1;
}
glfwMakeContextCurrent(window);
//*GLAD
if (!gladLoadGLLoader((GLADloadproc) &glfwGetProcAddress))
{
printf("GLAD init fail");
glfwTerminate();
return -1;
}
//*Window
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
glfwSetFramebufferSizeCallback(window, &framebuffer_size_callback);
//*Shaders
ShaderProgram shader_program;
if (!shader_program_init(&shader_program, VERTEX_SHADER_FILE, FRAGMENT_SHADER_FILE)) {
glfwTerminate();
return -1;
}
//*Triangle rendering
//Vertices
float tri_vertices[4 * VERTEX_SIZE] = { //FORM A TRAPEZOID
//Position //Texture coordinates
-0.5f, 0.5f, 0.0f, 1.0f, //Top-left
-0.5f, -0.5f, 0.0f, 0.0f, //Bottom-left
0.5f, 0.75f, 1.0f, 1.0f, //Top-right
0.5f, -0.75f, 1.0f, 0.0f //Bottom-right
};
//Indices
unsigned int tri_indices[6] = {
2, 0, 1, //Top-right, top-left, bottom-left
2, 3, 1 //Top-right, bottom-right, bottom-left
};
//VAO
unsigned int tri_vao;
glGenVertexArrays(1, &tri_vao);
glBindVertexArray(tri_vao);
//VBO
unsigned int tri_vbo;
glGenBuffers(1, &tri_vbo);
glBindBuffer(GL_ARRAY_BUFFER, tri_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(tri_vertices), tri_vertices, GL_STATIC_DRAW);
//EBO
unsigned int tri_ebo;
glGenBuffers(1, &tri_ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tri_ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(tri_indices), tri_indices, GL_STATIC_DRAW);
//Config
//Position
glVertexAttribPointer(POSITION_ATTRIBUTE_LOC, POSITION_SIZE, GL_FLOAT, GL_FALSE, STRIDE, (void*) POSITION_OFFSET);
glEnableVertexAttribArray(POSITION_ATTRIBUTE_LOC);
//Texture coordinates
glVertexAttribPointer(TEXTURE_COORD_ATTRIBUTE_LOC, TEXTURE_COORD_SIZE, GL_FLOAT, GL_FALSE, STRIDE, (void*) TEXTURE_COORD_OFFSET);
glEnableVertexAttribArray(TEXTURE_COORD_ATTRIBUTE_LOC);
//*Textures
unsigned int brick_tex = load_bmp_texture("purple_bricks.bmp");
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, brick_tex);
//Attaching to uniform
glUseProgram(shader_program);
glUniform1i(glGetUniformLocation(shader_program, "brick"), 0);
//*Rendering setup
//Shader
glUseProgram(shader_program);
//*Main loop
while (!glfwWindowShouldClose(window))
{
//*Blittering
//Background
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
//Triangles
glBindVertexArray(tri_vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tri_ebo);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*) 0);
//*Buffer, events
glfwSwapBuffers(window);
glfwPollEvents();
}
//*End program
glDeleteVertexArrays(1, &tri_vao);
glDeleteBuffers(1, &tri_vbo);
glDeleteBuffers(1, &tri_ebo);
glfwTerminate();
return 0;
}
//Centers the OpenGL part of the window and keeps the same width and height
static void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport((width - WINDOW_WIDTH) / 2, (height - WINDOW_HEIGHT) / 2, WINDOW_WIDTH, WINDOW_HEIGHT);
}
//Loads and sets up a BMP texture
static unsigned int load_bmp_texture(const char* name) {
//Loading into array
RGBImage image;
read_bmp(name, &image);
//Generating texture in GL
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
//Setting mapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //X
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); //Y
//Setting filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//Setting texture and mipmap
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image.data);
glGenerateMipmap(GL_TEXTURE_2D);
//Freeing image array
free_RGBImage(image);
return texture;
}
image.h
//Code for loading a bmp file as an array
//Definitely not part of the problem
//24 bit bmps
//C libs
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
//Prevent struct packing
#pragma pack(1)
typedef struct RGB {
unsigned char R;
unsigned char G;
unsigned char B;
} RGB;
typedef struct RGBImage {
int width;
int height;
RGB* data;
} RGBImage;
typedef struct BMPFileHeader {
char name[2];
uint32_t size;
uint32_t garbage;
uint32_t image_offset;
} BMPFileHeader;
typedef struct BMPInfoHeader {
uint32_t header_size;
uint32_t width;
uint32_t height;
uint16_t color_planes;
uint16_t bits_per_pixel;
uint32_t compression;
uint32_t image_size;
} BMPInfoHeader;
void free_RGBImage(RGBImage image) {
free(image.data);
}
bool read_bmp(const char* file_name, RGBImage* image) {
FILE* fp = fopen(file_name, "rb");
if (fp == NULL) {
printf("Couldn't open %s\n", file_name);
return false;
}
BMPFileHeader file_header;
fread(file_header.name, sizeof(BMPFileHeader), 1, fp);
if ((file_header.name[0] != 'B') || (file_header.name[1] != 'M')) {
fclose(fp);
return false;
}
BMPInfoHeader info_header;
fread(&info_header, sizeof(BMPInfoHeader), 1, fp);
if ((info_header.header_size != 40) || (info_header.compression != 0) || (info_header.bits_per_pixel != 24)) {
fclose(fp);
return false;
}
fseek(fp, file_header.image_offset, SEEK_SET);
image->width = info_header.width;
image->height = info_header.height;
image->data = (RGB*) malloc(image->height * image->width * sizeof(RGB));
for (int i = 0; i < image->height; i++) {
fread(&image->data[i * image->width], sizeof(RGB), image->width, fp);
}
int R;
for (int i = 0; i < image->height * image->width; i++) {
R = image->data[i].R;
image->data[i].R = image->data[i].B;
image->data[i].B = R;
}
fclose(fp);
return true;
}
shader.h
//Code for compiling and linking a shader
//Definitely not part of the problem
//C libs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
//GL libs
#include <glad/glad.h>
#include <GLFW/glfw3.h>
//Consts
#define INFO_LOG_SIZE 512
static bool _glad_is_init();
static bool _glfw_is_init();
typedef unsigned int ShaderProgram;
bool shader_program_init(ShaderProgram* shader_program, char* vertex_shader_file_name, char* fragment_shader_file_name)
{
if (!_glfw_is_init()) {
printf("Shader: glfw uninitialized\n");
return 0;
}
else if (!_glad_is_init()) {
printf("Shader: glad uninitialized\n");
return 0;
}
long int file_size;
size_t new_source_length;
FILE* fp;
//*Reading vertex shader file
//Open
fp = fopen(vertex_shader_file_name, "r");
if (fp == NULL) {
printf("Couldn't open vertex shader file\n");
return 0;
}
//Find length for buffer
fseek(fp, 0L, SEEK_END);
file_size = ftell(fp);
if (file_size == -1) {
printf("Couldn't seek end of file\n");
return 0;
}
rewind(fp);
char vertex_shader_source[(file_size + 1) * sizeof(char)];
//Read
new_source_length = fread(vertex_shader_source, sizeof(char), file_size, fp);
if (ferror(fp) != 0) {
printf("Error when reading file\n");
return 0;
}
//Add string termination
vertex_shader_source[new_source_length] = '\0';
//Close
fclose(fp);
//*Reading fragment shader
//Open
fp = fopen(fragment_shader_file_name, "r");
if (fp == NULL) {
printf("Couldn't open fragment shader file\n");
return 0;
}
//Find length for buffer
fseek(fp, 0L, SEEK_END);
file_size = ftell(fp);
if (file_size == -1) {
printf("Couldn't seek end of file\n");
return 0;
}
rewind(fp);
char fragment_shader_source[(file_size + 1) * sizeof(char)];
//Read
new_source_length = fread(fragment_shader_source, sizeof(char), file_size, fp);
if (ferror(fp) != 0) {
printf("Error reading file\n");
return 0;
}
//Add string termination
fragment_shader_source[new_source_length] = '\0';
//Close
fclose(fp);
//*Compiling
//For error checking
int success;
char infolog[INFO_LOG_SIZE];
const char* vertex_shader_code = vertex_shader_source; //vertex_shader_source is of type char foo[n], a VLA.
const char* fragment_shader_code = fragment_shader_source;
//Vertex
unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_code, NULL);
glCompileShader(vertex_shader);
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertex_shader, INFO_LOG_SIZE, NULL, infolog);
printf("Vertex shader compile fail: \n%s\n", infolog);
return 0;
}
//Fragment
unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_code, NULL);
glCompileShader(fragment_shader);
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragment_shader, INFO_LOG_SIZE, NULL, infolog);
printf("Fragment shader compile fail: \n%s\n", infolog);
return 0;
}
//Program
*shader_program = glCreateProgram();
glAttachShader(*shader_program, vertex_shader);
glAttachShader(*shader_program, fragment_shader);
glLinkProgram(*shader_program);
glGetProgramiv(*shader_program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(*shader_program, INFO_LOG_SIZE, NULL, infolog);
printf("Shader program compile fail: \n%s\n", infolog);
return 0;
}
//Deleting
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return 1;
}
bool _glfw_is_init() {
if (!glfwInit())
{
return false;
}
return true;
}
bool _glad_is_init() {
if (!gladLoadGLLoader((GLADloadproc) &glfwGetProcAddress))
{
return false;
}
return true;
}
vertex_shader.glsl
#version 330 core
layout (location = 0) in vec2 vertex_position;
layout (location = 1) in vec2 vertex_texture_coordinate;
out vec2 texture_coordinate;
void main()
{
gl_Position = vec4(vertex_position, 1.0, 1.0);
texture_coordinate = vertex_texture_coordinate;
};
fragment_shader.glsl
#version 330 core
in vec4 color;
in vec2 texture_coordinate;
out vec4 FragColor;
uniform sampler2D brick;
void main()
{
FragColor = texture(brick, texture_coordinate);
};
Texture used
Result of program
Edit: for those reading in the future, this link helped a lot with implementing the method described in the answer below: https://www.cs.cmu.edu/~16385/s17/Slides/10.2_2D_Alignment__DLT.pdf