我需要调试一个GLSL程序,但我不知道如何输出中间结果。 是否可以使用 GLSL 进行一些调试跟踪(如 printf)?
您无法轻松地从 GLSL 内与 CPU 进行通信。使用 glslDevil 或其他工具是最好的选择。
printf 需要尝试从运行 GLSL 代码的 GPU 返回到 CPU。相反,您可以尝试向前推至显示屏。不要尝试输出文本,而是输出一些在屏幕上视觉上独特的内容。例如,只有当您到达代码中想要添加 printf 的位置时,您才能将某些内容绘制为特定颜色。如果您需要打印一个值,您可以根据该值设置颜色。
void main(){
float bug=0.0;
vec3 tile=texture2D(colMap, coords.st).xyz;
vec4 col=vec4(tile, 1.0);
if(something) bug=1.0;
col.x+=bug;
gl_FragColor=col;
}
GLSL Sandbox 对于着色器来说对我来说非常方便。
本身不进行调试(已被回答为无能力),但可以方便地快速查看输出的变化。
您可以尝试这个:https://github.com/msqrt/shader-printf,这是一个适当地称为“GLSL 的简单 printf 功能”的实现。
您可能还想尝试 ShaderToy,也许还可以观看“The Art of Code”YouTube 频道中的类似视频 (https://youtu.be/EBrAdahFtuo),您可以在其中看到一些行之有效的技术用于调试和可视化。我强烈推荐他的频道,因为他写了一些非常好的东西,而且他还擅长以新颖、高度吸引人和易于消化的格式呈现复杂的想法(他的 Mandelbrot 视频就是一个极好的例子:https:/ /youtu.be/6IWXkV82oyY)
我希望没人介意这个迟到的回复,但这个问题在 Google GLSL 调试搜索中排名靠前,当然,9 年来发生了很多变化:-)
PS:其他替代方案还可以是 NVIDIA nSight 和 AMD ShaderAnalyzer,它们为着色器提供完整的步进调试器。
如果你想可视化某个值在屏幕上的变化,你可以使用与此类似的热图函数(我用hlsl编写的,但很容易适应glsl):
float4 HeatMapColor(float value, float minValue, float maxValue)
{
#define HEATMAP_COLORS_COUNT 6
float4 colors[HEATMAP_COLORS_COUNT] =
{
float4(0.32, 0.00, 0.32, 1.00),
float4(0.00, 0.00, 1.00, 1.00),
float4(0.00, 1.00, 0.00, 1.00),
float4(1.00, 1.00, 0.00, 1.00),
float4(1.00, 0.60, 0.00, 1.00),
float4(1.00, 0.00, 0.00, 1.00),
};
float ratio=(HEATMAP_COLORS_COUNT-1.0)*saturate((value-minValue)/(maxValue-minValue));
float indexMin=floor(ratio);
float indexMax=min(indexMin+1,HEATMAP_COLORS_COUNT-1);
return lerp(colors[indexMin], colors[indexMax], ratio-indexMin);
}
然后在像素着色器中,您只需输出类似以下内容:
return HeatMapColor(myValue, 0.00, 50.00);
并且可以了解它如何随像素变化:
当然,您可以使用任何您喜欢的颜色。
此答案的底部是 GLSL 代码的示例,它允许将完整的
float
值输出为颜色,编码 IEEE 754 binary32
。我像下面这样使用它(这个片段给出了模型视图矩阵的 yy
分量):
vec4 xAsColor=toColor(gl_ModelViewMatrix[1][1]);
if(bool(1)) // put 0 here to get lowest byte instead of three highest
gl_FrontColor=vec4(xAsColor.rgb,1);
else
gl_FrontColor=vec4(xAsColor.a,0,0,1);
在屏幕上显示此内容后,您可以使用任何颜色选择器,将颜色格式化为 HTML(如果不需要更高的精度,则将
00
附加到 rgb
值,然后进行第二遍以获得较低的精度)字节(如果这样做的话),并且您将获得 float
的十六进制表示形式为 IEEE 754 binary32
。
这是
toColor()
的实际实现(您可以在ShaderToy上玩它):
const int emax=127;
// Input: x>=0
// Output: base 2 exponent of x if (x!=0 && !isnan(x) && !isinf(x))
// -emax if x==0
// emax+1 otherwise
int floorLog2(float x)
{
if(x==0.) return -emax;
// NOTE: there exist values of x, for which floor(log2(x)) will give wrong
// (off by one) result as compared to the one calculated with infinite precision.
// Thus we do it in a brute-force way.
for(int e=emax;e>=1-emax;--e)
if(x>=exp2(float(e))) return e;
// If we are here, x must be infinity or NaN
return emax+1;
}
// Input: any x
// Output: IEEE 754 biased exponent with bias=emax
int biasedExp(float x) { return emax+floorLog2(abs(x)); }
// Input: any x such that (!isnan(x) && !isinf(x))
// Output: significand AKA mantissa of x if !isnan(x) && !isinf(x)
// undefined otherwise
float significand(float x)
{
// converting int to float so that exp2(genType) gets correctly-typed value
float expo=float(floorLog2(abs(x)));
return abs(x)/exp2(expo);
}
// Input: x\in[0,1)
// N>=0
// Output: Nth byte as counted from the highest byte in the fraction
int part(float x,int N)
{
// All comments about exactness here assume that underflow and overflow don't occur
const float byteShift=256.;
// Multiplication is exact since it's just an increase of exponent by 8
for(int n=0;n<N;++n)
x*=byteShift;
// Cut higher bits away.
// $q \in [0,1) \cap \mathbb Q'.$
float q=fract(x);
// Shift and cut lower bits away. Cutting lower bits prevents potentially unexpected
// results of rounding by the GPU later in the pipeline when transforming to TrueColor
// the resulting subpixel value.
// $c \in [0,255] \cap \mathbb Z.$
// Multiplication is exact since it's just and increase of exponent by 8
float c=floor(byteShift*q);
return int(c);
}
// Input: any x acceptable to significand()
// Output: significand of x split to (8,8,8)-bit data vector
ivec3 significandAsIVec3(float x)
{
ivec3 result;
float sig=significand(x)/2.; // shift all bits to fractional part
result.x=part(sig,0);
result.y=part(sig,1);
result.z=part(sig,2);
return result;
}
// Input: any x such that !isnan(x)
// Output: IEEE 754 defined binary32 number, packed as ivec4(byte3,byte2,byte1,byte0)
ivec4 packIEEE754binary32(float x)
{
int e = biasedExp(x);
// sign to bit 7
int s = x<0. ? 128 : 0;
ivec4 binary32;
binary32.yzw=significandAsIVec3(x);
// clear the implicit integer bit of significand
if(binary32.y>=128) binary32.y-=128;
// put lowest bit of exponent into its position, replacing just cleared integer bit
binary32.y+=128*int(mod(float(e),2.));
// prepare high bits of exponent for fitting into their positions
e/=2;
// pack highest byte
binary32.x=e+s;
return binary32;
}
vec4 toColor(float x)
{
ivec4 binary32=packIEEE754binary32(x);
// Transform color components to [0,1] range.
// Division is inexact, but works reliably for all integers from 0 to 255 if
// the transformation to TrueColor by GPU uses rounding to nearest or upwards.
// The result will be multiplied by 255 back when transformed
// to TrueColor subpixel value by OpenGL.
return vec4(binary32)/255.;
}
我正在分享一个片段着色器示例,我如何实际调试。
#version 410 core
uniform sampler2D samp;
in VS_OUT
{
vec4 color;
vec2 texcoord;
} fs_in;
out vec4 color;
void main(void)
{
vec4 sampColor;
if( texture2D(samp, fs_in.texcoord).x > 0.8f) //Check if Color contains red
sampColor = vec4(1.0f, 1.0f, 1.0f, 1.0f); //If yes, set it to white
else
sampColor = texture2D(samp, fs_in.texcoord); //else sample from original
color = sampColor;
}
GLSL Shader源代码由图形驱动程序编译和链接并在GPU上执行。
如果你想调试着色器,那么你必须使用图形调试器,如RenderDoc或NVIDIA Nsight。
现有的答案都是好东西,但我想分享一个小宝石,它对于调试 GLSL 着色器中棘手的精度问题很有价值。对于表示为浮点数的非常大的 int 数字,需要注意正确使用 Floor(n) 和 Floor(n + 0.5) 来将 round() 实现为精确的 int。然后可以通过以下逻辑渲染一个精确 int 的浮点值,将字节组件打包为 R、G 和 B 输出值。
// Break components out of 24 bit float with rounded int value
// scaledWOB = (offset >> 8) & 0xFFFF
float scaledWOB = floor(offset / 256.0);
// c2 = (scaledWOB >> 8) & 0xFF
float c2 = floor(scaledWOB / 256.0);
// c0 = offset - (scaledWOB << 8)
float c0 = offset - floor(scaledWOB * 256.0);
// c1 = scaledWOB - (c2 << 8)
float c1 = scaledWOB - floor(c2 * 256.0);
// Normalize to byte range
vec4 pix;
pix.r = c0 / 255.0;
pix.g = c1 / 255.0;
pix.b = c2 / 255.0;
pix.a = 1.0;
gl_FragColor = pix;
我发现了一个非常好的github库(https://github.com/msqrt/shader-printf) 您可以在着色器文件中使用 printf 函数。
起诉这个
vec3 dd(vec3 finalColor,vec3 valueToDebug){
//debugging
finalColor.x = (v_uv.y < 0.3 && v_uv.x < 0.3) ? valueToDebug.x : finalColor.x;
finalColor.y = (v_uv.y < 0.3 && v_uv.x < 0.3) ? valueToDebug.y : finalColor.y;
finalColor.z = (v_uv.y < 0.3 && v_uv.x < 0.3) ? valueToDebug.z : finalColor.z;
return finalColor;
}
//on the main function, second argument is the value to debug
colour = dd(colour,vec3(0.0,1.0,1.));
gl_FragColor = vec4(clamp(colour * 20., 0., 1.),1.0);
对纹理进行离线渲染并评估纹理的数据。 您可以通过谷歌搜索“渲染到纹理”opengl 找到相关代码 然后使用 glReadPixels 将输出读入数组并对其执行断言(因为在调试器中查看如此庞大的数组通常并没有多大用处)。
此外,您可能希望禁用对不介于 0 和 1 之间的输出值的钳位,这仅支持 浮点纹理。
我个人有一段时间被正确调试着色器的问题所困扰。似乎没有一个好的方法 - 如果有人找到一个好的(并且不是过时/不推荐使用的)调试器,请告诉我。