Algorithm 在GLSL中将浮点数转换为十进制数字

Algorithm 在GLSL中将浮点数转换为十进制数字,algorithm,opengl,printf,glsl,shader,Algorithm,Opengl,Printf,Glsl,Shader,,GLSL缺少任何类型的printf调试 但有时我真的希望在调试着色器时检查数值 我一直在尝试创建一个可视化调试工具。 我发现,如果使用sampler2D,其中数字0123456789已在monospace中渲染,则可以在着色器中相当轻松地渲染任意数字序列。基本上,你只需要改变你的x坐标 现在,要使用它来检查浮点数,我需要一个算法来将float转换为十进制数字序列,就像您在任何printf实现中可以找到的那样 不幸的是,这些算法似乎需要表示 更高精度格式的浮点数,我不知道会是什么样子 可能在GL

,GLSL缺少任何类型的printf调试

但有时我真的希望在调试着色器时检查数值

我一直在尝试创建一个可视化调试工具。 我发现,如果使用
sampler2D
,其中数字
0123456789
已在monospace中渲染,则可以在着色器中相当轻松地渲染任意数字序列。基本上,你只需要改变你的x坐标

现在,要使用它来检查浮点数,我需要一个算法来将
float
转换为十进制数字序列,就像您在任何
printf
实现中可以找到的那样

不幸的是,这些算法似乎需要表示 更高精度格式的浮点数,我不知道会是什么样子 可能在GLSL中,我似乎只有32位的
float
s可用

出于这个原因,我认为这个问题不是任何一般的“printf如何工作”问题的重复,而是关于如何在GLSL的约束下使这些算法工作的问题。我见过,但不知道那里发生了什么

我尝试过的算法不是很好

我的第一次尝试,标记的版本A(注释掉)看起来很糟糕: 举三个随机的例子,
renderecimal(1.0)
呈现为
1.09999702
renderecimal(2.5)
给了我
2.599999246
RenderDecimal(2.6)
2.699999280
的形式发布

我的第二次尝试,标记为版本B,似乎 稍微好一点:
1.0
2.6
都很好,但是
renderecimal(2.5)
仍然与一个明显的 将
5
四舍五入,剩余值为
0.099…
。结果显示为
2.599000022

下面我的最小/完整/可验证示例以一些简短的GLSL 1.20代码开始,然后 我碰巧选择了Python2.x作为其余部分,只是为了编译着色器并加载和渲染纹理。它需要pygame、NumPy、PyOpenGL和PIL第三方软件包。请注意,Python实际上只是一个样板文件,可以用C或其他任何语言重新编写(尽管很繁琐)。对于这个问题,只有顶部的GLSL代码才是关键,因此我认为
python
python2.x
标记不会有帮助

它要求将以下图像保存为
数字。png

vertexShaderSource = """\

varying vec2 vFragCoordinate;
void main(void)
{
    vFragCoordinate = gl_Vertex.xy;
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

"""
fragmentShaderSource = """\

varying vec2      vFragCoordinate;

uniform vec2      uTextureSize;
uniform sampler2D uTextureSlotNumber;

float OrderOfMagnitude( float x )
{
    return x == 0.0 ? 0.0 : floor( log( abs( x ) ) / log( 10.0 ) );
}
void RenderDecimal( float value )
{
    // Assume that the texture to which uTextureSlotNumber refers contains
    // a rendering of the digits '0123456789' packed together, such that
    const vec2 startOfDigitsInTexture = vec2( 0, 0 ); // the lower-left corner of the first digit starts here and
    const vec2 sizeOfDigit = vec2( 100, 125 ); // each digit spans this many pixels
    const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in
    
    value = abs( value );
    vec2 pos = vFragCoordinate - startOfDigitsInTexture;
    float dpstart = max( 0.0, OrderOfMagnitude( value ) );
    float decimal_position = dpstart - floor( pos.x / sizeOfDigit.x );
    float remainder = mod( pos.x, sizeOfDigit.x );
    
    if( pos.x >= 0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0 && pos.y < sizeOfDigit.y  )
    {
        float digit_value;
        
        // Version B
        float dp, running_value = value;
        for( dp = dpstart; dp >= decimal_position; dp -= 1.0 )
        {
            float base = pow( 10.0, dp );
            digit_value = mod( floor( running_value / base ), 10.0 );
            running_value -= digit_value * base;
        }
        
        // Version A
        //digit_value = mod( floor( value * pow( 10.0, -decimal_position ) ), 10.0 );
        
        

        vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + remainder + digit_value * sizeOfDigit.x, startOfDigitsInTexture.y + pos.y );
        gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
    }
    
    // Render the decimal point
    if( ( decimal_position == -1.0 && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
        ( decimal_position ==  0.0 && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
    {
        gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
    }
}

void main(void)
{
    gl_FragColor = texture2D( uTextureSlotNumber, vFragCoordinate / uTextureSize );
    RenderDecimal( 2.5 ); // for current demonstration purposes, just a constant
}

"""

# Python (PyOpenGL) code to demonstrate the above
# (Note: the same OpenGL calls could be made from any language)

import os, sys, time

import OpenGL
from OpenGL.GL import *
from OpenGL.GLU import *

import pygame, pygame.locals # just for getting a canvas to draw on

try: from PIL import Image  # PIL.Image module for loading image from disk
except ImportError: import Image  # old PIL didn't package its submodules on the path

import numpy # for manipulating pixel values on the Python side

def CompileShader( type, source ):
    shader = glCreateShader( type )
    glShaderSource( shader, source )
    glCompileShader( shader )
    result = glGetShaderiv( shader, GL_COMPILE_STATUS )
    if result != 1:
        raise Exception( "Shader compilation failed:\n" + glGetShaderInfoLog( shader ) )
    return shader

class World:
    def __init__( self, width, height ):

        self.window = pygame.display.set_mode( ( width, height ), pygame.OPENGL | pygame.DOUBLEBUF )

        # compile shaders
        vertexShader = CompileShader( GL_VERTEX_SHADER, vertexShaderSource )
        fragmentShader = CompileShader( GL_FRAGMENT_SHADER, fragmentShaderSource )
        # build shader program
        self.program = glCreateProgram()
        glAttachShader( self.program, vertexShader )
        glAttachShader( self.program, fragmentShader )
        glLinkProgram( self.program )
        # try to activate/enable shader program, handling errors wisely
        try:
            glUseProgram( self.program )
        except OpenGL.error.GLError:
            print( glGetProgramInfoLog( self.program ) )
            raise

        # enable alpha blending
        glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE )
        glEnable( GL_DEPTH_TEST )
        glEnable( GL_BLEND )
        glBlendEquation( GL_FUNC_ADD )
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )

        # set projection and background color
        gluOrtho2D( 0, width, 0, height )
        glClearColor( 0.0, 0.0, 0.0, 1.0 )
        
        self.uTextureSlotNumber_addr = glGetUniformLocation( self.program, 'uTextureSlotNumber' )
        self.uTextureSize_addr = glGetUniformLocation( self.program, 'uTextureSize' )

    def RenderFrame( self, *textures ):
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
        for t in textures: t.Draw( world=self )
        pygame.display.flip()
        
    def Close( self ):
        pygame.display.quit()
        
    def Capture( self ):
        w, h = self.window.get_size()
        rawRGB = glReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE )
        return Image.frombuffer( 'RGB', ( w, h ), rawRGB, 'raw', 'RGB', 0, 1 ).transpose( Image.FLIP_TOP_BOTTOM )
    
class Texture:
    def __init__( self, source, slot=0, position=(0,0,0) ):
        
        # wrangle array
        source = numpy.array( source )
        if source.dtype.type not in [ numpy.float32, numpy.float64 ]: source = source.astype( float ) / 255.0
        while source.ndim < 3: source = numpy.expand_dims( source, -1 )
        if source.shape[ 2 ] == 1: source = source[ :, :, [ 0, 0, 0 ] ]    # LUMINANCE -> RGB
        if source.shape[ 2 ] == 2: source = source[ :, :, [ 0, 0, 0, 1 ] ] # LUMINANCE_ALPHA -> RGBA
        if source.shape[ 2 ] == 3: source = source[ :, :, [ 0, 1, 2, 2 ] ]; source[ :, :, 3 ] = 1.0  # RGB -> RGBA
        # now it can be transferred as GL_RGBA and GL_FLOAT
        
        # housekeeping
        self.textureSize = [ source.shape[ 1 ], source.shape[ 0 ] ]
        self.textureSlotNumber = slot
        self.textureSlotCode = getattr( OpenGL.GL, 'GL_TEXTURE%d' % slot )
        self.listNumber = slot + 1
        self.position = list( position )
        
        # transfer texture content
        glActiveTexture( self.textureSlotCode )
        self.textureID = glGenTextures( 1 )
        glBindTexture( GL_TEXTURE_2D, self.textureID )
        glEnable( GL_TEXTURE_2D )
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA32F, self.textureSize[ 0 ], self.textureSize[ 1 ], 0, GL_RGBA, GL_FLOAT, source[ ::-1 ] )
        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST )
        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST )

        # define surface
        w, h = self.textureSize
        glNewList( self.listNumber, GL_COMPILE )
        glBegin( GL_QUADS )
        glColor3f( 1, 1, 1 )
        glNormal3f( 0, 0, 1 )
        glVertex3f( 0, h, 0 )
        glVertex3f( w, h, 0 )
        glVertex3f( w, 0, 0 )
        glVertex3f( 0, 0, 0 )
        glEnd()
        glEndList()
        
    def Draw( self, world ):
        glPushMatrix()
        glTranslate( *self.position )
        glUniform1i( world.uTextureSlotNumber_addr, self.textureSlotNumber )
        glUniform2f( world.uTextureSize_addr, *self.textureSize )
        glCallList( self.listNumber )
        glPopMatrix()
        

world = World( 1000, 800 )
digits = Texture( Image.open( 'digits.png' ) )
done = False
while not done:
    world.RenderFrame( digits )
    for event in pygame.event.get():
        # Press 'q' to quit or 's' to save a timestamped snapshot
        if event.type  == pygame.locals.QUIT: done = True
        elif event.type == pygame.locals.KEYUP and event.key in [ ord( 'q' ), 27 ]: done = True
        elif event.type == pygame.locals.KEYUP and event.key in [ ord( 's' ) ]:
            world.Capture().save( time.strftime( 'snapshot-%Y%m%d-%H%M%S.png' ) )
world.Close()

vertexShaderSource=”“”\
可变矢量2坐标;
真空总管(真空)
{
vFragCoordinate=gl_Vertex.xy;
gl_位置=gl_模型视图投影矩阵*gl_顶点;
}
"""
fragmentShaderSource=“”\
可变矢量2坐标;
统一的vec2文本大小;
均匀取样器2D uTextureSlotNumber;
浮点数量级(浮点x)
{
返回x==0.0?0.0:floor(log(abs(x))/log(10.0));
}
void renderCIMAL(浮动值)
{
//假设uTextureSlotNumber引用的纹理包含
//压缩在一起的数字“0123456789”的呈现,以便
const vec2 startofdigitintexture=vec2(0,0);//第一个数字的左下角从这里开始,然后
const vec2 sizeOfDigit=vec2(100125);//每个数字跨越这么多像素
const float nSpaces=10.0;//假设我们有这么多数字的空间来渲染
值=abs(值);
vec2 pos=vFragCoordinate-开始数字集成;
浮点dpstart=max(0.0,数量级(值));
浮点十进制位置=dpstart-地板(位置x/sizeOfDigit.x);
浮动余数=模(位置x,尺寸F数字x);
如果(pos.x>=0&&pos.x=0&&pos.y=十进制位置;dp-=1.0)
{
浮动底座=功率(10.0,dp);
数字_值=mod(地板(运行_值/基础),10.0);
运行_值-=数字_值*基数;
}
//版本A
//数字值=模(地板(值*功率(10.0,-十进制位置)),10.0);
vec2 textureSourcePosition=vec2(StartToDigitIntexture.x+余数+位数值*sizeOfDigit.x,StartToDigitIntexture.y+位置y);
gl_FragColor=纹理2d(uTextureSlotNumber,纹理资源位置/uTextureSize);
}
//渲染小数点
if((十进制位置==-1.0和余数/sizeOfDigit.x<0.1和abs(pos.y)/sizeOfDigit.y<0.1)||
(十进制位置==0.0和余数/sizeOfDigit.x>0.9和abs(pos.y)/sizeOfDigit.y<0.1))
{
gl_FragColor=texture2D(uTextureSlotNumber,(startoDigiitsIntexture+sizeOfDigit*vec2(1.5,0.5))/uTextureSize;
}
}
真空总管(真空)
{
gl_FragColor=纹理2d(uTextureSlotNumber,vFragCoordinate/uTextureSize);
renderCIMAL(2.5);//对于当前的演示,只需一个常量
}
"""
#Python(PyOpenGL)代码来演示上述内容
#(注意:任何语言都可以进行相同的OpenGL调用)
导入操作系统、系统、时间
导入OpenGL
从OpenGL.GL导入*
从OpenGL.GLU导入*
导入pygame,pygame.locals#只是为了得到一张画布来画画
try:from PIL import Image#PIL.Image模块用于从磁盘加载映像
除了ImportError:import Image#old PIL没有在路径上打包其子模块
导入numpy#用于在Python端操纵像素值
def编译头(类型、源):
着色器=glCreateShader(类型)
glShaderSource(着色器,源)
glCompileShader(着色器)
结果=glGetShaderiv(着色器,GL\u编译\u状态)
如果有结果!=1:
引发异常(“着色器编译失败:\n”+glGetShaderInfoLog(着色器))
重新
// Assume that the texture to which uTextureSlotNumber refers contains
// a rendering of the digits '0123456789' packed together, such that
const vec2 startOfDigitsInTexture = vec2( 100, 125 ); // the lower-left corner of the first digit starts here and
const vec2 sizeOfDigit = vec2( 0.1, 0.2 ); // each digit spans this many pixels
const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in

void RenderDigit( int strPos, int digit, vec2 pos )
{
    float testStrPos = pos.x / sizeOfDigit.x;
    if ( testStrPos >= float(strPos) && testStrPos < float(strPos+1) )
    {
        float start = sizeOfDigit.x * float(digit);
        vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + start + mod( pos.x, sizeOfDigit.x ),     startOfDigitsInTexture.y + pos.y );
        gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
    }
}
const int MAX_DIGITS = 32;
int       digits[MAX_DIGITS];
int       noOfDigits = 0;
int       posOfComma = 0;

void Reverse( int start, int end )
{
    for ( ; start < end; ++ start, -- end )
    {
        int digit = digits[start];
        digits[start] = digits[end];
        digits[end] = digit;
    }
}

void ValueToDigits( float value )
{
    const float base = 10.0;
    int start = noOfDigits;

    value = abs( value );
    float frac = value; value = floor(value); frac -= value;

    // integral digits
    for ( ; value > 0.0 && noOfDigits < MAX_DIGITS; ++ noOfDigits )
    {
        float newValue = floor( value / base );
        digits[noOfDigits] = int( value - base * newValue );
        value = newValue;
    }
    Reverse( start, noOfDigits-1 );

    posOfComma = noOfDigits;

    // fractional digits
    for ( ; frac > 0.0 && noOfDigits < MAX_DIGITS; ++ noOfDigits )
    {
        frac *= base;
        float digit = floor( frac );
        frac -= digit;
        digits[noOfDigits] = int( digit );
    }
}
void RenderDecimal( float value )
{
    // fill the array of digits with the floating point value
    ValueToDigits( value );

    // Render the digits
    vec2 pos = vFragCoordinate.xy - startOfDigitsInTexture;
    if( pos.x >= 0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0 && pos.y < sizeOfDigit.y  )
    {
        // render the digits
        for ( int strPos = 0; strPos < noOfDigits; ++ strPos )
            RenderDigit( strPos, digits[strPos], pos );
    }

    // Render the decimal point
    float testStrPos = pos.x / sizeOfDigit.x;
    float remainder = mod( pos.x, sizeOfDigit.x );
    if( ( testStrPos >= float(posOfComma) && testStrPos < float(posOfComma+1) && remainder / sizeOfDigit.x < 0.1 && abs( pos.y     ) / sizeOfDigit.y < 0.1 ) ||
        ( testStrPos >= float(posOfComma-1) && testStrPos < float(posOfComma) && remainder / sizeOfDigit.x > 0.9 && abs( pos.y     ) / sizeOfDigit.y < 0.1 ) )
    {
        gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) /     uTextureSize );
    }
}
varying vec2      vFragCoordinate;

uniform vec2      uTextureSize;
uniform sampler2D uTextureSlotNumber;

float Digit( float x, int position, float base )
{
    int i;
    float digit;

    if( position < 0 )
    {
        x = fract( x );
        for( i = -1; i >= position; i-- )
        {
            if( x <= 0.0 ) { digit = 0.0; break; }
            x *= base;
            digit = floor( x );
            x -= digit;
        }
    }
    else
    {
        x = floor( x );
        float prevx;
        for( i = 0; i <= position; i++ )
        {
            if( x <= 0.0 ) { digit = 0.0; break; }
            prevx = x;
            x = floor( x / base );
            digit = prevx - base * x;
        }
    }
    return digit;
}

float OrderOfMagnitude( float x )
{
    return x == 0.0 ? 0.0 : floor( log( abs( x ) ) / log( 10.0 ) );
}
void RenderDecimal( float value )
{
    // Assume that the texture to which uTextureSlotNumber refers contains
    // a rendering of the digits '0123456789' packed together, such that
    const vec2 startOfDigitsInTexture = vec2( 0, 0 ); // the lower-left corner of the first digit starts here and
    const vec2 sizeOfDigit = vec2( 100, 125 ); // each digit spans this many pixels
    const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in

    value = abs( value );
    vec2 pos = vFragCoordinate - startOfDigitsInTexture;
    float dpstart = max( 0.0, OrderOfMagnitude( value ) );
    int decimal_position = int( dpstart - floor( pos.x / sizeOfDigit.x ) );
    float remainder = mod( pos.x, sizeOfDigit.x );

    if( pos.x >= 0.0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0.0 && pos.y < sizeOfDigit.y  )
    {
        float digit_value = Digit( value, decimal_position, 10.0 );
        vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + remainder + digit_value * sizeOfDigit.x, startOfDigitsInTexture.y + pos.y );
        gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
    }

    // Render the decimal point
    if( ( decimal_position == -1 && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
        ( decimal_position ==  0 && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
    {
        gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
    }
}

void main(void)
{
    gl_FragColor = texture2D( uTextureSlotNumber, vFragCoordinate / uTextureSize );
    RenderDecimal( 2.5 ); // for current demonstration purposes, just a constant
}