明暗器组成

osgearth在其几种渲染模式中使用glsl明暗器。默认情况下,OSGearth将检测图形硬件的功能,并自动选择要使用的适当模式。

由于osgearth依赖于明暗器,因此作为开发人员,您可能希望自定义渲染或在glsl中添加自己的效果和功能。任何与shaders合作过的人都会遇到同样的挑战:

  • 着色程序是单片的。添加新的明暗器代码需要您复制、修改和替换现有代码,这样就不会丢失其功能。

  • 保持更改与原始代码的着色器的更改同步是维护方面的一个噩梦。

  • 维护多个版本的shader main()s既麻烦又困难。

  • 随着glsl代码库变得越来越复杂,并且添加了更多的功能,维护可怕的“uber shader”变得无法管理。

明暗器组成 通过以下方式解决这些问题 模块化 明暗器管道。您可以添加和删除 功能 在程序中的任何时候,都不会复制、粘贴或破解其他人的glsl代码。

接下来,我们将讨论osgearth的着色合成框架的结构。

框架基础

shader composition框架自动提供main()函数。你不需要写它们。相反,您编写模块化函数并告诉框架何时何地执行它们。

下面可以看到osgearth创建的main()函数。这个 LOCATION_* 指示符允许您在明暗器的执行管道中的不同点插入函数。

以下是osgearth内置shaders主电源的伪代码:

// VERTEX SHADER:

void main(void)
{
    vec4 vertex = gl_Vertex;

    // "LOCATION_VERTEX_MODEL" user functions are called here:
    model_func_1(vertex);
    model_func_2(vertex);
    ...

    vertex = gl_ModelViewMatrix * vertex;

    // "LOCATION_VERTEX_VIEW" user functions are called here:
    view_func_1(vertex);
    ...

    vertex = gl_ProjectionMatrix * vertex;

    // "LOCATION_VERTEX_CLIP" user functions are called last:
    clip_func_1(vertex);
    ...

    gl_Position = vertex;
}


// FRAGMENT SHADER:

void main(void)
{
    vec4 color = gl_Color;
    ...

    // "LOCATION_FRAGMENT_COLORING" user functions are called here:
    coloring_func_1(color);
    ...

    // "LOCATION_FRAGMENT_LIGHTING" user functions are called here:
    lighting_func_1(color);
    ...

    gl_FragColor = color;
}

正如您所看到的,我们已经做出了设计决策来指定有意义的函数注入点 most 应用。这并不是说它们对任何事情都是完美的,而是我们相信这种方法使框架易于使用,并且不太“低级”。

重要的 :此时的明暗器组合框架仅支持顶点和片段明暗器。它还不支持几何体或镶嵌着色。我们计划将来再增加这个。

VirtualProgram

osgearth引入了一个新的osg状态属性,名为 VirtualProgram 执行运行时明暗器合成。自从 VirtualProgram 是一个 osg::StateAttribute ,可以将一个附加到场景图中的任何节点。属于 VirtualProgram 可以覆盖场景图中较高位置的明暗器。通过这种方式,您可以在osgearth中添加、合并和覆盖各个着色程序函数。

在运行时,A VirtualProgram 将查看当前状态并将 osg::Program 它使用内置的main()并调用通过 VirtualProgram .

添加函数

从我们前面看到的生成的电源中,osgearth调用用户函数。这些并不存在于osgearth生成的默认明暗器中;相反,它们表示开发人员可以“注入”到明暗器管道中的各个位置的代码。

例如,让我们使用用户函数来创建一个简单的“薄雾”效果:

// haze_vertex:
out vec3 v_pos;
void setup_haze(inout vec4 vertexView)
{
    v_pos = vertexView.xyz;
}

// haze_fragment:
in vec3 v_pos;
void apply_haze(inout vec4 color)
{
    float dist = clamp( length(v_pos)/10000000.0, 0, 0.75 );
    color = mix(color, vec4(0.5, 0.5, 0.5, 1.0), dist);
}

// C++:
VirtualProgram* vp = VirtualProgram::getOrCreate( stateSet );

vp->setFunction( "setup_haze", haze_vertex,   ShaderComp::LOCATION_VERTEX_VIEW);
vp->setFunction( "apply_haze", haze_fragment, ShaderComp::LOCATION_FRAGMENT_LIGHTING);

在这个例子中,函数 setup_haze 在内置顶点函数之后从内置顶点明暗器main()调用。这个 apply_haze 函数在内置片段函数之后从core fragment shader main()调用。

共有六个注入点,如下所示:

位置

明暗器类型

签名

shadercomp::location_vertex_model

VERTEX

void func(inout vec4顶点)

shadercomp::位置_顶点_视图

VERTEX

void func(inout vec4顶点)

shadercomp::location_vertex_clip

VERTEX

void func(inout vec4顶点)

shadercomp::location_fragment_着色

FRAGMENT

void func(输入vec4颜色)

shadercomp::location_fragment_lighting

FRAGMENT

void func(输入vec4颜色)

shadercomp::location_fragment_输出

FRAGMENT

void func(输入vec4颜色)

每个顶点位置都允许您在特定的顶点上操作 坐标空间 . 你可以改变顶点,但是你 must 把它放在同一个地方。

MODEL

顶点是几何体中未转换的原始值。

VIEW

顶点相对于视点,视点位于原点(0,0,0),并指向-Z轴。在视图空间中,原始顶点已由 gl_ModelViewMatrix .

CLIP

发布投影剪辑空间。剪辑空间位于 [-w..w] 沿所有三个轴的范围,是通过以下方式转换原始顶点的结果: gl_ModelViewProjectionMatrix .

碎片位置如下。

COLORING

在应用照明之前解析片段颜色时,将调用此处的函数。纹理或颜色调整通常在这个阶段发生。

LIGHTING

此处的函数会影响应用于片段颜色的照明。这就是太阳光、凹凸贴图或法线贴图通常会出现的地方。

OUTPUT

这是设置gl_fragcolor的地方。默认情况下,内置fragment main()将为您设置它。但您可以设置输出明暗器以用自己的行为替换此行为。这样做的一个典型原因是实现MRT渲染(请参见osgearth-mrt示例)。

着色程序包

前面我们向您展示了如何使用 VirtualProgram . shader composition框架还提供了 ShaderPackage 它支持更高级的着色管理方法。我们现在就谈谈这些。

虚拟程序元数据

如我们所见,当您使用 VirtualProgram 您需要告诉osgearth要调用的glsl函数的名称,以及调用它的管道中的位置,就像这样:

VirtualProgram* vp;
....
vp->setFunction( "color_it_red", shaderSource, ShaderComp::LOCATION_FRAGMENT_COLORING );

那就行了。但如果函数名或注入位置发生更改,则需要记住使glsl代码与 setFunction() 参数。

在同一位置指定这一切会更容易。一 ShaderPackage 你就这么做吧。下面是一个例子:

#version 330

#pragma vp_entryPoint  color_it_red
#pragma vp_location    fragment_coloring

void color_it_red(inout vec4 color)
{
    color.r = 1.0;
}

现在不要打电话 VirtualProgram::setFunction() 目录,您可以创建一个 ShaderPackage ,添加代码,并调用Load在 VirtualProgram ::

ShaderPackage package;
package.add( shaderFileName, shaderSource );
package.load( virtualProgram, shaderFileName );

它采用“文件名”,因为材质球可以在外部文件中。但这不是要求。请继续阅读了解更多详细信息。

这个 vp_location 值遵循基于代码的值,如下所示:

vertex_model
vertex_view
vertex_clip
fragment_coloring
fragment_lighting
fragment_output

外部GLSL文件

这个 ShaderPackage 允许从文件或字符串加载GLSL代码。当你打电话给 add 方法如上所示,它告诉包(a)首先按该名称查找文件并从该文件加载;(b)如果文件不存在,请使用源字符串中的代码。

那么让我们来看这个例子:

ShaderPackage package;
package.add( "myshader.frag.glsl", backupSourceCode );
...
package.load( virtualProgram, "myshader.frag.glsl" );

该包将尝试从glsl文件加载着色器。它将在 OSG_FILE_PATH . 如果找不到该文件,它将从包中与该明暗器关联的备份源代码加载明暗器。

osgearth在内部使用这种技术来“内联”其股票着色代码。这使您可以选择在应用程序中部署glsl文件,或者将它们保持在内联状态——应用程序仍然可以以任何方式工作。

包含文件

这个 ShaderPackage 支持概念如果 包含文件 . 您的GLSL代码可以 包括 通过引用同一包中的任何其他明暗器的文件名。使用自定义 #pragma 要包含另一个文件:

#pragma include myCode.vertex.glsl

就像C++一样 包括 将直接内联加载另一个文件(或源代码)。因此,您所包含的文件的结构必须像您已经将其放置在include文件中一样。(这意味着它不能拥有自己的 #version 例如,字符串。)

再一次:那个 包括 以及 包括 必须用相同的 ShaderPackage .