I'm trying to take Kivy's 3D Rotating Monkey Head example and apply a texture to it.
It seems that Kivy's graphics Context "BindTexture()
method should do the trick, but it seems to have no effect. I modified the example to include a call to "BindTexture()" at in the init() method. I also changed the shader code include textures.
It seems that I've managed to pass texture coordinates to the shader just fine, because when I render using only the texture coordinates as color values (using comment line near end of shader code), I get a multi-colored object. However, when I place those texture coordinates inside of a texture2D() call, the object is rendered with no texture at all.
According to the BindTexture docs:
The BindTexture Instruction will bind a texture and enable GL_TEXTURE_2D for subsequent drawing.
So it seems like that should be pretty straightforward, but I'm clearly missing something.
I'm new to Kivy and OpenGL. One of the things I'm really struggling with is how to pass values to the input variables in a GL shader code. For example, in my shader code, the variable "texture0" is used for the texture in question - however, I'm uncertain how that ever gets mapped to my texture. Based on some of the examples I've seen, like Kivy's multitexture example, it seems that "BindTexture()" should take care of that, but I'm a little uncomfortable that it all seems like black magic to me right now.
main.py:
from kivy.clock import Clock
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.graphics import RenderContext, Color, Rectangle, BindTexture
class Renderer(Widget):
def __init__(self, **kwargs):
self.canvas = RenderContext(compute_normal_mat=True)
# self.canvas.shader.source = resource_find('default.glsl')
self.canvas.shader.source = resource_find('simple.glsl')
self.scene = ObjFile(resource_find("monkey.obj"))
BindTexture(source='mtexture2.png')
super(Renderer, self).__init__(**kwargs)
with self.canvas:
self.cb = Callback(self.setup_gl_context)
PushMatrix()
self.setup_scene()
PopMatrix()
self.cb = Callback(self.reset_gl_context)
BindTexture(source='mtexture2.png')
self.canvas['texture0'] = 0
Clock.schedule_interval(self.update_glsl, 1 / 60.)
def setup_gl_context(self, *args):
glEnable(GL_DEPTH_TEST)
def reset_gl_context(self, *args):
glDisable(GL_DEPTH_TEST)
def update_glsl(self, delta):
asp = self.width / float(self.height)
proj = Matrix().view_clip(-asp, asp, -1, 1, 1, 100, 1)
# Color(1,0,0)
self.canvas['projection_mat'] = proj
self.canvas['diffuse_light'] = (1.0, 0.0, 0.8)
self.canvas['ambient_light'] = (0.1, 0.1, 0.1)
self.rot.angle += delta * 100
def setup_scene(self):
Color(1, 1, 1)
PushMatrix()
Translate(0, -2.5, -8)
self.rot = Rotate(1, 0, 1, 0)
UpdateNormalMatrix()
for m in list(self.scene.objects.values()):
self.mesh = Mesh(
vertices=m.vertices,
indices=m.indices,
fmt=m.vertex_format,
mode='triangles',
)
PopMatrix()
class RendererApp(App):
def build(self):
return Renderer()
if __name__ == "__main__":
RendererApp().run()
Shader code "simple.glsl":
/* simple.glsl
simple diffuse lighting based on laberts cosine law; see e.g.:
http://en.wikipedia.org/wiki/Lambertian_reflectance
http://en.wikipedia.org/wiki/Lambert%27s_cosine_law
*/
---VERTEX SHADER-------------------------------------------------------
#ifdef GL_ES
precision highp float;
#endif
/* Vertex attributes/inputs, defined in MeshData Object */
attribute vec3 v_pos;
attribute vec3 v_normal;
attribute vec2 v_tc0;
/* Outputs to the fragment shader */
varying vec2 tex_coord0;
varying vec4 normal_vec;
varying vec4 vertex_pos;
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
void main (void) {
//compute vertex position in eye_space and normalize normal vector
vec4 pos = modelview_mat * vec4(v_pos,1.0);
vertex_pos = pos;
normal_vec = vec4(v_normal,0.0);
gl_Position = projection_mat * pos;
tex_coord0 = v_tc0;
}
---FRAGMENT SHADER-----------------------------------------------------
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from Vertex Shader */
varying vec4 normal_vec;
varying vec4 vertex_pos;
varying vec2 tex_coord0;
uniform sampler2D texture0;
uniform mat4 normal_mat;
void main (void){
//correct normal, and compute light vector (assume light at the eye)
vec4 v_normal = normalize( normal_mat * normal_vec ) ;
vec4 v_light = normalize( vec4(0,0,0,1) - vertex_pos );
//reflectance based on lamberts law of cosine
float theta = clamp(dot(v_normal, v_light), 0.0, 1.0);
//gl_FragColor = vec4(theta, theta, theta, 1.0)*vec4(tex_coord0, 1, 1);
gl_FragColor = vec4(theta, theta, theta, 1.0)*texture2D(texture0, tex_coord0);
}
The binding point between a texture object and the texture sampler uniform is the texture unit. The texture object is bound to a texture unit and the index of the texture unit is set to the texture sampler uniform. I have no idea how that works using kivy. The initial value of the sampler is 0. If the texture is bound to texture unit 0, it should work. The texture is bound to the texture unit by BindTexture(source='mtexture2.png')
and the texture unit is assigned to the sampler uniform by self.canvas['texture0'] = 0
.
There seems to be an issue when you use texture unit 0. Use texture unit 1 instead.
Bind the texture in setup_scene
and set the value to the sampler uniform in update_glsl
:
class Renderer(Widget):
# [...]
def update_glsl(self, delta):
asp = self.width / float(self.height)
proj = Matrix().view_clip(-asp, asp, -1, 1, 1, 100, 1)
self.canvas['texture0'] = 1 # <------------------------------
self.canvas['projection_mat'] = proj
self.canvas['diffuse_light'] = (1.0, 0.0, 0.8)
self.canvas['ambient_light'] = (0.1, 0.1, 0.1)
self.rot.angle += delta * 100
def setup_scene(self):
BindTexture(source='mtexture2.png', index=1) # <-------------
Color(1, 1, 1)
PushMatrix()
Translate(0, -2.5, -8)
self.rot = Rotate(1, 0, 1, 0)
UpdateNormalMatrix()
for m in list(self.scene.objects.values()):
self.mesh = Mesh(
vertices=m.vertices,
indices=m.indices,
fmt=m.vertex_format,
mode='triangles',
)
PopMatrix()
A further issue ism that "monkey.obj" does not have texture coordinates (vt
entries). So the texture coordinates attribute is (0, 0) by default for all vertices.
Emulate the texture coordinates by the normal vector in the vertex shader. e.g:
void main (void) {
// [...]
tex_coord0 = v_normal.xz * 0.5 + 0.5;
}