Как обсуждали другие, GLSL не имеет никакой отладки отпечатка. Но иногда я действительно хочу рассматривать числовые значения при отладке моих шейдеров.
Я пытаюсь создать визуальный инструмент отладки.
Я обнаружил, что можно сделать произвольную серию цифр довольно легко в шейдере,
если вы работаете с sampler2D
, в котором цифры 0123456789
были отображены в моноширине.
В принципе, вы просто жонглируете своей координатой x.
Теперь, чтобы использовать это для проверки числа с плавающей запятой, мне нужен алгоритм преобразования float
к последовательности десятичных цифр, например, вы можете найти в любой реализации printf
.
К сожалению, насколько я понимаю тему, эти алгоритмы, похоже, нуждаются в повторном представлении
число с плавающей запятой в формате более высокой точности, и я не вижу, как это будет
возможно в GLSL, где я, кажется, имею только 32-разрядный float
. По этой причине, я думаю
этот вопрос не является дублированием какого-либо общего вопроса "как работает печать", а скорее
в частности, о том, как можно использовать такие алгоритмы для работы под ограничениями GLSL. Я видел этот вопрос и ответ, но понятия не имею, что там происходит.
Алгоритмы, которые я пробовал, не очень хороши. Моя первая попытка, отмеченная версией A (прокомментированная), показалась довольно плохим:
взять три случайных примера, RenderDecimal(1.0)
, оказанные как 1.099999702
, RenderDecimal(2.5)
дал мне
2.599999246
и RenderDecimal(2.6)
вышли как 2.699999280
. Моя вторая попытка, отмеченная версией B, показалась
немного лучше: 1.0
и 2.6
оба выглядят отлично, но RenderDecimal(2.5)
все еще не соответствует кажущемуся
округление 5
с тем, что остаток 0.099...
. Результат
отображается как 2.599000022
.
Мой минимальный/полный/проверяемый пример, ниже, начинается с некоторого короткого кода GLSL 1.20, а затем
Кажется, я выбрал Python 2.x для остальных, просто чтобы скомпилировать шейдеры и текстуры
загружается и отображается. Для этого требуются pygame, numpy, PyOpenGL и PIL сторонние
пакеты. Обратите внимание, что Python действительно просто шаблонный и может быть тривиально (хотя и утомительно) переписан
в С или что-нибудь еще. Только код GLSL наверху имеет решающее значение для этого вопроса, и для этого
Я не думаю, что теги python
или python 2.x
были бы полезны.
Для сохранения требуется следующее изображение: digits.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()