2017-06-28 7 views
8

As others have discussed, fehlt GLSL jede Art von Printf-Debugging. Aber manchmal möchte ich wirklich numerische Werte beim Debuggen meiner Shader untersuchen.Gleitkommazahlen in GLSL in Dezimalzahlen umwandeln?

Ich habe versucht, ein visuelles Debugging-Tool zu erstellen. Ich fand, dass es möglich ist, eine beliebige Reihe von Ziffern ziemlich einfach in einem Shader zu rendern, wenn Sie mit einem sampler2D arbeiten, in dem die Ziffern in Monospace gerendert wurden. Im Grunde jonglieren Sie nur Ihre x-Koordinate.

Nun, dies zu verwenden, um eine Fließkommazahl zu untersuchen, ich brauche einen Algorithmus für ein float auf eine Folge von Dezimalziffern, wie Sie in jeder printf Implementierung finden könnten. Leider as far as I understand the topic diese Algorithmen scheinen die Fließkommazahl in einem höheren Präzisionsformat wieder darzustellen, und ich sehe nicht, wie dies in GLSL möglich sein wird, wo ich scheinen nur 32- zu haben Bit float s verfügbar. Aus diesem Grund denke ich, diese Frage ist kein Duplikat von einem allgemeinen "Wie funktioniert printf Arbeit" Frage, sondern speziell darüber, wie solche Algorithmen gemacht werden können, um unter den Einschränkungen von GLSL zu arbeiten. Ich habe gesehen this question and answer, aber habe keine Ahnung, was dort vor sich geht.

Die Algorithmen, die ich ausprobiert habe, sind nicht sehr gut. Mein erster Versuch, markierte Version A (kommentiert out) schien ziemlich schlecht: drei zufällige Beispiele zu nehmen, RenderDecimal(1.0) als 1.099999702 gemacht, gab RenderDecimal(2.5) mir 2.599999246 und RenderDecimal(2.6) kam als 2.699999280 aus. Mein zweiter Versuch, markierte Version B, schien etwas besser: 1.0 und 2.6 beide kommen in Ordnung, aber RenderDecimal(2.5) nicht übereinstimmt noch eine scheinbare Aufrundung der 5 mit der Tatsache, dass der Rest 0.099... ist. Das Ergebnis erscheint als 2.599000022.

Meine minimal/complete/nachprüfbar Beispiel unten, beginnt mit einigen shortish GLSL 1.20 Code, und dann ich zufällig Python 2.x für den Rest gewählt haben, sondern nur die Shadern zusammengestellt zu erhalten und die Texturen geladen und gerendert Es erfordert die Pakete pygame, numpy, PyOpenGL und PIL von Drittanbietern. Beachten Sie, dass das Python wirklich nur ein Musterbeispiel ist und trivial (wenn auch mühsam) in C oder irgendetwas anderes geschrieben werden könnte. Nur der GLSL-Code an der Spitze ist für diese Frage kritisch, und für diesen Grund glaube ich nicht, dass die python oder python 2.x Tags hilfreich wären.

Es erfordert folgendes Bild als digits.png gespeichert werden:

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 '' 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() 

Antwort

10

+1 für interessantes Problem. War neugierig, also habe ich versucht, das zu programmieren. Ich brauche die Verwendung von Arrays, also wählte ich #version 420 core. Meine App rendert einen einzelnen Quad-Bildschirm mit Koordinaten <-1,+1>.Ich verwende ganze ASCII 8x8 Pixel 32x8 Zeichen Schrift Textur ich vor einigen Jahren erstellt:

font

Der Scheitelpunkt ist einfach:

//--------------------------------------------------------------------------- 
// Vertex 
//--------------------------------------------------------------------------- 
#version 420 core 
//--------------------------------------------------------------------------- 
layout(location=0) in vec4 vertex; 
out vec2 pos; // screen position <-1,+1> 
void main() 
    { 
    pos=vertex.xy; 
    gl_Position=vertex; 
    } 
//--------------------------------------------------------------------------- 

Fragment ein bisschen komplizierter ist:

//--------------------------------------------------------------------------- 
// Fragment 
//--------------------------------------------------------------------------- 
#version 420 core 
//--------------------------------------------------------------------------- 
in vec2 pos;     // screen position <-1,+1> 
out vec4 gl_FragColor;   // fragment output color 
uniform sampler2D txr_font;  // ASCII 32x8 characters font texture unit 
uniform float fxs,fys;   // font/screen resolution ratio 
//--------------------------------------------------------------------------- 
const int _txtsiz=32;   // text buffer size 
int txt[_txtsiz],txtsiz;  // text buffer and its actual size 
vec4 col;      // color interface for txt_print() 
//--------------------------------------------------------------------------- 
void txt_decimal(float x)  // print float x into txt 
    { 
    int i,j,c;   // l is size of string 
    float y,a; 
    const float base=10; 
    // handle sign 
    if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; } 
    else  { txt[txtsiz]='+'; txtsiz++; } 
    // divide to int(x).fract(y) parts of number 
    y=x; x=floor(x); y-=x; 
    // handle integer part 
    i=txtsiz;     // start of integer part 
    for (;txtsiz<_txtsiz;) 
     { 
     a=x; 
     x=floor(x/base); 
     a-=base*x; 
     txt[txtsiz]=int(a)+'0'; txtsiz++; 
     if (x<=0.0) break; 
     } 
    j=txtsiz-1;     // end of integer part 
    for (;i<j;i++,j--)  // reverse integer digits 
     { 
     c=txt[i]; txt[i]=txt[j]; txt[j]=c; 
     } 
    // handle fractional part 
    for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;) 
     { 
     y*=base; 
     a=floor(y); 
     y-=a; 
     txt[txtsiz]=int(a)+'0'; txtsiz++; 
     if (y<=0.0) break; 
     } 
    txt[txtsiz]=0; // string terminator 
    } 
//--------------------------------------------------------------------------- 
void txt_print(float x0,float y0) // print txt at x0,y0 [chars] 
    { 
    int i; 
    float x,y; 
    // fragment position [chars] relative to x0,y0 
    x=0.5*(1.0+pos.x)/fxs; x-=x0; 
    y=0.5*(1.0-pos.y)/fys; y-=y0; 
    // inside bbox? 
    if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return; 
    // get font texture position for target ASCII 
    i=int(x);    // char index in txt 
    x-=float(i); 
    i=txt[i]; 
    x+=float(int(i&31)); 
    y+=float(int(i>>5)); 
    x/=32.0; y/=8.0; // offset in char texture 
    col=texture2D(txr_font,vec2(x,y)); 
    } 
//--------------------------------------------------------------------------- 
void main() 
    { 
    col=vec4(0.0,1.0,0.0,1.0); // background color 
    txtsiz=0; 
    txt[txtsiz]='F'; txtsiz++; 
    txt[txtsiz]='l'; txtsiz++; 
    txt[txtsiz]='o'; txtsiz++; 
    txt[txtsiz]='a'; txtsiz++; 
    txt[txtsiz]='t'; txtsiz++; 
    txt[txtsiz]=':'; txtsiz++; 
    txt[txtsiz]=' '; txtsiz++; 
    txt_decimal(12.345); 
    txt_print(1.0,1.0); 
    gl_FragColor=col; 
    } 
//--------------------------------------------------------------------------- 
Hier

meine CPU-Seite Uniformen:

glUniform1i(glGetUniformLocation(prog_id,"txr_font"),0); 
    glUniform1f(glGetUniformLocation(prog_id,"fxs"),(8.0)/float(xs)); 
    glUniform1f(glGetUniformLocation(prog_id,"fys"),(8.0)/float(ys)); 

wo xs,ys ist meine Bildschirmauflösung. Font ist 8x8 in Einheit 0

Hier Ausgang für das Testfragment Code:

screenshot

Wenn Ihre Gleitkommagenauigkeit aufgrund HW Implementierung verringert wird, dann sollten Sie in hex Druck prüfen, wo keine Genauigkeit Verlust ist vorhanden (binärer Zugriff). Das könnte später auf ganze Zahlen zu decadic Base umgewandelt werden ...

siehe:

[Edit2] alten Stil GLSL Shadern

Ich habe versucht, in den Hafen alter Stil GLSL und plötzlich funktioniert es (bevor es nicht mit vorhandenen Arrays kompilieren würde, aber wenn ich daran denke, habe ichversuchtwas der wahre Grund war).

//--------------------------------------------------------------------------- 
// Vertex 
//--------------------------------------------------------------------------- 
varying vec2 pos; // screen position <-1,+1> 
void main() 
    { 
    pos=gl_Vertex.xy; 
    gl_Position=gl_Vertex; 
    } 
//--------------------------------------------------------------------------- 
//--------------------------------------------------------------------------- 
// Fragment 
//--------------------------------------------------------------------------- 
varying vec2 pos;     // screen position <-1,+1> 
uniform sampler2D txr_font;  // ASCII 32x8 characters font texture unit 
uniform float fxs,fys;   // font/screen resolution ratio 
//--------------------------------------------------------------------------- 
const int _txtsiz=32;   // text buffer size 
int txt[_txtsiz],txtsiz;  // text buffer and its actual size 
vec4 col;      // color interface for txt_print() 
//--------------------------------------------------------------------------- 
void txt_decimal(float x)  // print float x into txt 
    { 
    int i,j,c;   // l is size of string 
    float y,a; 
    const float base=10.0; 
    // handle sign 
    if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; } 
    else  { txt[txtsiz]='+'; txtsiz++; } 
    // divide to int(x).fract(y) parts of number 
    y=x; x=floor(x); y-=x; 
    // handle integer part 
    i=txtsiz;     // start of integer part 
    for (;txtsiz<_txtsiz;) 
     { 
     a=x; 
     x=floor(x/base); 
     a-=base*x; 
     txt[txtsiz]=int(a)+'0'; txtsiz++; 
     if (x<=0.0) break; 
     } 
    j=txtsiz-1;     // end of integer part 
    for (;i<j;i++,j--)  // reverse integer digits 
     { 
     c=txt[i]; txt[i]=txt[j]; txt[j]=c; 
     } 
    // handle fractional part 
    for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;) 
     { 
     y*=base; 
     a=floor(y); 
     y-=a; 
     txt[txtsiz]=int(a)+'0'; txtsiz++; 
     if (y<=0.0) break; 
     } 
    txt[txtsiz]=0; // string terminator 
    } 
//--------------------------------------------------------------------------- 
void txt_print(float x0,float y0) // print txt at x0,y0 [chars] 
    { 
    int i; 
    float x,y; 
    // fragment position [chars] relative to x0,y0 
    x=0.5*(1.0+pos.x)/fxs; x-=x0; 
    y=0.5*(1.0-pos.y)/fys; y-=y0; 
    // inside bbox? 
    if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return; 
    // get font texture position for target ASCII 
    i=int(x);    // char index in txt 
    x-=float(i); 
    i=txt[i]; 
    x+=float(int(i-((i/32)*32))); 
    y+=float(int(i/32)); 
    x/=32.0; y/=8.0; // offset in char texture 
    col=texture2D(txr_font,vec2(x,y)); 
    } 
//--------------------------------------------------------------------------- 
void main() 
    { 
    col=vec4(0.0,1.0,0.0,1.0); // background color 
    txtsiz=0; 
    txt[txtsiz]='F'; txtsiz++; 
    txt[txtsiz]='l'; txtsiz++; 
    txt[txtsiz]='o'; txtsiz++; 
    txt[txtsiz]='a'; txtsiz++; 
    txt[txtsiz]='t'; txtsiz++; 
    txt[txtsiz]=':'; txtsiz++; 
    txt[txtsiz]=' '; txtsiz++; 
    txt_decimal(12.345); 
    txt_print(1.0,1.0); 
    gl_FragColor=col; 
    } 
//--------------------------------------------------------------------------- 
+1

Würde das zweite Mal für die ausgefallene Retro-Schriftart upvote! – HolyBlackCat

+0

Das ist eine beeindruckende Arbeit! Vielen Dank! Aus irgendeinem Grund, obwohl eine meiner Maschinen GLSL> 4.20 (4.30 in der Tat) hat, würde ihr Compiler keine Zeichenliterale wie "-'" erlauben (es heißt 'ERROR: 0:22: '': illegales Zeichen (')) (0x27) '). Also musste ich diese durch ganzzahlige Literale ersetzen, aber dann konnte ich das funktionieren lassen. Sehr cool! Ich hätte erwähnen sollen, dass ich GLSL-Versionen wirklich bis 1.20 unterstützen möchte, aber das sollte nicht von Ihrer Lösung ablenken, besonders da der Kern-Dezimalisierungsalgorithmus aussieht, als sollte er auch dort portierbar sein. – jez

+0

Auch: Ich nehme Ihren Punkt über das Rendern im Binärformat und würde dies gerne tun, da es einfach genug sein sollte, die Bildschirmaufnahme zu automatisieren, gefolgt von der Decodierung einer Sequenz von Ein-Aus-Pixeln. Dies könnte tatsächlich eine nützlichere Allzwecklösung sein. Kann ich das auch in frühen GLSL-Versionen machen? Ich weiß über 'FloatBitsToUint', aber das ist nur in Version 3.30+ verfügbar. Hatten Sie andere Funktionen im Sinn, wenn Sie von "binärem Zugriff" sprachen, und wenn ja, wären sie auf älteren Systemen verfügbar? Das ist eine ganz eigene Frage, die ich vermute ... – jez

3

Zunächst möchte ich erwähnen möchte, dass die erstaunliche Lösung von Spektre fast perfekt ist und auch eine allgemeine Lösung für die Textausgabe. Ich gab seine Antwort upvote. Als Alternative präsentiere ich eine minimal-invasive Lösung, und verbessern Sie den Code der Frage.

Ich möchte nicht verschweigen, dass ich die Lösung von Spektre studiert und in meine Lösung integriert habe.

// Assume that the texture to which uTextureSlotNumber refers contains 
// a rendering of the digits '' 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); 
    } 
} 

Die Funktion ValueToDigits interpretiert eine Gleitkommazahl eine füllt ein Array mit den Ziffern auf. Jede Nummer im Array ist in (0, 9).

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); 
    } 
} 

Anruf ValueToDigits in Ihrer ursprünglichen Funktion und finden Sie die Ziffer und Textur-Koordinaten für das aktuelle Fragment.

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); 
    } 
} 
1

Hier ist meine aktualisierte Fragment-Shader, die in der ursprünglichen Frage in den Eintrag fallen gelassen werden kann. Es implementiert den Dezimalstellen-Suchalgorithmus, den Spektre vorgeschlagen hat, in einer Weise, die sogar mit dem alten GLSL 1.20-Dialekt, den ich verwende, kompatibel ist. Ohne diese Einschränkung ist die Lösung von Spektre natürlich viel eleganter und leistungsfähiger.

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 '' 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 
}