初识OpenGL

来源:互联网 发布:淘宝u站九块九包邮 编辑:程序博客网 时间:2024/06/07 05:20

初识OpenGL


OpenGLES20实现效果

什么是OpenGLES

“OpenGL ES是基于OpenGL三维图形API的子集,主要针对手机以及掌上电脑等嵌入式设备设计的。”简而言之,就是用来开发嵌入式设备的三维图形显示的一套API。

OpenGL10和OpenGL20的异同

网上大部分Demo都是用OpenGLES10来开发的,至于OpenGLES20有啥好处,这个回头再讲。

Android代码中如何使用OpenGL20

一句话,是通过GLSurfaceView来使用OpenGL。

具体来讲,是通过

“Activity -> GLSurfaceView -> GLSurfaceView.Render -> 构建图形”

这样的一个顺序来一步步使用OpenGLES20的接口的。

构建图形的时候,还用到了几个东西:

  1. 通过 顶点坐标矩阵 完成 图形动画变换投影(物体前后关系的显示)

  2. 通过 纹理坐标矩阵顶点颜色矩阵 完成 纹理映射上色 ,在 着色器语言脚本 (.sh代码中)实现 光照 效果

  3. 通过 摄像机矩阵 设置观看的 视角

这里我们用到了很多矩阵。不要怕,只不过是纸老虎。大部分矩阵变换的代码是不需要我们亲自弄懂原理的,接口中已经封装好了。

而在代码例子中,矩阵变换更是已经被封装成了个工具类,直接调用即可。我们只需要关心坐标矩阵就可以了。

另外,着色器语言脚本也有点吓人。一看,是.sh文件,里面的代码和天书似的。不用怕,这个我们也不需要亲自实现。至少暂时是不需要的。

重点,我认为,是图形的构建

下面结合一个例子来讲,是如何一步步建模,贴纹理的。

代码运行效果

Activity中要注意的点

public class MyActivity extends Activity {    private MySurfaceView mGLSurfaceView;    static boolean threadFlag;//纹理矩形绕X轴旋转工作标志位    @Override    protected void onCreate(Bundle savedInstanceState)     {        super.onCreate(savedInstanceState);                 //设置为全屏        requestWindowFeature(Window.FEATURE_NO_TITLE);         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN ,                WindowManager.LayoutParams.FLAG_FULLSCREEN);        //设置为竖屏模式        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);        //切换到主界面        //初始化GLSurfaceView        mGLSurfaceView = new MySurfaceView(this);        setContentView(mGLSurfaceView);         mGLSurfaceView.requestFocus();//获取焦点        mGLSurfaceView.setFocusableInTouchMode(true);//设置为可触控      }    @Override    protected void onResume() {        super.onResume();        threadFlag=true;        mGLSurfaceView.onResume();    }    @Override    protected void onPause() {        super.onPause();        threadFlag=false;        mGLSurfaceView.onPause();    }    }

自寻关键点。

GLSurfaceView和GLSurfaceView.Render中要注意的点

public class MySurfaceView extends GLSurfaceView {private final float TOUCH_SCALE_FACTOR = 180.0f/320;//角度缩放比例private SceneRenderer mRenderer;//场景渲染器private float mPreviousY;//上次的触控位置Y坐标private float mPreviousX;//上次的触控位置X坐标int textureId;//系统分配的纹理idpublic MySurfaceView(Context context) {    super(context);    this.setEGLContextClientVersion(2); //设置使用OPENGL ES2.0    mRenderer = new SceneRenderer();    //创建场景渲染器    setRenderer(mRenderer);             //设置渲染器                 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染   }//触摸事件回调方法@Override public boolean onTouchEvent(MotionEvent e) {    float y = e.getY();    float x = e.getX();    switch (e.getAction()) {    case MotionEvent.ACTION_MOVE:        float dy = y - mPreviousY;//计算触控笔Y位移        float dx = x - mPreviousX;//计算触控笔X位移        mRenderer.texRect.yAngle += dx * TOUCH_SCALE_FACTOR;//设置纹理矩形绕y轴旋转角度        mRenderer.texRect.zAngle+= dy * TOUCH_SCALE_FACTOR;//设置第纹理矩形绕z轴旋转角度    }    mPreviousY = y;//记录触控笔位置    mPreviousX = x;//记录触控笔位置    return true;}private class SceneRenderer implements GLSurfaceView.Renderer {       Triangle texRect;//纹理矩形    public void onDrawFrame(GL10 gl)     {         //清除深度缓冲与颜色缓冲        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);        //绘制纹理矩形        texRect.drawSelf(textureId);                 }      public void onSurfaceChanged(GL10 gl, int width, int height) {        //设置视窗大小及位置         GLES20.glViewport(0, 0, width, height);         //计算GLSurfaceView的宽高比        float ratio = (float) width / height;        //调用此方法计算产生透视投影矩阵        MatrixState.setProject(-ratio, ratio, -1, 1, 1, 10);        //调用此方法产生摄像机9参数位置矩阵        MatrixState.setCamera(0,0,3,0f,0f,0f,0f,1.0f,0.0f);    }    public void onSurfaceCreated(GL10 gl, EGLConfig config) {        //设置屏幕背景色RGBA        GLES20.glClearColor(0.5f,0.5f,0.5f, 1.0f);          //创建三角形对对象         texRect=new Triangle(MySurfaceView.this);                //打开深度检测        GLES20.glEnable(GLES20.GL_DEPTH_TEST);        //初始化纹理        initTexture();        //关闭背面剪裁           GLES20.glDisable(GLES20.GL_CULL_FACE);    }}public void initTexture()//textureId{    //生成纹理ID    int[] textures = new int[1];    GLES20.glGenTextures    (            1,          //产生的纹理id的数量            textures,   //纹理id的数组            0           //偏移量    );        textureId=textures[0];        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);    //通过输入流加载图片===============begin===================    InputStream is = this.getResources().openRawResource(R.drawable.icon);    Bitmap bitmapTmp;    try     {        bitmapTmp = BitmapFactory.decodeStream(is);    }     finally     {        try         {            is.close();        }         catch(IOException e)         {            e.printStackTrace();        }    }    //通过输入流加载图片===============end=====================      //实际加载纹理    GLUtils.texImage2D    (            GLES20.GL_TEXTURE_2D,   //纹理类型,在OpenGL ES中必须为GL10.GL_TEXTURE_2D            0,                    //纹理的层次,0表示基本图像层,可以理解为直接贴图            bitmapTmp,            //纹理图像            0                     //纹理边框尺寸    );    bitmapTmp.recycle();          //纹理加载成功后释放图片}}

要点:

  1. 构造方法中,this.setEGLContextClientVersion(2);这句话很重要。如果不写,会一直报called unimplemented OpenGL ES API错误。另外,要注意写setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);这句话。最后,在构造方法中初始化渲染器。

  2. 没错,Render是个接口,这个要实现它的三个方法。Render和构建图形联系的比较紧密。从这里我们可以依稀有点感觉为啥构建图形要写一个drawSelf方法,主要是为了在onDrawFrame方法中调用。里面三个方法中实现的代码相对来说比较固定。如果有多个图形需要绘制,也都在这一个Render中进行绘制。比如在SurfaceCreated中用for循环绘制多个三角形,在onDrawFrame中用for循环给所有图形调用drawSelf方法。

  3. 最后,讲initTexture()这个方法。这个方法是生成纹理id,绑定纹理图片的关键。GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);这句话用于绑定纹理。GLES20.glTexParameterf(int target, int pname, float param)这个方法是设置参数。里面的参数会在“关于纹理映射”那一节中详细说明。总之,它生成了纹理id,并通过drawSelf方法中传入了图形构建中。

构建图形时要注意的点

//纹理三角形public class Triangle {   int mProgram;//自定义渲染管线程序idint muMVPMatrixHandle;//总变换矩阵引用idint maPositionHandle; //顶点位置属性引用id  int maTexCoorHandle; //顶点纹理坐标属性引用id  String mVertexShader;//顶点着色器         String mFragmentShader;//片元着色器FloatBuffer   mVertexBuffer;//顶点坐标数据缓冲FloatBuffer   mTexCoorBuffer;//顶点纹理坐标数据缓冲int vCount=0;   public float xAngle = 0;// 绕x轴旋转的角度public float yAngle = 0;// 绕y轴旋转的角度public float zAngle = 0;// 绕z轴旋转的角度public Triangle(MySurfaceView mv){           //初始化顶点坐标与着色数据    initVertexData();    //初始化着色器            initShader(mv);}//初始化顶点坐标与着色数据的方法public void initVertexData(){    //顶点坐标数据的初始化================begin============================    vCount=3;    final float UNIT_SIZE=0.15f;    float vertices[]=new float[]    {        0*UNIT_SIZE,11*UNIT_SIZE,0,        -11*UNIT_SIZE,-11*UNIT_SIZE,0,        11*UNIT_SIZE,-11*UNIT_SIZE,0,    };    //创建顶点坐标数据缓冲    //vertices.length*4是因为一个整数四个字节    ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);    vbb.order(ByteOrder.nativeOrder());//设置字节顺序    mVertexBuffer = vbb.asFloatBuffer();//转换为Float型缓冲    mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据    mVertexBuffer.position(0);//设置缓冲区起始位置    //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer    //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题    //顶点坐标数据的初始化================end============================    //顶点纹理坐标数据的初始化================begin============================    float texCoor[]=new float[]//顶点颜色值数组,每个顶点4个色彩值RGBA    {            0.5f,0,             0,1,             1,1                 };            //创建顶点纹理坐标数据缓冲    ByteBuffer cbb = ByteBuffer.allocateDirect(texCoor.length*4);    cbb.order(ByteOrder.nativeOrder());//设置字节顺序    mTexCoorBuffer = cbb.asFloatBuffer();//转换为Float型缓冲    mTexCoorBuffer.put(texCoor);//向缓冲区中放入顶点着色数据    mTexCoorBuffer.position(0);//设置缓冲区起始位置    //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer    //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题    //顶点纹理坐标数据的初始化================end============================}//初始化着色器public void initShader(MySurfaceView mv){    //加载顶点着色器的脚本内容    mVertexShader=ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());    //加载片元着色器的脚本内容    mFragmentShader=ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());      //基于顶点着色器与片元着色器创建程序    mProgram = createProgram(mVertexShader, mFragmentShader);    //获取程序中顶点位置属性引用id      maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");    //获取程序中顶点纹理坐标属性引用id      maTexCoorHandle= GLES20.glGetAttribLocation(mProgram, "aTexCoor");    //获取程序中总变换矩阵引用id    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");  }public void drawSelf(int texId){             //制定使用某套shader程序     GLES20.glUseProgram(mProgram);             MatrixState.setInitStack();     //设置沿Z轴正向位移1     MatrixState.transtate(0, 0, 1);     //设置绕y轴旋转     MatrixState.rotate(yAngle, 0, 1, 0);     //设置绕z轴旋转     MatrixState.rotate(zAngle, 0, 0, 1);       //设置绕x轴旋转     MatrixState.rotate(xAngle, 1, 0, 0);     //将最终变换矩阵传入shader程序     GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);      //为画笔指定顶点位置数据     GLES20.glVertexAttribPointer       (            maPositionHandle,               3,             GLES20.GL_FLOAT,             false,            3*4,               mVertexBuffer     );            //为画笔指定顶点纹理坐标数据     GLES20.glVertexAttribPointer       (            maTexCoorHandle,             2,             GLES20.GL_FLOAT,             false,            2*4,               mTexCoorBuffer     );        //允许顶点位置数据数组     GLES20.glEnableVertexAttribArray(maPositionHandle);       GLES20.glEnableVertexAttribArray(maTexCoorHandle);       //绑定纹理     GLES20.glActiveTexture(GLES20.GL_TEXTURE0);     GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);     //绘制纹理矩形     GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount); }}

这个类中,一共三个主要的方法,initVertexData()初始化坐标点数据。 initShader(mv)初始化着色器。 drawSelf(int texId)绘制自身。前两个方法都在构造方法中调用了,后一个方法在GLSurfaceView中的onDrawFrame()方法中调用。

第一个方法initVertexData(),主要是创建坐标点数据缓冲区。坐标点数据包括1顶点位置数据2纹理坐标数据。这个数据缓冲区有什么用呢?数据缓冲区要和.sh着色器语言绑定在一起,把坐标点数据传入.sh代码中,才能通过坐标点数据绘制出图形的面

这个过程是在第三个方法drawSelf()中实现的。但是要弄懂第三个方法,我们要先弄明白第二个方法initShader(mv)作为铺垫。

我们瞧瞧initShader(mv)都做了什么。首先通过ShaderUtil加载了顶点对应的着色器代码”vertex.sh”,然后加载片元对应的着色器代码”frag.sh”,通过这两个.sh脚本创建着色器程序,并返回着色器程序引用id——mProgram。通过mProgram,可以获取出.sh脚本中的几个变量,maPositionHandle顶点位置属性引用id,maTexCoorHandle顶点纹理坐标引用id,muMVPMatrixHandle总变换矩阵引用id。这三个引用id,是用来和缓冲区数据和变换矩阵数据绑定在一起的。

第三个方法,drawSelf(int texId),先完成了变换矩阵初始化和变换,最后通过getFinalMatrix()方法获取到最终的变换矩阵。并把它和总变换矩阵的引用id绑定在了一起。接下来完成了绑定坐标点数据缓存和引用id的代码。最后,更新顶点数据,绑定纹理,最后绘制图形。

Shader语言(着色器语言)

先贴出来ShaderUtil的代码。

//加载顶点Shader与片元Shader的工具类public class ShaderUtil {//加载制定shader的方法public static int loadShader(     int shaderType, //shader的类型  GLES20.GL_VERTEX_SHADER   GLES20.GL_FRAGMENT_SHADER     String source   //shader的脚本字符串) {    //创建一个新shader    int shader = GLES20.glCreateShader(shaderType);    //若创建成功则加载shader    if (shader != 0)     {        //加载shader的源代码        GLES20.glShaderSource(shader, source);        //编译shader        GLES20.glCompileShader(shader);        //存放编译成功shader数量的数组        int[] compiled = new int[1];        //获取Shader的编译情况        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);        if (compiled[0] == 0)         {//若编译失败则显示错误日志并删除此shader            Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");            Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));            GLES20.glDeleteShader(shader);            shader = 0;              }      }    return shader;}//创建shader程序的方法 public static int createProgram(String vertexSource, String fragmentSource) {    //加载顶点着色器    int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);    if (vertexShader == 0)     {        return 0;    }    //加载片元着色器    int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);    if (pixelShader == 0)     {        return 0;    }    //创建程序    int program = GLES20.glCreateProgram();    //若程序创建成功则向程序中加入顶点着色器与片元着色器    if (program != 0)     {        //向程序中加入顶点着色器        GLES20.glAttachShader(program, vertexShader);        checkGlError("glAttachShader");        //向程序中加入片元着色器        GLES20.glAttachShader(program, pixelShader);        checkGlError("glAttachShader");        //链接程序        GLES20.glLinkProgram(program);        //存放链接成功program数量的数组        int[] linkStatus = new int[1];        //获取program的链接情况        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);        //若链接失败则报错并删除程序        if (linkStatus[0] != GLES20.GL_TRUE)         {            Log.e("ES20_ERROR", "Could not link program: ");            Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));            GLES20.glDeleteProgram(program);            program = 0;        }    }    return program;}//检查每一步操作是否有错误的方法 public static void checkGlError(String op) {    int error;    while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR)     {        Log.e("ES20_ERROR", op + ": glError " + error);        throw new RuntimeException(op + ": glError " + error);    }}//从sh脚本中加载shader内容的方法public static String loadFromAssetsFile(String fname,Resources r){    String result=null;         try    {        InputStream in=r.getAssets().open(fname);        int ch=0;                   ByteArrayOutputStream baos = new ByteArrayOutputStream();        while((ch=in.read())!=-1)        {            baos.write(ch);        }              byte[] buff=baos.toByteArray();        baos.close();        in.close();    result=new String(buff,"UTF-8");     result=result.replaceAll("\\r\\n","\n");    }    catch(Exception e)    {        e.printStackTrace();    }           return result;}}

然后贴出来两个着色器脚本。这两个脚本是放在assets目录下的。

frag.sh代码

precision mediump float;varying vec2 vTextureCoord; //接收从顶点着色器过来的参数uniform sampler2D sTexture;//纹理内容数据void main()                         {               //给此片元从纹理中采样出颜色值                gl_FragColor = texture2D(sTexture, vTextureCoord); }                  

vertex.sh代码

uniform mat4 uMVPMatrix; //总变换矩阵attribute vec3 aPosition;  //顶点位置attribute vec2 aTexCoor;    //顶点纹理坐标varying vec2 vTextureCoord;  //用于传递给片元着色器的变量void main()     {                                       gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置    vTextureCoord = aTexCoor;//将接收的纹理坐标传递给片元着色器}    

这里的重点是.sh脚本的语法和含义。这个等之后有空再深讲。这里只需要清楚它是用来给图形上色即可。

关于投影、摄像机、变换

投影摄像机 的参数都会影响到上述getFinalMatrix()方法得到的最终变换矩阵的计算结果。这俩的含义和方法中的参数,等之后有空再讲。而这里在程序里,就是俩矩阵。

变换 的种类比较多,分为平移,旋转,缩放等。本质也是对于矩阵的计算。在这个程序里,就是用封装好的方法,对于变换矩阵进行计算。

//存储系统矩阵状态的类public class MatrixState {private static float[] mProjMatrix = new float[16];//4x4矩阵 投影用private static float[] mVMatrix = new float[16];//摄像机位置朝向9参数矩阵private static float[] mMVPMatrix;//最后起作用的总变换矩阵static float[] mMMatrix=new float[16] ;//具体物体的移动旋转矩阵public static void setInitStack()//获取不变换初始矩阵{    Matrix.setRotateM(mMMatrix, 0, 0, 1, 0, 0);}public static void transtate(float x,float y,float z)//设置沿xyz轴移动{    Matrix.translateM(mMMatrix, 0, x, y, z);}public static void rotate(float angle,float x,float y,float z)//设置绕xyz轴转动{    Matrix.rotateM(mMMatrix,0,angle,x,y,z);}//设置摄像机public static void setCamera(        float cx,   //摄像机位置x        float cy,   //摄像机位置y        float cz,   //摄像机位置z        float tx,   //摄像机目标点x        float ty,   //摄像机目标点y        float tz,   //摄像机目标点z        float upx,  //摄像机UP向量X分量        float upy,  //摄像机UP向量Y分量        float upz   //摄像机UP向量Z分量        ){    Matrix.setLookAtM    (            mVMatrix,             0,             cx,            cy,            cz,            tx,            ty,            tz,            upx,            upy,            upz    );}//设置透视投影参数public static void setProject(    float left,     //near面的left    float right,    //near面的right    float bottom,   //near面的bottom    float top,      //near面的top    float near,     //near面距离    float far       //far面距离){    Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);}//获取具体物体的总变换矩阵public static float[] getFinalMatrix(){    mMVPMatrix=new float[16];    Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);    Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);            return mMVPMatrix;}}

关于坐标和绘制方式

这里面说的坐标有两个,一个是位置坐标,一个是纹理坐标。位置坐标采取xyz三轴坐标系,方向是右手坐标系方向(四指向y,大拇指向x,手心向z)。而纹理坐标系采取的是ST坐标系。S是横轴,向右为正。T是纵轴,向下为正。在纹理坐标系中,1单位为贴图长宽。根据位置坐标系和纹理坐标系,我们创建出位置的坐标矩阵和纹理的坐标矩阵。

另外,绘制方式,是在GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount)第一个参数体现出来的。一共有七种绘制方式,比较常用的是三角形的三种绘制方式,因为它能绘制出来面。其它四种,可以绘制出来点和线。这是三角形的三种绘制方式。

GL_TRIANGLESGL_TRIANGLE_STRIPGL_TRIANGLE_FAN

同样的坐标,不同的绘制方式绘制出的结果和绘制效率都是不同的。具体等到之后用空再补充。

关于背面裁剪和卷绕

背面裁剪和卷绕的主要目的是,让被挡住看不到的面不绘制,这样来节约资源,增加效率。至于如何判断是否是背面,这是通过判断坐标点的卷绕方向来判断的,可以手动设置卷绕方向是顺时针为正还是逆时针为正,默认是逆时针卷绕为正面。

关于光照

光照分为三种反射方式,两种光照方式。

反射方式:

  1. 环境光
  2. 散射光
  3. 镜面光

光照方式:

  1. 定位光
  2. 定向光

对于不同的效果,或是所有效果的叠加,都是在.sh代码中写进去的。计算颜色矩阵,最后把结果展现在片元上。

关于纹理映射

上面已经讲了纹理坐标系是S-T轴。S轴向左为正,T轴向下为正。而纹理坐标的范围,是0.0~1.0,没有负数。不论实际纹理图的尺寸如何,它横向纵向坐标的最大值都是1。而且,1代表纹理图片横纵方向的最边界点。用纹理坐标,把纹理图片切割成和图形方向相同形状相同的图形。然后就完成了纹理的映射。

当实际图形比较大,纹理图片比较小的时候,纹理图片可能会出现拉伸

拉伸分为两种方式:

  1. 重复拉伸:类似于桌面的平铺效果
  2. 截取拉伸:把纹理图片边缘的像素一直重复

另外,纹理有一个采样过程,这个采样过程分为两种:

  1. MIN采样:当纹理图比需要映射的图元尺寸大时,系统会采取MIN对应的纹理采样算法设置。
  2. MAG采样:当纹理图比需要映射的图元尺寸小时,系统会采取MAG对应的纹理采样算法设置。

纹理采样算法分为两种:

  1. 最近点采样:计算较快,但是把较小的纹理图映射到较大的图元上时容易产生明显的锯齿。
  2. 线性纹理采样:计算速度比最近点采样要慢,但是不会有上述情况的锯齿。虽然不会有锯齿,但是会边缘模糊。MAG采样时常用线性采样。

这样我们就能理解在initTexture()方法中,那几行代码的含义。

最后还要加上一句,当我们用png贴图的时候,透明的地方会变黑。如果向解决这个问题,需要在drawSelf()方法中加上两句代码:

    GLES20.glEnable( GLES20.GL_BLEND );    GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

这样能解决贴图半透明背景的问题。

end

后续,会把上面没有写的东西补充完整,再加上3D建模等,加载3D模型,以及混合和雾等操作的说明。

0 0
原创粉丝点击