纹理采样
在opengl和directx里有纹理采样函数vector4 Sampler.texture2d(float s,float t),用它可以取得对应纹理坐标的纹理值;然而,并不知道这个函数的具体实现是怎么样的.事实上从图像读取的纹理就是一个数组,类似这样:
- imgData=newunsignedchar[width*height*3];
纹理坐标可以转换为数组的下标,类似这样:
- floatu=(float)(width-1)*s;
- floatv=(float)(height-1)*(1.0-t);
- intiu=(int)u;
- intiv=(int)v;
纹理坐标s和t的范围是0到1,这边采用的是opengl的坐标系,y轴从下到上递增,但是图片保存的坐标系y轴是从上到下递增,所以1.0-t做y轴取反以获得opengl坐标系的对应的纹理坐标,这边运算出的u和v的范围是0到纹理宽和0到纹理高,然后将v乘以纹理宽加上u然后乘以3(一个纹理包含rgb分量)即可获得数组下标:
- intimgIndex=3*(iv*width+iu);
最后通过数组下标取得对应的纹理值:
- color.x=(float)imgData[imgIndex]*INV_SCALE;
- color.y=(float)imgData[imgIndex+1]*INV_SCALE;
- color.z=(float)imgData[imgIndex+2]*INV_SCALE;
这边直接把1/255作为一个宏可以加快运算速度:
- #defineINV_SCALE0.003921568627451
然后return color;即可获得纹理颜色值,以上就是POINT/NEAREST采样的实现.事实上这是最基本的纹理采样函数实现,这里边还有问题,效果不是很好.
线性纹理过滤
POINT/NEAREST采样固然能够取得纹理的颜色值,但是效果看上去很差,有大块的走样,上面有这样的代码:
- intiu=(int)u;
- intiv=(int)v;
问题就出在这里,(int)这样直接抛弃小数点以后的值导致采样出的相邻纹理并不连续,那么用float采样行吗? 答案是:不行! 这边实现的采样函数是从数组取值,纹理坐标转为数组下标,数组下标不能用float只能用int,那么就没办法了吗? 并不是,可以对周围纹理进行采样然后按照各自比例进行混合,这样能够提高显示效果.原理如下:
例如计算出的u和v类似这样:3.45,4.55 那么u就在3到4之间,v在4到5之间,比例是(1-0.45):0.45和(1-0.55):0.55,那么对(3,4),(4,4),(3,5),(4,5)进行采样,然后乘以各自比例进行颜色混合计算即可得出经过过滤的颜色值.
具体实现如下:
- VECTOR4DSampler::texture2D(floats,floatt){
- VECTOR4Dcolor(1,1,1,1);
- floatu=(float)(width-1)*s;
- floatv=(float)(height-1)*(1.0-t);
- intiu=(int)u;
- intiv=(int)v;
- intuNext=iu+1<=(width-1)?iu+1:iu;
- intvNext=iv+1<=(height-1)?iv+1:iv;
- floatuNextPer=u-iu;
- floatvNextPer=v-iv;
- floatuPer=1.0-uNextPer;
- floatvPer=1.0-vNextPer;
- intimgIndex=3*(iv*width+iu);
- color.x=(float)imgData[imgIndex]*INV_SCALE;
- color.y=(float)imgData[imgIndex+1]*INV_SCALE;
- color.z=(float)imgData[imgIndex+2]*INV_SCALE;
- intimgIndexNextU=3*(iv*width+uNext);
- intimgIndexNextV=3*(vNext*width+iu);
- intimgIndexNextUV=3*(vNext*width+uNext);
- VECTOR4DcolorNextU(1,1,1,1),colorNextV(1,1,1,1),colorNextUV(1,1,1,1);
- colorNextU.x=(float)imgData[imgIndexNextU]*INV_SCALE;
- colorNextU.y=(float)imgData[imgIndexNextU+1]*INV_SCALE;
- colorNextU.z=(float)imgData[imgIndexNextU+2]*INV_SCALE;
- colorNextV.x=(float)imgData[imgIndexNextV]*INV_SCALE;
- colorNextV.y=(float)imgData[imgIndexNextV+1]*INV_SCALE;
- colorNextV.z=(float)imgData[imgIndexNextV+2]*INV_SCALE;
- colorNextUV.x=(float)imgData[imgIndexNextUV]*INV_SCALE;
- colorNextUV.y=(float)imgData[imgIndexNextUV+1]*INV_SCALE;
- colorNextUV.z=(float)imgData[imgIndexNextUV+2]*INV_SCALE;
- color.x=color.x*uPer*vPer+colorNextU.x*uNextPer*vPer+colorNextV.x*uPer*vNextPer+colorNextUV.x*uNextPer*vNextPer;
- color.y=color.y*uPer*vPer+colorNextU.y*uNextPer*vPer+colorNextV.y*uPer*vNextPer+colorNextUV.y*uNextPer*vNextPer;
- color.z=color.z*uPer*vPer+colorNextU.z*uNextPer*vPer+colorNextV.z*uPer*vNextPer+colorNextUV.z*uNextPer*vNextPer;
- returncolor;
- }
这样得出的纹理颜色比之前点采样的看上去好多了.
渲染到纹理
现代opengl和directx都有FrameBuffer/RenderTarget功能,有了这项功能可以进行Render To Texture(渲染到纹理)操作.RTT操作的具体应用有很多,最普遍的应用莫过于Shadow Map技术.事实上RTT的具体实现方法就是把渲染缓冲区的值复制给纹理数组,也并没有多少复杂的,具体实现如下:
- voidwriteFrameBuffer2Sampler(FrameBuffer*fb,Sampler*sampler){
- for(inti=0;i<fb->height;i++){
- for(intj=0;j<fb->width;j++){
- intindex=(i*fb->width+j)*3;
- sampler->imgData[((i)*fb->width+j)*3]=fb->colorBuffer[index];
- sampler->imgData[((i)*fb->width+j)*3+1]=fb->colorBuffer[index+1];
- sampler->imgData[((i)*fb->width+j)*3+2]=fb->colorBuffer[index+2];
- }
- }
- }
那样,渲染得到的结果就可以作为纹理使用了.
调用方法
类似opengl和directx在shader里调用纹理采样函数,这边模拟了opengl的shader:
- voidfragmentShader(Fragmentinput,FragmentOut&output){
- VECTOR4DtexColor(1,1,1,1);
- if(currTexture!=NULL)
- texColor=currTexture->texture2D(input.s,input.t);
- output.r=texColor.x;
- output.g=texColor.y;
- output.b=texColor.z;
- output.a=texColor.w;
- }
FragmentShader在光栅化函数rasterize中调用,每生成一个fragment则调用一次FragmentShader.
图片数据加载参见:http://blog.csdn.net/zxx43/article/details/41594871 |