I'm trying to map a texture to a square face in OpenGL 3.2, but the texture gets tiled in the upper right corner. I've tried changing the uv coordinates, vertices and shaders, but nothing seems to have the desired effect. Sorry if it's a lot of code, I've done my best to extract only the relevant parts. Please explain your answer and include any relevant questions or documentation, so I can understand why this is happening.
object.vs
#version 150 core
precision mediump float;
in vec2 vertex_texture_coord;
in vec3 vertex_normal;
in vec3 vertex_position;
out vec3 position;
out vec3 normal;
out vec4 color;
out vec2 texture_coord;
uniform mat4 transform;
uniform mat4 model;
uniform vec4 self_color;
void main()
{
gl_Position = transform * vec4(vertex_position, 1.0);
position = vec3(model * vec4(vertex_position, 1.0));
normal = normalize(mat3(transpose(inverse(model))) * vertex_normal);
color = self_color;
texture_coord = vertex_texture_coord;
}
object.fs
#version 150 core
precision mediump float;
out vec4 fragment_color;
in vec3 position;
in vec3 normal;
in vec4 color;
in vec2 texture_coord;
uniform vec3 light_color;
uniform vec3 light_position;
uniform sampler2D textureObj;
uniform int texture_mode;
void main()
{
// ambient
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * light_color;
vec3 light_direction = normalize(light_position - position);
float diff = max(dot(normal, light_direction), 0.0);
vec3 diffuse = diff * light_color;
vec4 result = vec4(ambient + diffuse, 1.0) * color;
if (texture_mode == 0) {
fragment_color = result;
} else {
vec4 tex_color = texture(textureObj, texture_coord);
if(tex_color.a == 0.0)
discard;
fragment_color = tex_color;
}
}
square.obj
# Blender v2.80 (sub 75) OBJ File: 'square.blend'
# www.blender.org
mtllib square.mtl
o Cube
v 1.000000 1.000000 0.000000
v 1.000000 -1.000000 0.000000
v -1.000000 1.000000 0.000000
v -1.000000 -1.000000 0.000000
vt 0 0
vt 0 1
vt 1 1
vt 1 0
vn 0.0000 0.0000 1.0000
usemtl Material
f 4/1/1 3/2/1 1/3/1 2/4/1
viewer.py
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GL import shaders
import glm
from pywavefront import Wavefront
import numpy as np
from PIL import Image
from os.path import dirname, realpath, join
from ast import literal_eval
from screeninfo import get_monitors
try:
from ctypes import windll
"""prevents stretching on windows"""
windll.user32.SetProcessDPIAware()
except:
pass
screen = get_monitors()[0]
dir_path = dirname(realpath(__file__))
uv_test_png = join(dir_path, 'uv_test.png')
square_obj = join(dir_path, 'square.obj')
with open(join(dir_path, 'object.fs'), 'r') as shader_file:
object_fs = shader_file.read()
shader_file.close()
with open(join(dir_path, 'object.vs'), 'r') as shader_file:
object_vs = shader_file.read()
shader_file.close()
def dedup_and_index(sequence):
sequence_ref = {}
new_sequence = []
indices = [i for i in range(len(sequence))]
for i, vertex in enumerate(list(sequence)):
strung = str(list(vertex))
if strung in sequence_ref:
indices[i] = sequence_ref[strung]
else:
indices[i] = len(sequence_ref)
sequence_ref[strung] = indices[i]
for i in sequence_ref.keys():
new_sequence.append(literal_eval(i))
return indices, new_sequence
class Obj:
def __init__(self, scene, v_shader, f_shader, texture=None):
"""Loads a Wavefront OBJ file. """
self.vertex_info = [] # unique combinations of position, normal and color
self.model = glm.mat4()
self.position = np.array([0, 0, 0], np.float32)
self.light = self
self.light.color = (0, 0, 0)
self.light.position = (0, 0, 0)
self.shader = None
self.vertex_shader = v_shader
self.fragment_shader = f_shader
self.texture_mode = 0
self.texture = texture
self.translated = glm.mat4()
self.rotated = glm.mat4()
self.scaled = glm.mat4()
scene = Wavefront(scene)
scene.parse()
for material in scene.materials.values():
vertex_format = 0
for i in material.vertex_format:
if i.isdigit():
vertex_format += int(i)
vertices = np.array(material.vertices, np.float32).reshape(len(material.vertices) // vertex_format, vertex_format)
indices, vertex_info = dedup_and_index(vertices)
self.color = material.diffuse
self.indices = np.array(indices, np.int32)
self.vertex_info = np.array(vertex_info, np.float32)
def generate(self):
self.VAO, self.VBO, self.EBO = GLuint(), GLuint(), GLuint()
self.VAO = glGenVertexArrays(1)
glBindVertexArray(self.VAO)
self.VBO = glGenBuffers(1)
self.EBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.VBO)
glBufferData(GL_ARRAY_BUFFER, self.vertex_info, GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.EBO)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.indices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 32, None)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(8))
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(20))
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glEnableVertexAttribArray(2)
glBindVertexArray(0)
self.set_texture(self.texture_mode)
def compile_shader(self):
vertex_shader = shaders.compileShader(self.vertex_shader, GL_VERTEX_SHADER)
fragment_shader = shaders.compileShader(self.fragment_shader, GL_FRAGMENT_SHADER)
self.shader = shaders.compileProgram(vertex_shader, fragment_shader)
self.uniforms = {}
for name in ('model', 'transform', 'self_color', 'light_color', 'light_position', 'texture_mode'):
self.uniforms[name] = glGetUniformLocation(self.shader, name)
def use_shader(self):
if self.shader == None:
self.compile_shader()
shaders.glUseProgram(self.shader)
glUniformMatrix4fv(self.uniforms['model'], 1, False, np.array(self.model))
glUniformMatrix4fv(self.uniforms['transform'], 1, False, np.array(self.model))
glUniform4f(self.uniforms['self_color'], *self.color)
glUniform3f(self.uniforms['light_color'], *self.light.color[:3])
glUniform3f(self.uniforms['light_position'], *self.light.position[:3])
glUniform1i(self.uniforms['texture_mode'], self.texture_mode)
def set_texture(self, setting):
self.texture_mode = setting
if self.texture_mode:
self.apply_texture()
def apply_texture(self):
if isinstance(self.texture, Image.Image):
texture = self.texture
else:
texture = Image.open(self.texture)
texture_data = np.flipud(np.asarray(texture)).tobytes()
self.tex_id = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glTexImage2D(GL_TEXTURE_2D, 0, 4, *texture.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_data)
glGenerateMipmap(GL_TEXTURE_2D)
def draw(self):
self.use_shader()
glBindVertexArray(self.VAO)
if self.texture_mode:
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glDrawElements(GL_TRIANGLES, len(self.indices), GL_UNSIGNED_INT, None)
def main():
pygame.init()
display = (screen.width, screen.height)
pygame.display.set_mode(display, FULLSCREEN|DOUBLEBUF|OPENGL)
glEnable(GL_DEPTH_TEST)
# glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
square = Obj(square_obj, object_vs, object_fs, uv_test_png)
square.generate()
square.set_texture(1)
while True:
for event in pygame.event.get():
if (event.type == KEYDOWN and event.key == K_ESCAPE) or event.type == QUIT:
raise SystemExit
square.draw()
pygame.display.flip()
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
pygame.time.wait(10)
if __name__ == '__main__':
main()