OpenGL到底是如何进行透视校正线性插值的?

2023-11-30

如果线性插值发生在 OpenGL 管道的光栅化阶段,并且顶点已经转换到屏幕空间,那么用于透视正确插值的深度信息从何而来?

谁能详细描述 OpenGL 如何从屏幕空间基元到具有正确插值的片段?


顶点着色器的输出是four分量向量,vec4 gl_Position。来自核心 GL 4.4 规范的第 13.6 节坐标变换:

剪辑坐标对于着色器执行的顶点结果,它产生一个顶点坐标gl_Position.

剪辑坐标上的透视除法产生标准化设备坐标,后面跟着一个viewport变换(参见第 13.6.1 节)将这些坐标转换为窗口坐标.

OpenGL 将透视划分为

device.xyz = gl_Position.xyz / gl_Position.w

但随后保留了1 / gl_Position.w作为最后一个组成部分gl_FragCoord:

gl_FragCoord.xyz = device.xyz scaled to viewport
gl_FragCoord.w = 1 / gl_Position.w

该变换是双射的,因此不会丢失深度信息。事实上,正如我们在下面看到的,1 / gl_Position.w对于透视正确插值至关重要。


重心坐标简介

给定一个三角形 (P0, P1, P2),我们可以通过顶点的线性组合来参数化三角形内的所有点:

P(b0,b1,b2) = P0*b0 + P1*b1 + P2*b2

其中 b0 + b1 + b2 = 1 且 b0 ≥ 0、b1 ≥ 0、b2 ≥ 0。

给定三角形内的点 P,满足上式的系数 (b0, b1, b2) 称为重心坐标那一点。对于非退化三角形,它们是唯一的,并且可以计算为以下三角形面积的商:

b0(P) = area(P, P1, P2) / area(P0, P1, P2)
b1(P) = area(P0, P, P2) / area(P0, P1, P2)
b2(P) = area(P0, P1, P) / area(P0, P1, P2)

每个 bi 可以被认为是“必须混合多少 Pi”。所以 b = (1,0,0), (0,1,0) 和 (0,0,1) 是三角形的顶点,(1/3, 1/3, 1/3) 是重心,等等。

给定三角形顶点上的属性 (f0, f1, f2),我们现在可以将其插值到内部:

f(P) = f0*b0(P) + f1*b1(P) + f2*b2(P)

这是 P 的线性函数,因此它是给定三角形上的唯一线性插值。数学也适用于 2D 或 3D。

透视正确插值

假设我们在屏幕上填充一个投影的 2D 三角形。对于每个片段,我们都有它的窗口坐标。首先,我们通过反转来计算其重心坐标P(b0,b1,b2)函数,它是窗口坐标中的线性函数。这给了我们片段的重心坐标二维三角形投影.

属性的透视正确插值将在剪辑坐标(以及世界坐标)。为此,我们需要获取剪辑空间中片段的重心坐标。

当它发生时(参见[1] and [2]),片段的深度在窗口坐标中不是线性的,但是深度逆 (1/gl_Position.w) 是。因此,当通过深度倒数加权时,属性和剪辑空间重心坐标在窗口坐标中线性变化。

因此,我们通过以下方式计算透视校正重心:

     ( b0 / gl_Position[0].w, b1 / gl_Position[1].w, b2 / gl_Position[2].w )
B = -------------------------------------------------------------------------
      b0 / gl_Position[0].w + b1 / gl_Position[1].w + b2 / gl_Position[2].w

然后用它来插入顶点的属性。

Note: GL_NV_fragment_shader_barycentric通过暴露设备线性重心坐标gl_BaryCoordNoPerspNV并通过纠正视角gl_BaryCoordNV.

执行

下面是一段 C++ 代码,它以类似于 OpenGL 的方式在 CPU 上对三角形进行光栅化和着色。我鼓励您将其与下面列出的着色器进行比较:

struct Renderbuffer { int w, h, ys; void *data; };
struct Vert { vec4 position, texcoord, color; };
struct Varying { vec4 texcoord, color; };

void vertex_shader(const Vert &in, vec4 &gl_Position, Varying &OUT) {
    OUT.texcoord = in.texcoord;
    OUT.color = in.color;
    gl_Position = vec4(in.position.x, in.position.y, -2*in.position.z - 2*in.position.w, -in.position.z);
}

void fragment_shader(vec4 &gl_FragCoord, const Varying &IN, vec4 &OUT) {
    OUT = IN.color;
    vec2 wrapped = IN.texcoord.xy - floor(IN.texcoord.xy);
    bool brighter = (wrapped[0] < 0.5) != (wrapped[1] < 0.5);
    if(!brighter)
        OUT.rgb *= 0.5f;
}

// render output unit/render operations pipeline
void rop(Renderbuffer &buf, int x, int y, const vec4 &c) {
    uint8_t *p = (uint8_t*)buf.data + buf.ys*(buf.h - y - 1) + 4*x;
    p[0] = linear_to_srgb8(c[0]);
    p[1] = linear_to_srgb8(c[1]);
    p[2] = linear_to_srgb8(c[2]);
    p[3] = lround(c[3]*255);
}

void draw_triangle(Renderbuffer &color_attachment, const box2 &viewport, const Vert *verts) {
    auto area = [](const vec2 &p0, const vec2 &p1, const vec2 &p2) { return cross(p1 - p0, p2 - p0); };
    auto interpolate = [](const auto a[3], auto p, const vec3 &coord) { return coord.x*a[0].*p + coord.y*a[1].*p + coord.z*a[2].*p; };

    Varying perVertex[3];
    vec4 gl_Position[3];

    box2 aabb = { viewport.hi, viewport.lo };
    for(int i = 0; i < 3; ++i) {
        vertex_shader(verts[i], gl_Position[i], perVertex[i]);

        // convert to normalized device coordinates
        gl_Position[i].w = 1/gl_Position[i].w;
        gl_Position[i].xyz *= gl_Position[i].w;

        // convert to window coordinates
        gl_Position[i].xy = mix(viewport.lo, viewport.hi, 0.5f*(gl_Position[i].xy + 1.0f));
        aabb = join(aabb, gl_Position[i].xy);
    }

    const float denom = 1/area(gl_Position[0].xy, gl_Position[1].xy, gl_Position[2].xy);

    // loop over all pixels in the rectangle bounding the triangle
    const ibox2 iaabb = lround(aabb);
    for(int y = iaabb.lo.y; y < iaabb.hi.y; ++y)
    for(int x = iaabb.lo.x; x < iaabb.hi.x; ++x)
    {
        vec4 gl_FragCoord;
        gl_FragCoord.xy = vec2(x, y) + 0.5f;

        // fragment barycentric coordinates in window coordinates
        const vec3 barycentric = denom*vec3(
            area(gl_FragCoord.xy, gl_Position[1].xy, gl_Position[2].xy),
            area(gl_Position[0].xy, gl_FragCoord.xy, gl_Position[2].xy),
            area(gl_Position[0].xy, gl_Position[1].xy, gl_FragCoord.xy)
        );

        // discard fragment outside the triangle. this doesn't handle edges correctly.
        if(barycentric.x < 0 || barycentric.y < 0 || barycentric.z < 0)
            continue;

        // interpolate inverse depth linearly
        gl_FragCoord.z = interpolate(gl_Position, &vec4::z, barycentric);
        gl_FragCoord.w = interpolate(gl_Position, &vec4::w, barycentric);

        // clip fragments to the near/far planes (as if by GL_ZERO_TO_ONE)
        if(gl_FragCoord.z < 0 || gl_FragCoord.z > 1)
            continue;

        // convert to perspective correct (clip-space) barycentric
        const vec3 perspective = 1/gl_FragCoord.w*barycentric*vec3(gl_Position[0].w, gl_Position[1].w, gl_Position[2].w);

        // interpolate attributes
        Varying varying = {
            interpolate(perVertex, &Varying::texcoord, perspective),
            interpolate(perVertex, &Varying::color, perspective),
        };

        vec4 color;
        fragment_shader(gl_FragCoord, varying, color);
        rop(color_attachment, x, y, color);
    }
}

int main(int argc, char *argv[]) {
    Renderbuffer buffer = { 512, 512, 512*4 };
    buffer.data = calloc(buffer.ys, buffer.h);

    // VAO interleaved attributes buffer
    Vert verts[] = {
        { { -1, -1, -2, 1 }, { 0, 0, 0, 1 }, { 0, 0, 1, 1 } },
        { { 1, -1, -1, 1 }, { 10, 0, 0, 1 }, { 1, 0, 0, 1 } },
        { { 0, 1, -1, 1 }, { 0, 10, 0, 1 }, { 0, 1, 0, 1 } },
    };

    box2 viewport = { 0, 0, buffer.w, buffer.h };
    draw_triangle(buffer, viewport, verts);

    stbi_write_png("out.png", buffer.w, buffer.h, 4, buffer.data, buffer.ys);
}

OpenGL 着色器

以下是用于生成参考图像的 OpenGL 着色器。

顶点着色器:

#version 450 core
layout(location = 0) in vec4 position;
layout(location = 1) in vec4 texcoord;
layout(location = 2) in vec4 color;
out gl_PerVertex { vec4 gl_Position; };
layout(location = 0) out Varying { vec4 texcoord; vec4 color; } OUT;
void main() {
    OUT.texcoord = texcoord;
    OUT.color = color;
    gl_Position = vec4(position.x, position.y, -2*position.z - 2*position.w, -position.z);
}

片段着色器:

#version 450 core
layout(location = 0) in Varying { vec4 texcoord; vec4 color; } IN;
layout(location = 0) out vec4 OUT;
void main() {
    OUT = IN.color;
    vec2 wrapped = fract(IN.texcoord.xy);
    bool brighter = (wrapped.x < 0.5) != (wrapped.y < 0.5);
    if(!brighter)
        OUT.rgb *= 0.5;
}

Results

以下是由 C++(左)和 OpenGL(右)代码生成的几乎相同的图像:

差异是由不同的精度和舍入模式引起的。

为了进行比较,这是一个透视不正确的视图(使用barycentric代替perspective对于上面代码中的插值):

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

OpenGL到底是如何进行透视校正线性插值的? 的相关文章

  • 用圆形减去(遮盖掉?)路径

    我正在使用 Spark Path 在 Flex 中绘制一条路径 我想从这条路径中减去一个圆形 如下图所示 道路又黑又宽 有任何想法吗 我尝试使用 Shape 对象创建蒙版 但无法完全创建其中有圆孔的蒙版 找到了 不涉及口罩 我拿了Path并
  • glEnableVertexAttribArray 中“index”参数的含义以及(可能)OS X OpenGL 实现中的错误

    1 我是否正确理解 要使用顶点数组或VBO进行绘制 我需要所有属性在着色器程序链接之前调用glBindAttribLocation 或者在着色器程序成功链接后调用glGetAttribLocation 然后使用glVertexAttribP
  • 简单的线框格式?

    我正在寻找一种用于线框模型的简单文件格式 我知道 VRML u3D 等 但这些对于我的需求来说似乎很重要 我的标准是 必须有明确的规格 要么是开放的 要么是非常完善 记录的 我只需要 想要 简单的模型 顶点和边 我不想处理面孔或物体 如果格
  • 测量字符串的像素宽度

    我需要在 Cocoa Touch 中测量字符串的像素宽度 谁能给我指出一个解释如何执行此操作的链接 在 iPhone 操作系统上略有不同 请查看NSString UIKit 添加参考 http developer apple com iph
  • 用于预乘 ARGB 的 SSE alpha 混合

    我正在尝试编写一个支持 SSE 的 alpha 合成器 这就是我想出的 首先 混合两个 4 像素向量的代码 alpha blend two 128 bit 16 byte SSE vectors containing 4 pre multi
  • PyQt - 如何检查 QDialog 是否可见?

    我有个问题 我有这个代码 balls Ball for i in range 1 10 因此 当我说 Ball 时 这将在 QDialog 上绘制一个球 然后当这完成后 我正在移动球QDialog无限循环中 我想说类似的话while QDi
  • NV_path_rendering替代方案[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我刚刚观看了 Siggraph 2012 的一个非常令人印象深刻的演示 http nvidia fullviewmedia com sig
  • glut 库中缺少 glutInitContextVersion()

    我正在练习一些 opengl 代码 但是当我想通过以下方式强制 opengl 上下文使用特定版本的 opengl 时glutInitContextVersion 它编译过程失败并给出以下消息 使用未声明的标识符 glutInitContex
  • 如何在 GTX 560 及更高版本上使用 OpenGL 进行立体 3D?

    我正在使用在 Windows 7 上运行的开源触觉和 3D 图形库 Chai3D 我重写了该库以使用 Nvidia nvision 执行立体 3D 我将 OpenGL 与 GLUT 一起使用 并使用 glutInitDisplayMode
  • 当 OpenGL 中同时绑定 1D 和 2D 纹理时,正确的行为是什么?

    假设你有这样的东西 glBindTexture GL TEXTURE 2D my2dTex glBindTexture GL TEXTURE 1D my1dTex glBegin 正确的 OpenGL 行为是什么 要绘制一维纹理 二维纹理还
  • 将四元数旋转转换为旋转矩阵?

    基本上 给定一个四元数 qx qy qz qw 我如何将其转换为OpenGL旋转矩阵 我也对哪个矩阵行是 向上 向右 向前 等感兴趣 我有一个四元数的相机旋转 我需要在向量中 以下代码基于四元数 qw qx qy qz 其中顺序基于 Boo
  • 将带有 glut 的点击坐标添加到向量链接列表中

    我想创建一个向量链接列表 并在 GLUT 库的帮助下获取点击的位置并将它们附加到链接列表中 这些是我写的结构 typedef struct vector int x int y Vector typedef struct VectorLis
  • OpenGL - 两个纹理的幂

    OpenGL 使用二次幂纹理 这是因为由于 MipMapping 某些 GPU 只接受 2 的幂纹理 当绘制比实际更大的纹理时 使用这些二次方纹理会导致问题 我想到了一种方法来解决这个问题 即仅在我们使纹理小于实际大小时使用 PO2 比率
  • Glew+GLFW Win32 无依赖项 Visual Studio

    是否可以在不将文件复制到 C 的情况下构建并链接 Glew 和 GLFW 我找不到任何说明如何在不将 DLL 复制到 C 上的 Visual Studio 目录的情况下使用这些库的文档 我只想包含项目目录中所需的所有 dll 和 lib 文
  • 我应该如何缓冲绘制的矩形以提高性能(C#/.NET/WinForms/GDI+)

    我在做什么 我正在开发一个 C NET 4 7 2 WinForms 应用程序 它使用以下命令在表单上绘制大量填充矩形Graphics FillRectangle https learn microsoft com en us dotnet
  • 关闭 Löve2D 中的抗锯齿功能

    我在用着L ve2D http love2d org用于编写一个小游戏 L ve2D 是 Lua 的开源游戏引擎 我遇到的问题是 当您在非整数位置绘制精灵时 某些抗锯齿过滤器会自动应用于精灵 love graphics draw sprit
  • 使用 JOGL 和 Android OpenGL 编写可移植 Java 应用程序

    我计划编写一款可以在 PC 和 Android 上运行的 Java 3D 游戏 不幸的是 这两个平台似乎没有通用的 OpenGL API API 是否有显着差异 有没有办法在两个版本中使用相同的 3D 代码 这是不是一个好主意 Androi
  • 更改 GLUT 调用以与 MFC/C++ 一起使用

    我有一个使用 GLUT 进行 OpenGL 渲染的程序 现在我需要它位于 MFC 项目内部 以便它可以与另一个程序组件一起使用 我已经按照这个教程进行操作 http www codeguru com cpp g m opengl openf
  • 如何在 SVG 元素上使用箭头标记?

    我需要在 d3 js 中创建一个箭头 但我找到的只是带有节点图的示例 我需要的是简单地制作一个从 A 点到 B 点的箭头 我尝试实现以下示例中的部分代码 http bl ocks org 1153292 http bl ocks org 1
  • SSBO 是更大的 UBO?

    我目前正在 OpenGL 4 3 中使用 UBO 进行渲染 以将所有常量数据存储在 GPU 上 诸如材料描述 矩阵等内容 它可以工作 但是 UBO 的小尺寸 我的实现为 64kB 迫使我多次切换缓冲区 减慢渲染速度 我正在寻找类似的方法来存

随机推荐

  • SwiftUI - 如何脉动图像不透明度?

    我在 SwiftUI 中有一个图像 我希望它永远 脉动 每秒左右来来去去 我尝试了很多方法 但似乎无法达到我想要的效果 我尝试过的事情之一是下面的代码 它似乎什么也没做 Image systemName dot radiowaves lef
  • 自定义 className 语义 ui 反应

    我希望做这样的事情
  • jQuery ajax 加载 MVC 控制器参数的问题

    介绍 我正在使用带有 WebApi 2 0 和 jQuery 的 ASP NET MVC 5 Problem 我正在尝试显示一个带有来自 ajax 调用的用户详细信息的模式 使用 getJSON 我成功收到来自 WebApi 的 Json
  • 无法在 Heroku 上“运行”

    我已经将 Clojure 应用程序部署到 Heroku 当我运行 请求它时 我收到错误 http glowing planet 168 herokuapp com 所以现在我想启动一个 REPL 看看是否可以获得更多信息 但是当我尝试时我得
  • 从 Fortran 调用 C 函数,其中 C 函数名称最初是从 C 传入的

    由于不相关的原因 我需要将 C C 函数名称传递到 Fortran 子例程中 该子例程又调用该 C 函数 我发现我可以成功地将函数名称传递到 Fortran 子例程中 在该子例程中我可以调用正确的 C 函数 然而 C 函数的参数在这次调用中
  • Java计算器不执行if语句[重复]

    这个问题在这里已经有答案了 我对编程比较陌生 最近开始学习 Java 以便进入 Android 编程 我以为我会创建一个非常简单的计算器来练习 但似乎我的 if 语句不起作用 import java util Scanner public
  • 即使我获得了类窗口的句柄,BringWindowToTop 也不起作用

    我用以下方法注册我的班级 BOOL CNDSClientDlg InitInstance Register Window Updated on 16th Nov 2010 Subhen Register our unique class n
  • 调整大小的黑色画布不会随着时间的推移完全褪色为黑色

    我有一块黑色画布 里面画着东西 我希望里面绘制的东西随着时间的推移 按照绘制的顺序 先进先出 逐渐变成黑色 如果我使用尚未调整大小的画布 则此方法有效 调整画布大小时 元素会褪色为灰白色 问题 调整画布大小后 为什么白色斑点没有完全褪色为黑
  • 如何在ViewModel中玩Storyboard?

    我在 View 中定义了一个故事板
  • 海量设备/节点的距离计算

    I have N移动设备 节点 比如 100K 我定期获取它们的位置 纬度 经度 值 一些设备 逻辑连接 到大致M其他设备 比如 10 个 我的程序定期比较每个设备与其逻辑连接的设备之间的距离 并确定该距离是否在阈值内 例如 100 米 我
  • 在 iOS 中单击 UIButton 时如何将项目插入到 UITableView

    我一直在练习 tableViews 但我不知道如何在单击按钮时插入新项目 这就是我所拥有的 BIDViewController h import
  • 有什么作用!! R 中的运算符均值

    有人可以解释一下我们需要什么吗 or 运营商来自rlang 我尝试学习more关于准引用但没有得到任何东西 我已经在 Stack 上找到了几篇关于 curly curly 运算符的帖子 并且了解到我们使用 当我们将数据帧的变量 或对象的其他
  • 尝试编写一个简单的轮播

    我正在尝试构建一个简单的轮播 我知道有很多 但我更喜欢尝试找出答案 这是我的轮播代码 div class amnavigation div class previous lt div ul li icon li li icon li li
  • 将静态库答案放在 flash 部分的开头

    我正在使用 atmelstudio 编译固件映像 并希望将静态库 包括 gnu 的 libc a 和 libgcc a 中的函数放在 text 部分的开头 后面的 text 属于我的项目源代码 现在发生的情况恰恰相反 这是我的链接器脚本 A
  • 使用未知的编码键和值进行 Swift 解码[重复]

    这个问题在这里已经有答案了 我想从服务器解码以下对象 USD 6385 74 JPY 715249 73 EUR 5582 36 但我想使用具有未知键和值的可解码结构 这可能吗 Regards Spyros 你可以试试 let res tr
  • Node.js 中的计时器在自己的线程上运行吗?

    我在这里有点困惑 我知道 Javascript 是一种单线程语言 但在阅读事件循环时 我知道 在 setTimeout 或 setInterval 的情况下 JavaScript 会调用浏览器提供的 Web API 该 API 会生成一个新
  • ARM 指令 ldrex/strex 是否必须对缓存对齐的数据进行操作?

    在 Intel 上 CMPXCHG 的参数必须与缓存行对齐 因为 Intel 使用 MESI 来实现 CAS 在 ARM 上 ldrex 和 strex 对独占保留颗粒进行操作 需要明确的是 这是否意味着在 ARM 上操作的数据不必进行缓存
  • 根据 SQL 中设置的规则过滤列中的行

    我是 SQL 新手 我想根据设定的规则过滤列上的值 Group ID 1 09239820 2 2872498938 2 1267 3 23219823983 3 267839236 4 33287442 我想用字母替换 ID 列中的第 1
  • Java - Google API - 发布文档

    我在使用 Google 文档 API 上传信息时遇到问题 任务是上传文档 然后在上传后立即发布 我已经解决了第一部分 得到一个DocsService客户 通过以下方式验证我自己的身份client setUserCredentials use
  • OpenGL到底是如何进行透视校正线性插值的?

    如果线性插值发生在 OpenGL 管道的光栅化阶段 并且顶点已经转换到屏幕空间 那么用于透视正确插值的深度信息从何而来 谁能详细描述 OpenGL 如何从屏幕空间基元到具有正确插值的片段 顶点着色器的输出是four分量向量 vec4 gl