unity shader中的矩阵变换知识

2023-11-01

提要

       在图形的计算中,比如旋转、缩放、平移、投影等操作,矩阵都扮演着极其重要的角色,它是操作图元的基本工具。虽然很多的图形API已经封装好了这些矩阵操作,但是理解这些矩阵操作的原理会非常非常有帮助,比如说我们可以通过一些矩阵的快捷计算来加速你的代码。

      如果你有一些线性代数的基础,看下面的内容的时候也不会很轻松,因为有点难且比较没意思,如果没有修过这门课,最好把线性代数这本书拿来看看,因为这些东西真是基础中的基础,而且非常的重要。

齐次记法(Homogeneous Notation)

       空间一个点对应的是一个空间的位置,一个向量对应一个方向,两者都可以用一个三维向量 V = (Vx, Vy, Vz)来表示.

       这两者如果对于变换(比如旋转,缩放),用一个 3*3 矩阵就可以搞定,但对于平移变换就不适用了,因为位置变换对于向量是没有意义的,而对于点才是有意义的。

       齐次记法就是用来解决这个问题的。

       所谓齐次记法就是用n+1维矢量表示n维矢量。

       在齐次记法下,空间点记为 = (Px, Py, Pz, Pw), 其中Pw = 1,4行x1列。

       空间向量记为 = (Vx, Vy, Vz, Vw),其中Vw = 0。 4行x1列。

       当出现Pw!=0 且Pw != 1时,就需要将坐标齐次化了,做法是同除以Pw,记为(Px/Pw, Py/Pw, Pz/Pw, 1).

       齐次记法下的变换矩阵如下所示:

给定一个移动变换矩阵

对于一个向量  = (Vx, Vy, Vz, Vw)和 相乘之后各值不变(TV)。

对于一个点   = (Px, Py, Pz, Pw)和 相乘之后结果变为 (Px+tx, Py+ty, Pz+tz, 1). (TP

       齐次坐标带来的便利:提供了用矩阵运算把二维、三维甚至高维空间中的一个点集从一个坐标系变化到另一个坐标系的有效方法。

基础变换

基础的变换包括平移,旋转,缩放,切变,反射,投影等,下面一个个来看。

平移变换

上面已经提到了,平移矩阵用T来表示:

tx,ty和tz分别表示向x,y,z方向移动的距离,如图

注意这个仿射矩阵(Offine Tranform Matrics)对于空间向量是没有作用的。

其逆矩    ,表示向相反的方向移动。

 旋转矩阵 Rotating

旋转变幻是指绕着一个轴旋转一定的角度,绕x,y,z旋转的旋转矩阵可以记为:

逆阵   , 表示绕同一个轴按相反的方向旋转相同的角度。

旋转矩阵的行列式都为1,因为它是正交矩阵。

关于图形(或物体)绕自身的某点旋转,其真实的过程是先将物体移动到旋转点与坐标原点相重合的位置,再将图形绕原点旋转,然后再进行平移变换,平移到原先的位置。

整个矩阵计算过程为   

缩放变换 Scaling

缩放就是放大和缩小,其矩阵表示为

如果 Sx = Sy = Sz,则称为等比变换(uniform),否则就不是(nonuniform)。

        其逆阵   ,表示按相反的方式进行缩放。

        Sx,Sy,Sz中有一个为负数,则改矩阵就是反射矩阵,如果刚好有两个因子为 -1, 则图形旋转  。反射矩阵通常需要特殊对待,比如,对于一个三角形,经过反射变换,顶点的顺序就可能会改变,这就会影响到面的法线,光照和背面消隐等算法就会受影响。可以通过计算左上角 3*3 矩阵的行列式的值来进行判断,若行列式的值为负,则是反射矩阵。

切变变换 Shearing

切变变换可以用于游戏中,制作出爆炸的时候画面抖动的效果,一共有六种:

第一个下标表示要改变的坐标轴,第二个下标表示沿着那个坐标轴变换。相关的矩阵也可以由此得出:第一个下标决定行,第二个决定列,则有:

效果如下:

其逆阵:

级联变换 Concatenation of Transforms

        由于矩阵乘法是没有交换率的,所以矩阵相乘的顺序非常重要,比如 S(2, 0.5, 1)和 , 根据它们执行的顺序不同,得到的结果也会不一样。

       将多个矩阵整合到一起的另一个好处是提高了效率,一般的顺序时 TRS。

欧拉变换 Euler TransForm

       欧拉变换可以将物体旋转到任意的方向,一个欧拉变换可以分为三个分量 h(ead), p(ich), r(oll),记为E(h,p,r)。

       其实就是三个旋转矩阵的级联矩阵:,由于都为对称阵,其逆阵   =  

      使用欧拉变换的时候会出现一个很蛋疼的问题-gimbal lock,可以看看这个视频- youtube video explaining gimbal lock

      还会出现的一个问题就是两个欧拉角之间的插值问题。

      为了避免万圣节锁,一个方法是设定好旋转轴的旋转顺序。

      另一中方法是使用四元组。

      

模型矩阵,视口矩阵和投影矩阵  The Model, View and Projection Matrices

        模型是由一系列的顶点构成的,顶点的坐标是相对于模型的中心来定义的,如果某个顶点的坐标值是(0,0,0),就意味着这个顶点在模型的正中间。

现在假设世界坐标在,模型的左边,则模型左边对应于世界坐标需要乘以一个平移矩阵,这个矩阵就是model matrix(模型矩阵)。

人们在操作这个模型的时候,需要对其进行一些变换,就需要将其每个定点移动到原点。

通过下图中黑色的箭头,就是将模型移动到原点。

这个变换矩阵就是model matrix(模型矩阵)

这个过程可以描述为:

         接下来是View Matrix(视口矩阵)。

        当你站在一座山的前面,想从各个角度来观察这座山的时候,你可以选择跑到不同的位置去看,也可以选择...移动整座山。这在现实生活中看似不行,但在图形学中,这一切都是可行的。

        现在在整个世界中只有一个model,当需要观察这个物体的时候,需要一个摄像机来进行观察,假设摄像机初始化在原点,经过一个平移矩阵移动,

glm::mat4 ViewMatrix = glm::translate(Tx, Ty ,Tz); 


这个矩阵就是View Matrix(视口矩阵),对应的就是世界坐标远点到摄像机的变换矩阵,过程可以由下图描述。

这里提一下glm中的一个神奇的lookat函数~超强的生成 View Matrics

glm::mat4 CameraMatrix = glm::LookAt(  
    cameraPosition, // the position of your camera, in world space  
    cameraTarget,   // where you want to look at, in world space  
    upVector        // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too  
);  


整个阶段的描述如下:

接下来是Projection matrices(投影矩阵)

        经过前面的Model Matriix 和 View Matrix的变换,现在处在的就是摄像机空间,也就意味着(0,0)上的点就会出现在屏幕的最中央,但是并不是两个坐标就可以决定顶点是否显示,我们不能忽略 Z 坐标,也就是顶点距离摄像机的位置。

       在透视投影中,根据顶点的坐标值,当Vx,Vy的值相同的时候,Vz的值越大,顶点就越在中间,可以参考下图。

一个4*4的矩阵可以用来描述投影:

glm::mat4 projectionMatrix = glm::perspective(  
    FoV,         // The horizontal Field of View, in degrees : the amount of "zoom". Think "camera lens". Usually between 90° (extra wide) and 30° (quite zoomed in)  
    4.0f / 3.0f, // Aspect Ratio. Depends on the size of your window. Notice that 4/3 == 800/600 == 1280/960, sounds familiar ?  
    0.1f,        // Near clipping plane. Keep as big as possible, or you'll get precision issues.  
    100.0f       // Far clipping plane. Keep as little as possible.  
); 


通过投影矩阵变换,模型从照相机坐标变为了齐次坐标,过程描述如下:

GLSL实战MVP

        我们知道,OpenGL中自带了一些接口函数,可以很方便的定义视口,投影矩阵等,但如果使用GLSL的话,所有顶点的位置都是由 *.vert  中的代码来确定,下面我们就来实践一下刚才学习的Model,View,Projection。

      首先是两个简单的Shader

basic.vert

#version 400  
layout(location = 0) in vec3 vertexPosition_modelspace;  
  
// Values that stay constant for the whole mesh.  
uniform mat4 MVP;  
  
void main(){  
  
    // Output position of the vertex, in clip space : MVP * position  
    gl_Position =   MVP * vec4(vertexPosition_modelspace,1);  
} 


就是将模型坐标与MVP矩阵相乘。

basic.frag

#version 400  
  
// Ouput data  
out vec3 color;  
  
void main()  
{  
    // Output color = red   
    color = vec3(1,0,0);  
}  


接着时初始化shader,vao

void CGL::compileShader()  
{  
    static const GLfloat g_vertex_buffer_data[] = {  
        -1.0f, -1.0f, 0.0f,  
         1.0f, -1.0f, 0.0f,  
         0.0f,  1.0f, 0.0f,  
    };  
    static const GLushort g_element_buffer_data[] = { 0, 1, 2 };  
  
    GLuint vertexbuffer;  
    glGenBuffers(1, &vertexbuffer);  
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);  
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);  
  
    glEnableVertexAttribArray(0);  
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);  
    glVertexAttribPointer(  
            0,                  // attribute. No particular reason for 0, but must match the layout in the shader.  
            3,                  // size  
            GL_FLOAT,           // type  
            GL_FALSE,           // normalized?  
            0,                  // stride  
            (void*)0            // array buffer offset  
    );  
    if( ! prog.compileShaderFromFile("shader/basic.vert",GLSLShader::VERTEX) )  
    {  
        printf("Vertex shader failed to compile!\n%s",  
               prog.log().c_str());  
        exit(1);  
    }  
    if( ! prog.compileShaderFromFile("shader/basic.frag",GLSLShader::FRAGMENT))  
    {  
        printf("Fragment shader failed to compile!\n%s",  
               prog.log().c_str());  
        exit(1);  
    }  
  
    if( ! prog.link() )  
    {  
        printf("Shader program failed to link!\n%s",  
               prog.log().c_str());  
        exit(1);  
    }  
    if( ! prog.validate() )  
    {  
        printf("Program failed to validate!\n%s",  
               prog.log().c_str());  
        exit(1);  
    }  
    prog.use();  
}  


然后是初始化Uniform变量:

void CGL::setUniform()  
{  
  
    // Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units  
    glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);  
    // Camera matrix  
    glm::mat4 View       = glm::lookAt(  
                                glm::vec3(3,3,3), // Camera is at (4,3,3), in World Space  
                                glm::vec3(0,0,0), // and looks at the origin  
                                glm::vec3(0,1,0)  // Head is up (set to 0,-1,0 to look upside-down)  
                           );  
    // Model matrix : an identity matrix (model will be at the origin)  
    glm::mat4 Model      = glm::mat4(1.0f);  
    // Our ModelViewProjection : multiplication of our 3 matrices  
    glm::mat4 MVP        = Projection * View * Model; // Remember, matrix multiplication is the other way around  
    prog.setUniform("MVP",MVP);  
    prog.setUniform("modelMatrics",Model);  
} 

渲染一下:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

unity shader中的矩阵变换知识 的相关文章

  • 使用 ShaderMaterial 复制 MeshLambertMaterial 会忽略纹理

    我注意到 THREE js 在内部使用着色器来创建核心材质 例如 MeshLambertMaterial 因此我决定将 Three js 代码中的兰伯特着色器复制到新的着色器中并在其上进行构建 这是我得到的代码 忠实地从 Three js
  • OpenGL ES - 在片段着色器中旋转纹理而不失真

    我正在使用 Android 的 GPUImage 库对位图应用一些效果 本质上 GPUImage接受位图并使用OpenGL ES 将1 x 1立方体渲染到位图大小的帧缓冲区中 用户可以编写自定义片段着色器来控制输出 我正在尝试编写一个片段着
  • 在 SceneKit 中使用 Metal 着色器

    我想用一个Metal着色器将卡通 单元着色应用于场景中使用的材质 我试图实现的着色器是苹果自己的AAPLCelShader发现于金属着色器展示柜 https developer apple com library ios samplecod
  • OpenGL 中着色器的问题

    我正在尝试在 C 中使用像 glShaderSource 这样的东西 Visual Studio 表示像 glCompileShader 或 glCreateProgram 这样的函数不存在 我使用 FreeGLUT 来包含 OpenGL
  • OpenGL 定向光着色器

    我想使用 OpenGL 和 GLSL 将定向光添加到我的场景中 问题在于 理论上正确的方法会产生错误的结果 在顶点着色器中我执行以下操作 光线的方向以世界坐标给出 并使用 viewMatrix 转换为相机坐标 使用法线矩阵将顶点的法线转换为
  • glUseProgram(0) 的作用是什么?

    OpenGL 文档为glUseProgram https www khronos org registry OpenGL Refpages gl4 html glUseProgram xhtml声称用参数调用它zero将导致着色器执行的结果
  • 如何使用鼠标改变OpenGL相机

    我正在尝试在 OpenGL 中设置一个相机来查看 3 维中的一些点 为了实现这一点 我不想使用旧的 固定的功能样式 glMatrixMode glTranslate 等 而是自己设置模型视图投影矩阵并在我的顶点着色器中使用它 正交投影就足够
  • 对数/线性 Z 缓冲区阴影贴图

    问题在于 伪影出现在距离很远的阴影中 我想尝试制作一个对数深度缓冲区 但我不明白应该在哪里完成以及如何完成 我对全向阴影贴图使用点光源方法 顶点着色器 version 460 core layout location 0 in vec3 a
  • 编写每个三角形/面具有纯色的 GLSL 片段着色器的方法

    我有顶点和三角形数据 其中包含每个数据的颜色triangle 面 不是每个顶点 即单个顶点由多个面共享 每个面可能具有不同的颜色 我应该如何在 GLSL 中解决这个问题以获得每个的纯色分配face正在渲染 通过平均顶点相邻多边形的颜色来计算
  • OpenGL:多个顶点的单个顶点属性?

    我有一个接受以下属性的顶点着色器 a posCoord 顶点位置 a texCoord 纹理坐标 传递给片段着色器 a alpha 透明度因子 传递给片段着色器 我正在渲染的对象都是 广告牌 一对直角三角形组成一个矩形 我正在使用一次调用g
  • 更改 Qt OpenGL 窗口示例以使用 OpenGL 3.3

    我正在尝试更改 Qt OpenGL 示例以使用更现代的 opengl 版本 330 似乎合适 所以我做了 在 main cpp 上设置版本和配置文件 设置着色器版本 更改着色器以使用统一 它现在构建没有任何错误 但我只看到一个空白窗口 我错
  • 在 QML 中控制纹理 3D 对象的不透明度

    我对 QML 中的 Qt 3D 有点陌生 我正在尝试控制 Qt 3D 的不透明度textured3D 对象 我正在使用简单qml3d https github com tripolskypetr simpleqml3d测试项目来做到这一点
  • GLSL 中统一浮点行为和常量浮点行为的不同

    我正在尝试在 GLSL 中实现模拟双精度 并且观察到一种奇怪的行为差异 导致 GLSL 中出现细微的浮点错误 考虑以下片段着色器 写入 4 浮点纹理以打印输出 layout location 0 out vec4 Output unifor
  • 如何计算正切和副法线?

    谈谈OpenGL着色语言 GLSL 中的凹凸贴图 镜面高光之类的东西 I have 顶点数组 例如 0 2 0 5 0 1 0 2 0 4 0 5 法线数组 例如 0 0 0 0 1 0 0 0 1 0 0 0 世界空间中点光源的位置 例如
  • 丢弃对 OpenGL 中的程序性能有影响吗?

    我正在读书this http code google com p gdc2011 android opengl wiki TalkTranscript文章 作者写道 以下是如何通过两个简单的步骤在每个平台上编写高性能应用程序 遵循最佳实践
  • 与整数纹理进行 Alpha 混合以进行对象拾取

    问题描述 你好 在我们的 WebGL 应用程序中 我们正在绘制许多 甚至数十万 形状 并且我们想要发现当前鼠标位于哪个形状 我正在寻找一种有效的方法 Details 形状定义为有符号距离函数 https en wikipedia org w
  • 使用 gl_FragColor 与 vec4 颜色?

    似乎有很多不明确的地方gl FragColor被弃用 例如 它缺失在GLSL 4 40 规范 https www khronos org registry OpenGL specs gl GLSLangSpec 4 40 pdf 但它包含在
  • 如何在 OpenGL 中绘制镜像某些东西的镜子?

    根据我的理解 要在 OpenGL 中进行镜像 您基本上需要绘制场景 然后将所有内容翻转并再次绘制 只是使其通过镜子可见 从而在镜子中创建完美翻转的图像 但我看到的问题是 执行此操作时 唯一可以看到其他镜子的镜子是在前一个镜子之后渲染的镜子
  • 计算着色器中的 Image2D

    我想使用 image2D 作为顶点的 2D 存储 该顶点将由计算着色器修改 但不起作用 创建纹理 glGenTextures 1 HeightMap glBindTexture GL TEXTURE 2D HeightMap glTexIm
  • Raspberry PI 上的 JavaFX:加载库存着色器时出错

    目前我正在尝试部署我的 JavaFX 应用程序 该应用程序可以在 Windows 上的 Raspberry Model B v1 2 上顺利运行 由于 JavaFX 不能直接在 Raspi 上使用 我已经按照此处所述使用 Gluon 进行了

随机推荐

  • Packet for query is too large

    mysql 执行insert语句时 提示Packet for query is too large异常 问题点 mysql支持的传输数据包大小超限了 解决办法 比较一劳永逸的方式是直接修改mysql安装目录下的my ini配置文件 增加一行
  • 利用python制作一款截图识别软件

    先给大家推荐一款截图软件 非常方便 可以把截出的图片放置到窗口上 并且可以随意移动 这个是微软开发的一款工具 Snipaste 支持各类电脑系统 先简单介绍一下它的用法 F1截图 ctrl c把图片复制到剪贴板 方便下次使用 ctrl v
  • python协程—asyncio模块

    为什么使用协程 当多线程或者多进程足够多时 实际上并不能解决性能的瓶颈问题 也就是多线程和多进程对小规模的请求可以提高效率 过多的请求实际上会降低服务资源响应效率 因此协程是更好的解决文案 什么是协程 当一个程序遇到阻塞时 如果将这个程序挂
  • (翻译)Linux中的IS_ERR()宏

    本文翻译自 https newbedev com is err macro in linux text 20IS ERR 20 28 29 20macro 20in 20Linux 20Tests 20if can 20find 20MAC
  • VUE3.0生命周期函数

    什么是生命周期 1 vue中每个组件都是独立的 每个组件都有一个属于它的生命周期 2 从一个组件创建 数据初始化 挂载 更新 销毁 这就是一个组件所谓的生命周期 强调的是一个时间段 在vue3中 新增了一个setup生命周期函数 setup
  • 四川岳池2021年高考成绩查询,2021年岳池中学升学率高不高?

    引语 了解一个学校 就要全方位的进行了解 清楚学校的师资 录取分数线 升学率等等 四川初升高升学网网编为同学们梳理了2021年岳池中学升学率高不高的相关信息 更多最新资讯可以关注四川初升高升学网公众号 岳池中学2021升学率 立即点击查看
  • MySQL 表分区 报错:Table has no partition for value XXX

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 对已存在的未分区的表进行分区 alter table test PARTITION BY RANGE id PARTITION p1 VALUES LESS THAN 10
  • 扫码点餐软件,打造智慧餐厅新体验!【点餐小程序】

    随着科技的不断发展 扫码点餐软件成为了越来越多餐厅的选择 它们可以帮助顾客快速方便地点餐 同时也带来了更高效的服务体验和更好的管理效果 在这篇文章中 我们将探讨如何通过扫码点餐软件打造智慧餐厅新体验 并且以一个具体的案例来解析其中的技巧 步
  • 10-6 计算时间差

    时间差可以用来衡量程序运行效率 计算其有三种方法 一是使用 difftime 函数 二是使用自定义的 TimeInMillisecond 函数 三是使用 clock 函数 1 使用 difftime 函数计算秒级时间差 三种方法本质相同 都
  • iTerm2使用笔记

    鼠标 设置在less vim中等使用滚轮 在高级设置里面 设置系统编辑方式 让写命令和在其他编辑器中一致 比如alt delete 删除一个word vim vimrc中设置系统clipboard set clipboard unnamed
  • 机器人学习--卡尔曼滤波及各种滤波解析

    什么是滤波 举个最直观的简单例子 臭水沟里舀一大勺水 需要过滤成干净水怎么办 用滤网 网孔可根据需要选择大小孔 过滤 在电路方面波形的高低通滤波原理类似 图像上的噪声点各种中值滤波等类似的滤波方案 Filter 在状态测量领域是根据 预测
  • webpack 和 ts 简单配置及使用

    如何使用webpack 与 ts结合使用 新建项目 执行项目初始化 npm init y 会生成 name tsdemo01 version 1 0 0 description main index js scripts test echo
  • Notion 的插件介绍和使用

    十分推荐 Save to Notion Notion Web Clipper notion 官方的剪藏插件 插件安装 Chrome 商店 https chrome google com webstore detail notion web
  • 看完这篇 教你玩转渗透测试靶机vulnhub——DC8

    Vulnhub靶机DC8渗透测试详解 Vulnhub靶机介绍 Vulnhub靶机下载 Vulnhub靶机安装 Vulnhub靶机漏洞详解 信息收集 暴力破解 提权 获取flag Vulnhub靶机渗透总结 Vulnhub靶机介绍 vulnh
  • keil仿真调试报错

    问题 程序能正常下载 但是keil仿真调试报错 进入仿真程序就在全速运行了 此时什么都干不了 查看command窗口日志信息会发现有报错 jlink error cpu is not halted 然后点击reset按钮会退出全速仿真 所有
  • 一个自己用的opencv历程

    include
  • 设两个栈(stack1,stack2)共享一个一维数组空间s[m],怎么最大限度地利用数组空间

    可以利用扩充栈操作 在传统的双端栈中 两个栈之间存在一种制约关系 两个栈中的元素总数最大可以达到M 如果一个栈中的元素较多 那么另一个栈中的元素就较少 两个栈中的元素总和超不过M 它主要利用了栈的 栈底位置不变 而栈顶位置动态变化 的特性
  • jquery 读取 解析 四级的xml文件 案例

    答案 jQuery 可以很容易地使用 AJAX 技术来解析 XML 文件 下面是一个案例 ajax type GET url yourXMLFile xml dataType xml success function xml xml fin
  • python day55

    今日内容 如何写一个测试脚本 创建一个test py文件 from django test import TestCase Create your tests here import os if name main os environ s
  • unity shader中的矩阵变换知识

    提要 在图形的计算中 比如旋转 缩放 平移 投影等操作 矩阵都扮演着极其重要的角色 它是操作图元的基本工具 虽然很多的图形API已经封装好了这些矩阵操作 但是理解这些矩阵操作的原理会非常非常有帮助 比如说我们可以通过一些矩阵的快捷计算来加速