我编写了一个体积渲染程序,可将一些2D图像转换为可由用户旋转的3d体积。我需要通过在点周围的每个方向上采用渐变来计算3d纹理中每个点的法线(用于照明)。
计算法线需要在片段着色器中进行六次额外的纹理访问。没有这些额外的纹理访问,程序要快得多,所以我试图以字节为单位预先计算每个方向(x,y,z)的渐变,并将其存储在原始纹理的BGA通道中。当我在CPU上测试时,我的字节似乎包含正确的值,但当我到达着色器时,它出现错误。很难说明为什么它会从着色器中失败,我认为这是因为某些渐变值是负数。但是,当我将纹理类型指定为GL_BYTE(而不是GL_UNSIGNED_BYTE)时,它仍然是错误的,这会搞砸原始纹理的外观。仅仅通过将数据渲染为颜色,我无法确切地知道出了什么问题。将负值放入纹理的正确方法是什么?当我在片段着色器中读取它时,我怎么知道值是负的?
以下代码显示了如何运行操作以从字节数组(byte [] all)计算渐变,然后将其转换为以3d纹理读入的字节缓冲区(byteBuffer bb)。函数'toLoc(x,y,z,w,h,l)'只返回(x + w *(y + z * h))* 4) - 它将3d下标转换为1d索引。图像是灰度图像,因此我丢弃gba并仅使用r通道保存原始值。其余通道(gba)存储渐变。
int pixelDiffxy=5;
int pixelDiffz=1;
int count=0;
Float r=0f;
byte t=r.byteValue();
for(int i=0;i<w;i++){
for(int j=0;j<h;j++){
for(int k=0;k<l;k++){
count+=4;
if(i<pixelDiffxy || i>=w-pixelDiffxy || j<pixelDiffxy || j>=h-pixelDiffxy || k<pixelDiffz || k>=l-pixelDiffz){
//set these all to zero since they are out of bounds
all[toLoc(i,j,k,w,h,l)+1]=t;//green=0
all[toLoc(i,j,k,w,h,l)+2]=t;//blue=0
all[toLoc(i,j,k,w,h,l)+3]=t;//alpha=0
}
else{
int ri=(int)all[toLoc(i,j,k,w,h,l)+0] & 0xff;
//find the values on the sides of this pixel in each direction (use red channel)
int xgrad1=(all[toLoc(i-pixelDiffxy,j,k,w,h,l)])& 0xff;
int xgrad2=(all[toLoc(i+pixelDiffxy,j,k,w,h,l)])& 0xff;
int ygrad1=(all[toLoc(i,j-pixelDiffxy,k,w,h,l)])& 0xff;
int ygrad2=(all[toLoc(i,j+pixelDiffxy,k,w,h,l)])& 0xff;
int zgrad1=(all[toLoc(i,j,k-pixelDiffz,w,h,l)])& 0xff;
int zgrad2=(all[toLoc(i,j,k+pixelDiffz,w,h,l)])& 0xff;
//find the difference between the values on each side and divide by the distance between them
int xgrad=(xgrad1-xgrad2)/(2*pixelDiffxy);
int ygrad=(ygrad1-ygrad2)/(2*pixelDiffxy);
int zgrad=(zgrad1-zgrad2)/(2*pixelDiffz);
Vec3f grad=new Vec3f(xgrad,ygrad,zgrad);
Integer xg=(int) (grad.x);
Integer yg=(int) (grad.y);
Integer zg=(int) (grad.z);
//System.out.println("gs are: "+xg +", "+yg+", "+zg);
byte gby= (byte) (xg.byteValue());//green channel
byte bby= (byte) (yg.byteValue());//blue channel
byte aby= (byte) (zg.byteValue());//alpha channel
//System.out.println("gba is: "+(int)gby +", "+(int)bby+", "+(int)aby);
all[toLoc(i,j,k,w,h,l)+1]=gby;//green
all[toLoc(i,j,k,w,h,l)+2]=bby;//blue
all[toLoc(i,j,k,w,h,l)+3]=aby;//alpha
}
}
}
}
ByteBuffer bb=ByteBuffer.wrap(all);
final GL gl = drawable.getGL();
final GL2 gl2 = gl.getGL2();
final int[] bindLocation = new int[1];
gl.glGenTextures(1, bindLocation, 0);
gl2.glBindTexture(GL2.GL_TEXTURE_3D, bindLocation[0]);
gl2.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);//-byte alignment
gl2.glTexParameteri(GL2.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
gl2.glTexParameteri(GL2.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
gl2.glTexParameteri(GL2.GL_TEXTURE_3D, GL2.GL_TEXTURE_WRAP_R, GL2.GL_CLAMP);
gl2.glTexParameteri(GL2.GL_TEXTURE_3D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
gl2.glTexParameteri(GL2.GL_TEXTURE_3D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
gl2.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE);
gl2.glTexImage3D( GL2.GL_TEXTURE_3D, 0,GL.GL_RGBA,
w, h, l, 0,
GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, bb );//GL_UNSIGNED_BYTE
是否有更好的方法可以将大量签名数据导入着色器?
答案 0 :(得分:4)
gl2.glTexImage3D( GL2.GL_TEXTURE_3D, 0,GL.GL_RGBA,
w, h, l, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, bb );
嗯,有两种方法可以做到这一点,具体取决于你想在着色器中做多少工作,以及你想要限制的OpenGL版本。
需要更多着色器工作的版本还需要更多代码。看,你想要做的是让你的着色器采用无符号字节,然后将它们重新解释为有符号字节。
通常这样做的方法是传递无符号的标准化字节(正如你所做的那样),它会在[0,1]范围内产生浮点值,然后通过乘以2来扩展该范围。减去1,在[-1,1]范围内产生数字。这意味着您的上传代码需要使用[-128,127]有符号字节,并通过向它们添加128将它们转换为[0,255]无符号字节。
我不知道如何在 Java 中执行此操作,它似乎根本没有无符号字节类型。你不能只传递一个2的补码字节,并期望它在着色器中工作;这不会发生。字节值-128将映射到浮点值1,这没有用。
如果您可以设法如上所述正确转换数据,那么您的着色器访问必须从[0,1]范围解压缩到[-1,1]范围。
如果您可以访问GL 3.x,那么您可以非常轻松地完成此操作,而不需要更改着色器:
gl2.glTexImage3D( GL2.GL_TEXTURE_3D, 0,GL.GL_RGBA8_SNORM,
w, h, l, 0, GL.GL_RGBA, GL.GL_BYTE, bb );
图像格式中的_SNORM
表示它是带符号的标准化格式。因此,[-128,127]范围内的字节将映射到范围[-1,1]上的浮点数。正是你想要的。