【问题标题】:Kivy: How to render 3D model in a given layoutKivy:如何在给定布局中渲染 3D 模型
【发布时间】:2020-04-27 02:00:07
【问题描述】:

我正在尝试制作一个简单的 3D 模型查看器。我从 3Drendering 示例开始,这是 kivy 示例之一。以下是我试过的测试代码。(Python 3.7, Kivy 1.11.1)

Python 代码:test_renderer.py

import kivy
kivy.require('1.11.1')

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from renderer import Renderer
from kivy.lang import Builder
#from kivy.properties import ObjectProperty
#from kivy.uix.label import Label
#from kivy.uix.button import Button

Builder.load_file('test_win.kv')


class Test_Win(BoxLayout):
    pass

class Test_Renderer(App):
    def build(self):
        self.title = 'Test Renderer'
        return Test_Win()


if __name__ == '__main__':
    Test_Renderer().run()

和kv文件:test_win.kv

# File name: test_win.kv
#:kivy 1.11.1

<Test_Win>:
    BoxLayout:
        orientation: 'horizontal'
        BoxLayout:
            orientation: 'vertical'
            Label:
                text: 'Test Renderer'
            Button:
                text: 'Click'
        Splitter:
            Renderer:

3Drendering 示例中的其他文件(renderer.py、objloader.py、simple.glsl、monkey.obj)(main.py、objloader.py、simple.glsl、monkey.obj)除了名称更改外相同main.py到renderer.py,都和上面两个文件在同一个文件夹里。

这些文件可以在以下位置找到 https://kivy.org/doc/stable/examples/gen__3Drendering__main__py.html#

或在https://github.com/kivy/kivy/tree/master/examples/3Drendering 使用 3D 文件。

生成的屏幕是as follows

旋转头显示在布局和小部件的顶部。看起来显示画布是根的画布,而不是我想要的分割器布局。

我刚开始 kivy,不知道从哪里开始。谁能帮我找到解决办法?

【问题讨论】:

  • 一个重要的提示是Canvas(误导性名称)只是一个图形指令列表。您有责任调整三角形的位置和比例以将它们放置在您想要的位置。
  • 感谢您的评论。是的,我明白这一点。但与其他小部件(包括布局)不同,Renderer 类的行为独立于布局。例如,当我将 Renderer: 替换为 Label: 时,它遵循我设置的布局。不知道如何将 Renderer 类的画布合并到布局中。
  • 3D 渲染是使用CanvasWidget 完成的。它可以是使用任何WidgetCanvas 的圆顶。事实上,您在App 中看到的所有内容都是通过Canvas 完成的。 Widgets 出现在您期望的位置的唯一原因是,设计师精心设计了每个 WidgetCanvas 指令以实现这一目标。尝试在任何Canvas 上使用Rectangle,并修改其possize。您将看到 Rectangle pos 仅与 Widget pos 相关,如果您这样做的话。
  • 我上述声明的一个例外是RelativeLayout,其中所有位置都相对于Layout 的位置。因此,您可能会考虑使用RelativeLayout 作为渲染器的基础。但RelativeLayout 的绘图Canvas 仍然不限于它的区域。

标签: python canvas layout kivy rendering


【解决方案1】:

我想出了一个技巧,几乎可以做你想做的事。因为涉及投影矩阵,所以有点复杂。也许更精通 OGL 的人可以做得更好。这是我的技巧:

首先,我想要 3D 对象的范围,所以我修改了 ObjFile 类的 __init__() 方法:

def __init__(self, filename, swapyz=False):
    """Loads a Wavefront OBJ file. """
    self.objects = {}
    self.vertices = []
    self.normals = []
    self.texcoords = []
    self.faces = []
    maxf = sys.float_info.max

    # initialize the minimums and maximums
    self.mins = [maxf, maxf, maxf]
    self.maxs = [-maxf, -maxf, -maxf]

    self._current_object = None

    material = None
    for line in open(filename, "r"):
        if line.startswith('#'):
            continue
        if line.startswith('s'):
            continue
        values = line.split()
        if not values:
            continue
        if values[0] == 'o':
            self.finish_object()
            self._current_object = values[1]
        # elif values[0] == 'mtllib':
        #    self.mtl = MTL(values[1])
        # elif values[0] in ('usemtl', 'usemat'):
        #    material = values[1]
        if values[0] == 'v':
            v = list(map(float, values[1:4]))
            if swapyz:
                v = v[0], v[2], v[1]

            # keep the vertex minimums and maximums
            for i in range(3):
                self.mins[i] = min(self.mins[i], v[i])
                self.maxs[i] = max(self.maxs[i], v[i])


            self.vertices.append(v)
        elif values[0] == 'vn':
            v = list(map(float, values[1:4]))
            if swapyz:
                v = v[0], v[2], v[1]
            self.normals.append(v)
        elif values[0] == 'vt':
            self.texcoords.append(map(float, values[1:3]))
        elif values[0] == 'f':
            face = []
            texcoords = []
            norms = []
            for v in values[1:]:
                w = v.split('/')
                face.append(int(w[0]))
                if len(w) >= 2 and len(w[1]) > 0:
                    texcoords.append(int(w[1]))
                else:
                    texcoords.append(-1)
                if len(w) >= 3 and len(w[2]) > 0:
                    norms.append(int(w[2]))
                else:
                    norms.append(-1)
            self.faces.append((face, norms, texcoords, material))
    self.finish_object()
    print('max :', self.maxs, ', mins:', self.mins)

而在Renderer类中,我修改了两个方法:

def update_glsl(self, delta):
    # Calculate new left edge for the clip matrix
    old_scene_width = self.scene.maxs[0] - self.scene.mins[0]
    app_width = App.get_running_app().root.width
    renderer_width = self.width
    ratio = app_width / renderer_width
    new_scene_width = old_scene_width * ratio
    left = self.scene.mins[0] - (new_scene_width - old_scene_width)

    # calculate new top and bottom of clip frustum that maintains monkey head aspect ratio
    scene_center_vertical = (self.scene.mins[1] + self.scene.maxs[1]) /  2.0
    old_scene_height = self.scene.maxs[1] - self.scene.mins[1]
    new_scene_height = old_scene_height * ratio
    bottom = scene_center_vertical - new_scene_height / 2.0
    top = scene_center_vertical + new_scene_height / 2.0

    # create new clip matrix
    proj = Matrix().view_clip(left, self.scene.maxs[0],
                              bottom, top,
                              1, self.scene.maxs[2] - self.scene.mins[2] - self.translate.z,
                              1)

    self.canvas['projection_mat'] = proj
    self.canvas['diffuse_light'] = (1.0, 1.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, 1)
    PushMatrix()

    # save a reference to the Translate matrix
    self.translate = Translate(0, 0, -3)

    self.rot = Rotate(1, 0, 1, 0)
    m = list(self.scene.objects.values())[0]
    UpdateNormalMatrix()
    self.mesh = Mesh(
        vertices=m.vertices,
        indices=m.indices,
        fmt=m.vertex_format,
        mode='triangles',
    )
    PopMatrix()

想法是调整裁剪矩阵,使猴头位于RendererWidget的中心。为此,我修改了剪辑矩阵的left 参数,使猴头不再位于剪辑截头体的中心。我还重新计算了截锥体的topbottom 参数,以避免不均匀缩放(失真)。

【讨论】:

  • 这是一个很好的答案!我试过你的代码,它按预期工作。我不知道该怎么感谢你才足够。这是我自己制作更完整的 3D 查看器的一个很好的起点。我真的很感激。
猜你喜欢
  • 2016-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-05
  • 2013-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多