0
\$\begingroup\$

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.

What my code does: What my code does

What I'm trying to do: What I'm trying to do

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()
\$\endgroup\$
0
\$\begingroup\$

In hindsight, it's kind of amazing the previous code worked at all. I had removed the layout qualifiers in object.vs when I changed the version directive from 330 core. Since that syntax isn't supported in 150, I needed to specify locations with glGetAttribLocation.

This is some example C++ code that helped me find the solution.

And documentation for glGetAttribLocation. (I've always found Khronos' own documentation somewhat obtuse)

Obj.generate() in viewer.py:

# glGetAttribLocation needs a shader program to reference
self.compile_shader()

vertex_texture_coord = glGetAttribLocation(self.shader, b'vertex_texture_coord')
vertex_normal = glGetAttribLocation(self.shader, b'vertex_normal')
vertex_position = glGetAttribLocation(self.shader, b'vertex_position')

# make sure it's positive since return value of -1 is an error
if vertex_texture_coord >= 0:
    glVertexAttribPointer(vertex_texture_coord, 2, GL_FLOAT, GL_FALSE, 32, None)
    glEnableVertexAttribArray(vertex_texture_coord)
if vertex_normal >= 0:
    glVertexAttribPointer(vertex_normal, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(8))
    glEnableVertexAttribArray(vertex_normal)
if vertex_position >= 0:
    glVertexAttribPointer(vertex_position, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(20))
    glEnableVertexAttribArray(vertex_position)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.