如何在 Kivy 中将纹理绑定到 3D 网格?
How to Bind a texture to a 3D mesh in Kivy?
我正在尝试采用 Kivy 的 3D Rotating Monkey Head 示例并对其应用纹理。
Kivy 的图形上下文“BindTexture()
方法似乎可以解决问题,但它似乎没有任何效果。我修改了示例以在 init( ) 方法。我还更改了包含纹理的着色器代码。
看来我已经成功地将纹理坐标传递给着色器了,因为当我只使用纹理坐标作为颜色值进行渲染时(在着色器代码末尾使用注释行),我得到了一个多-有色物体。但是,当我将这些纹理坐标放置在 texture2D() 调用中时,渲染的对象根本没有纹理。
根据 BindTexture 文档:
The BindTexture Instruction will bind a texture and enable
GL_TEXTURE_2D for subsequent drawing.
所以看起来应该很简单,但我显然遗漏了一些东西。
我是 Kivy 和 OpenGL 的新手。我真正苦苦挣扎的一件事是如何将值传递给 GL 着色器代码中的输入变量。例如,在我的着色器代码中,变量 "texture0" 用于相关纹理 - 但是,我不确定它是如何映射到我的纹理的。根据我见过的一些例子,比如 Kivy's multitexture 例子,似乎 "BindTexture()" 应该处理这个问题,但我有点不舒服,这对我来说就像黑魔法一样现在。
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()
着色器代码"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);
}
纹理对象和纹理采样器统一之间的绑定点是纹理单元。纹理对象绑定到纹理单元,纹理单元的索引设置为纹理采样器统一。我不知道使用 kivy 是如何工作的。采样器的初始值为0。如果纹理绑定到纹理单元0,它应该可以工作。纹理通过 BindTexture(source='mtexture2.png')
绑定到纹理单元,纹理单元通过 self.canvas['texture0'] = 0
.
分配给采样器统一
使用纹理单元 0 时似乎有问题。请改用纹理单元 1。
在setup_scene
绑定纹理,在update_glsl
:
设置采样器uniform的值
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()
另一个问题是 "monkey.obj" 没有纹理坐标(vt
条目)。因此所有顶点的纹理坐标属性默认为(0, 0)。
通过顶点着色器中的法向量模拟纹理坐标。例如:
void main (void) {
// [...]
tex_coord0 = v_normal.xz * 0.5 + 0.5;
}
我正在尝试采用 Kivy 的 3D Rotating Monkey Head 示例并对其应用纹理。
Kivy 的图形上下文“BindTexture()
方法似乎可以解决问题,但它似乎没有任何效果。我修改了示例以在 init( ) 方法。我还更改了包含纹理的着色器代码。
看来我已经成功地将纹理坐标传递给着色器了,因为当我只使用纹理坐标作为颜色值进行渲染时(在着色器代码末尾使用注释行),我得到了一个多-有色物体。但是,当我将这些纹理坐标放置在 texture2D() 调用中时,渲染的对象根本没有纹理。
根据 BindTexture 文档:
The BindTexture Instruction will bind a texture and enable GL_TEXTURE_2D for subsequent drawing.
所以看起来应该很简单,但我显然遗漏了一些东西。
我是 Kivy 和 OpenGL 的新手。我真正苦苦挣扎的一件事是如何将值传递给 GL 着色器代码中的输入变量。例如,在我的着色器代码中,变量 "texture0" 用于相关纹理 - 但是,我不确定它是如何映射到我的纹理的。根据我见过的一些例子,比如 Kivy's multitexture 例子,似乎 "BindTexture()" 应该处理这个问题,但我有点不舒服,这对我来说就像黑魔法一样现在。
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()
着色器代码"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);
}
纹理对象和纹理采样器统一之间的绑定点是纹理单元。纹理对象绑定到纹理单元,纹理单元的索引设置为纹理采样器统一。我不知道使用 kivy 是如何工作的。采样器的初始值为0。如果纹理绑定到纹理单元0,它应该可以工作。纹理通过 BindTexture(source='mtexture2.png')
绑定到纹理单元,纹理单元通过 self.canvas['texture0'] = 0
.
使用纹理单元 0 时似乎有问题。请改用纹理单元 1。
在setup_scene
绑定纹理,在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()
另一个问题是 "monkey.obj" 没有纹理坐标(vt
条目)。因此所有顶点的纹理坐标属性默认为(0, 0)。
通过顶点着色器中的法向量模拟纹理坐标。例如:
void main (void) {
// [...]
tex_coord0 = v_normal.xz * 0.5 + 0.5;
}