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()
Würde das zweite Mal für die ausgefallene Retro-Schriftart upvote! – HolyBlackCat
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
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