立体渲染
这最后一步的坐标“形象”,并重复平铺图像作为输入,并简单地通过在合适的位置采样平铺图像最终渲染图像。 它会从输入的坐标“图像“得到水平纹理坐标。他将从输出的坐标"图像"中计算垂直坐标(只是简单的重复自身).
这个采样过程是在GPU上通过自定义着色器完成的。 一个屏幕对齐的四边形开始呈现,接下来的像素着色器则被用来计算最终的颜色渲染。
#version 150
smooth in vec2 vTexCoord;
out vec4 outColor;
// Sampler for the generated offset texture.
uniform sampler2D uOffsetTexture;
// Sampler for the repeating pattern texture.
uniform sampler2D uPatternTexture;
// Scaling factor (i.e. ratio of height of two previous textures).
uniform float uScaleFactor;
void main( )
{
// The horizontal lookup coordinate comes directly from the
// computed offsets stored in the offset texture.
float lOffsetX = texture( uOffsetTexture, vTexCoord ).x;
// The vertical coordinate is computed using a scaling factor
// to map between the coordinates in the input height texture
// (i.e. vTexCoord.y) and where to look up in the repeating pattern.
// The scaling facture is the ratio of the two textures' height.
float lOffsetY = ( vTexCoord.y * uScaleFactor );
vec2 lCoords = vec2( lOffsetX , lOffsetY );
outColor = texture( uPatternTexture , lCoords );
};
这样就完成了算法概述。下一节介绍了CPU执行的坐标生成阶段。
CPU实现
在CPU上实现的算法只涉及到从输入的深度值产生偏移量(即纹理坐标)。上面的给出的是一段简单的C++版本的伪代码。这个算法主要分为三个步骤:
首先,从GPU中读取深度值到CPU
然后,根据深度值产生相应的偏移量
最后,将计算产生的偏移量从CPU写回GPU
第一步 : 从GPU中读取深度值
在进行场景渲染之后,深度值会被保存在GPU的一个纹理中。为了得到CPU算法中所需要的深度值,必须首先从GPU中获取深度值并保存到CPU能够访问的内存中。在这里,一个标准的浮点向量std::vector被用来保存这些用于在CPU进行计算的深度值。实现的代码如下:
// 从GPU中读取深度值.
glBindTexture( GL_TEXTURE_2D , mDepthTexture );
glGetTexImage(
GL_TEXTURE_2D ,
0 ,
GL_DEPTH_COMPONENT ,
GL_FLOAT ,
mInputDepths.data()
);
glBindTexture( GL_TEXTURE_2D , 0 );
深度值将被储存在 mInputDepths 这个浮点型向量中。
第二步 : 计算偏移量
计算偏移量就是简单地实现了上面的伪代码所描述的过程并将计算结果保存到内存数组中。下面的代码展示了如何将输入的深度值转换成相应的偏移量输出。
const int lPatternWidth = pPatternRenderer.GetPatternWidth();
const int lStereogramWidth = kSceneWidth + lPatternWidth;
for ( int j = 0; j < kSceneHeight; ++j )
{
// 首先初始化偏移量数组.
for ( int i = 0, lCountI = lPatternWidth; i < lCountI; ++i )
{
float& lOutput = mOutputOffsets[ j * lStereogramWidth + i ];
lOutput = i / static_cast< float >( lPatternWidth );
}
// 然后计算偏移量.
for ( int i = lPatternWidth; i < lStereogramWidth; ++i )
{
float& lOutput = mOutputOffsets[ j * lStereogramWidth + i ];
// 得到该像素所对应的深度值.
const int lInputI = i - lPatternWidth;
const float lDepthValue = mInputDepths[ j * kSceneWidth + lInputI ];
// Get where to look up for the offset value.
const float lLookUpPos = static_cast< float >( lInputI ) + kMaxOffset * ( 1 - lDepthValue );
// 在两个像素之间进行线性插值.
const int lPos1 = static_cast< int >( lLookUpPos );
const int lPos2 = lPos1 + 1;
const float lFrac = lLookUpPos - lPos1;
const float lValue1 = mOutputOffsets[ j * lStereogramWidth + lPos1 ];
const float lValue2 = mOutputOffsets[ j * lStereogramWidth + lPos2 ];
// 我们对线性插值的量加1以保证偏移量在一个给定的行中总是递增(以保证任何偏移量之间的线性插值都是有意义的)
const float lValue = 1.0f + ( lValue1 + lFrac * ( lValue2 - lValue1 ) );
lOutput = lValue;
}
}
第三步 : 将偏移量从CPU写入GPU
在上一步偏移量计算结束以后,这些值必须被写回GPU中进行渲染。这个操作和上面第一步的操作正好相反,具体实现的代码如下:
glBindTexture( GL_TEXTURE_2D , mOffsetTexture );
glTexSubImage2D(
GL_TEXTURE_2D ,
0 ,
0 ,
0 ,
lStereogramWidth ,
kSceneHeight ,
GL_RED ,
GL_FLOAT ,
mOutputOffsets.data()
);
glBindTexture( GL_TEXTURE_2D , mOffsetTexture );
偏移量将会被写入到GPU的存储器中。
这样就完成了算法的CPU实现。这种方法的最大缺点是每一帧都需要在CPU和GPU之间进行大量的数据交换。从GPU进行图像数据的读取然后再写回GPU,这严重影响了实时程序的性能。
为了防止这个问题,第二步的处理将直接在GPU的存储器上执行,以避免CPU和GPU之间的往返读写。这一方法将在下面的部分中进行具体描述。
GPU 实现
为了避免CPU和GPU之间不必要的往返读写,深度数据应该直接在GPU上进行处理。然而,stereogram生成算法需要得到先前输出图像同一行中设置的值。和使用片段着色器一样,对相同的纹理/图像缓冲区同时进行读取和写入对于传统GPU来说是非常不友好的的处理方法。
这里可以使用一个“带”为基础的方法,其中垂直条带会被从左至右呈现出来,每个频带的大小都不会超过左边的最小距离。在所提供的例子的源代码中可以看到,重复图案的宽度是85个像素,而最大的偏移量是30个像素(kMaxOffset的值),所以产生的最大的频带宽度为55个像素。由于不能过对于将要写入的纹理进行随机读取的操作,因此被渲染的纹理必须同时保存两个副本:一个用于读取,一个用于写入。那么刚才所写的必须被复制到另一个的纹理中去。
这种使用两个纹理的方法并不是最佳的。另外,频带的宽度对渲染的次数有直接的影响,这也将对性能产生直接的影响。不过,这个宽度是依赖于重复图案的,它可以根据具体的情况而改变,同时最大偏移量也是一个可以根据实时性需求改变的参数。性能会受到参数变化的影响,这并不是理想的情况。
一种更加灵活的方法是使用可编程渲染管线。使用OpenCL。GPGPU的API中“通用”的部分在类似的应用程序中发挥着十分重要的作用。这将允许使用GPU进行更通用,而非面向渲染的算法。这种灵活性使得我们能够有效地利用GPU进行立体图的生成。
首先,我们需要对前面CPU实现的算法做一些改变。然后对创建一个OpenCL的上下文,以及利用OpenCL对OpenGL的上下文的共享资源的使用进行说明。最后,将对使用OpenCL核函数来产生立体图的方法以及所需的要素进行展示。
渲染场景要做出的修改
CPU版本的深度贴图算法不能被用于GPU。这贴图会同时被OpenCL使用,而OpenCL能直接使用的OpenGL贴图格式有限。依据文档clCreateFromGLTexture2D其中提到的支持的图像通道格式,GL_DEPTH_COMPONENT32不是可以被OpenCL使用的图像格式,非常不幸,因为这个图像格式和我们想要使用的非常像,但是我们可以避开这个问题。
为从场景渲染步骤中获取深度纹理,第二个纹理对象将填充到帧缓冲区。切记只有单一深度纹理会附属于CPU版本。这个深度纹理仍然需要填充到深度缓冲区进行深度测试才能显示。不管怎样,另一个纹理会作为成颜色填充除非接收到相应颜色单元值,它将会接收深度值。下面的代码展示了如何创建纹理以及如何将它帧缓冲区对象。
// Skipped code to allocate depth texture...
// DIFFERENCE FROM CPU IMPLEMENTATION
// However, because OpenCL can't bind itself to depth textures, we also create
// a "normal" floating point texture that will also hold depths.
// This texture will be the input for our stereogram generation algorithm.
glGenTextures( 1 , &mColorTexture );
glBindTexture( GL_TEXTURE_2D , mColorTexture );
glTexImage2D(
GL_TEXTURE_2D ,
0 ,
GL_R32F ,
kSceneWidth ,
kSceneHeight ,
0 ,
GL_RED ,
GL_FLOAT ,
0
);
glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE );
glBindTexture( GL_TEXTURE_2D , 0 );
// Create a framebuffer object to render directly to the depth texture.
glGenFramebuffers( 1 , &mDepthFramebufferObject );
// Attach the depth texture and the color texture (to which depths will be output)
glBindFramebuffer( GL_FRAMEBUFFER , mDepthFramebufferObject );
glFramebufferTexture2D(
GL_FRAMEBUFFER ,
GL_DEPTH_ATTACHMENT ,
GL_TEXTURE_2D ,
mDepthTexture ,
0
);
glFramebufferTexture2D(
GL_FRAMEBUFFER ,
GL_COLOR_ATTACHMENT0 ,
GL_TEXTURE_2D ,
mColorTexture ,
0
);
glBindFramebuffer( GL_FRAMEBUFFER , 0 );
片断着色器会使用深度值对颜色填充进行渲染。正如下面代码所示的那样简洁。
#version 150
out vec4 outColor;
void main( )
{
float lValue = gl_FragCoord.z;
outColor = vec4( lValue , lValue , lValue , 1.0 );
}
这些修改将使纹理适用于 withclCreateFromGLTexture2D(),以便于在OpenCL的上下文共享,正如下面的部分展示的那样。
创建OpenCL context
通常执行如下步骤来创建一个OpenCL context:
List OpenCL platforms and choose one (usually the first one).
List OpenCL devices on this platform and choose one (usually the first one).
Create an OpenCL context on this device.
然而,对于多边形生成算法而言,必须注意合理分配OpenCL context,才能从现存的context中访问OpenCL资源。额外的参数将会被传递给OpenCL context创建例程来请求一个兼容的context。这意味着context创建可能会失败,例如 OpenGL context创建在一个我们试图分配一个OpenCL context的设备上。因此,创建步骤需要作适当修改以增强兼容性。
List OpenCL platforms and choose one (usually the first one).
List OpenCL devices on this platform
For each device:
Try to allocate a context
on this device
compatible with current OpenGL context
if context successfully created:
stop