游戏开发入门指引

推荐课程

底层向

笔记

  • 旋转是特殊的剪切

blender Lesson 3 变换矩阵 模拟

L3.blend.zip,记得去掉.zip后打开
image

ui_L3.py备份,建议下载源文件,有几何节点
# type: ignore
import bpy
import math
bl_info = {
    "name": "Games101 Visualization",
    "author": "AClon",
    "description": "Games101教程可视化",
    "blender": (2, 80, 0),
    "version": (0, 0, 2),
    "location": "View3D > Sidebar > Item",
    "warning": "",
    "category": "Item"
}

def Print(*args):
    if False:
        print(*args)

def Unit_M(length=4):
    return [[1 if i == j else 0 for j in range(length)] for i in range(length)]

def get_col(begin=0):
    # Print(f'get_col:{begin}')
    obj = bpy.context.object
    if not obj:
        return None
    NG=bpy.data.node_groups["Transform"]
    input=NG.nodes["Input Matrix"].inputs
    switch = NG.nodes["Switch"].inputs[0]
    if switch.default_value:
        newV = [None] * 4
        for i in range(len(newV)):
            newV[i] = bpy.data.node_groups["Transform"].nodes["Input Matrix"].inputs[i+begin*4].default_value
        return newV
    else:
        newV = [ i[begin] for i in obj.matrix_world ]
        # for i in range(len(newV)):
        #     input[i+begin*4].default_value = newV[i]   # update only 4
        return newV

def set_col(self,newV,begin=0):
    Print(f'set_col:{begin}')
    NG=bpy.data.node_groups["Transform"]
    input=NG.nodes["Input Matrix"].inputs
    switch = NG.nodes["Switch"].inputs[0]
    if self.shear:
        for i in range(len(newV)):
            input[i+begin*4].default_value = newV[i]   # update only 4
    else:
        self.shear = True


def update_matrix(input):
    Print('update_matrix')
    obj = bpy.context.object
    if not obj:
        return
    NG=bpy.data.node_groups["Transform"]
    switch = NG.nodes["Switch"].inputs[0]
    if switch.default_value:
        matrix = obj.matrix_world.copy()
        Print(f'switch:{switch.default_value}\nmatrix=\n{matrix}\n{obj.matrix_world}\n')
        obj.matrix_world = Unit_M()    # 复原
        Print(f'switch:{switch.default_value}\nmatrix2:\n{matrix}\n{obj.matrix_world}\n')
        for i in range(len(input)):
            input[i].default_value = matrix[i%4][i//4]
        
    else:
        matrix = Unit_M()
        # 找回
        for i in range(len(input)):
            matrix[i//4][i%4] = input[i].default_value
        obj.matrix_world = matrix.copy()
        Print(f'switch:{switch.default_value}\nmatrix:\n{matrix}\n')

def update_switch(self, context):
    Print('update_switch')
    NG=bpy.data.node_groups["Transform"]
    input=NG.nodes["Input Matrix"].inputs
    switch = NG.nodes["Switch"].inputs[0]

    switch.default_value = self.shear
    update_matrix(input)
    
    obj = bpy.context.object
    # 检测当前有没有几何节点修改器,没有则添加
    if not obj.modifiers:
        modifier = obj.modifiers.new(type='NODES', name='GN')
        # 设置几何节点修改器的节点树为 'Transform'
        modifier.node_group = bpy.data.node_groups.get('Transform')
    

class Get:
    def c1(self):
        return get_col(0)
    def c2(self):
        return get_col(1)
    def c3(self):
        return get_col(2)
    def c4(self):
        return get_col(3)
    
class Set:
    def c1(self, value):
        set_col(self,value,0)
    def c2(self, value):
        set_col(self,value,1)
    def c3(self, value):
        set_col(self,value,2)
    def c4(self, value):
        set_col(self,value,3)


class PropsGroup_games101(bpy.types.PropertyGroup):
    shear: bpy.props.BoolProperty(
        name='Shear',
        default=True,
        description='Enable Shear. 开启剪切变换',
        update=update_switch
    )
    c1: bpy.props.FloatVectorProperty(
        name='Column 1',
        size=4,
        default=[1.,0.,0.,0.],
        get=Get.c1,
        set=Set.c1,
        description='',
    )
    c2: bpy.props.FloatVectorProperty(
        name='Column 2',
        size=4,
        default=[0.,1.,0.,0.],
        get=Get.c2,
        set=Set.c2,
        description='',
    )
    c3: bpy.props.FloatVectorProperty(
        name='Column 3',
        size=4,
        default=[0.,0.,1.,0.],
        get=Get.c3,
        set=Set.c3,
        description='',
    )
    c4: bpy.props.FloatVectorProperty(
        name='Column 4',
        size=4,
        default=[0.,0.,0.,1.],
        get=Get.c4,
        set=Set.c4,
        description='',
    )


class Panel_games101(bpy.types.Panel):
    bl_label = 'GAMES101🎮:矩阵SRT'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Item'

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        props = scene.games101
        obj = context.object

        row = layout.row()
        row.operator(ReloadScriptOperator.bl_idname,
                        text="", icon='FILE_REFRESH',emboss=False)
        row.operator(UnregScriptOperator.bl_idname,
                        text="", icon='X',emboss=False)
        
        # lock icon button
        row.prop(props, "shear", text="", icon='MOD_LATTICE')

        col = layout.column(align=True)
        row = col.row(align=True)
        col_ = row.column(align=True)
        col_.prop(props, 'c1')
        col_ = row.column(align=True)
        col_.prop(props, 'c2')
        col_ = row.column(align=True)
        col_.prop(props, 'c3')
        col_ = row.column(align=True)
        col_.prop(props, 'c4')


class UnregScriptOperator(bpy.types.Operator):
    bl_idname = 'object.unreg_script'
    bl_label = 'Unregister'
    bl_options = {'REGISTER'}
    bl_description = 'Unregister Script. 移除面板,可以再次运行来重新加载脚本'
    
    def execute(self, context):
        unregister()
        return {'FINISHED'}


class ReloadScriptOperator(bpy.types.Operator):
    bl_idname = 'object.reload_script'
    bl_label = 'Reload'
    bl_options = {'REGISTER'}
    bl_description = 'Reload Script. 重载脚本'

    def execute(self, context):
        # 获取当前文本编辑器
        for area in bpy.context.screen.areas:
            if area.type == 'TEXT_EDITOR':
                with bpy.context.temp_override(area=area):
                    bpy.ops.text.resolve_conflict(resolution='RELOAD')
                    bpy.ops.text.run_script()
                break
        else:
            self.report({'ERROR'}, "没有找到文本编辑器")
            return {'CANCELLED'}

        return {'FINISHED'}


classes = (
    Panel_games101,
    PropsGroup_games101,
    ReloadScriptOperator,
    UnregScriptOperator,
)


def register():
    for i in classes:
        bpy.utils.register_class(i)
    bpy.types.Scene.games101 = bpy.props.PointerProperty(
        type=PropsGroup_games101)


def unregister():
    del bpy.types.Scene.games101
    for i in classes:
        bpy.utils.unregister_class(i)


if __name__ == "__main__":
    register()

质心坐标,判断某像素是否在三角形内

叉乘/平行四边形/行列式正负

具体来说,叉积的符号由右手法则决定:

如果从第一个向量通过最小角度旋转到第二个向量是逆时针方向,那么叉积的结果是正的。
如果是顺时针方向,那么叉积的结果是负的。


cos 点乘
sin 叉乘

View_camera 勘误纠错

为什么View矩阵内,先平移后旋转?知乎的有点绕。

有了摄像机的整个变换过程 MtMr 。
对于物体的变换,则对上面矩阵求逆,是 \((M_tM_r)^{−1}=M_r^{−1}M_t^{−1}\)
对于旋转矩阵,正交矩阵的逆=正交矩阵的转置

建议看glm::LookAt的实现
矩阵应用从右到左,给了cam的世界坐标,先平移再旋转;反之,旋转后再平移就不准确了

// glm::View矩阵,难点
Matrix lookat(Vec3f eye, Vec3f center, Vec3f up) {
    // 要理解Vec3f存的是 坐标点 还是 向量基,此处存的是 向量基
    Vec3f z = (eye - center).normalize();  // 点❌ 方向✅
    Vec3f x = (up ^ z).normalize();
    Vec3f y = (z ^ x).normalize();
    Matrix rot = Matrix::identity(4);
    Matrix trans = Matrix::identity(4);
    for (int i = 0; i < 3; i++) {
        rot[0][i] = x[i];
        rot[1][i] = y[i];
        rot[2][i] = z[i];
        trans[i][3] = -eye[i];
    }
    return rot * trans;  // 顺序很重要
    // 矩阵应用从右到左,给了cam的世界坐标,对坐标点做变换:先平移再旋转;反之,旋转后再平移就不准确了
    // 对cam向量基做变换:先旋转再平移
    // 世界坐标->cam坐标,让世界坐标点 主动出现在 cam的视野(2D屏幕/舞台)内。
}
posted @ 2024-11-23 08:51  Nolca  阅读(67)  评论(0)    收藏  举报