OpenGL之紋理

OpenGL之基礎(chǔ)
OpenGL之繪制簡單形狀
OpenGL之顏色
OpenGL之調(diào)整屏幕寬高比
OpenGL之三維

紋理

紋理可以用來表示圖像、照片、甚至由一個數(shù)學(xué)算法生成的分形數(shù)據(jù)。每個二維的紋理都由許多小的紋理元素(texel)組成,它們是小塊的數(shù)據(jù),使用紋理最常用的方式是直接從一個圖像文件加載數(shù)據(jù)

每個二維的紋理都有其自己的坐標(biāo)空間,其范圍是從一個拐角的 (0, 0) 到另一個拐角的 (11) ,按照慣例,一個維度叫做S,而另一個稱為T,這些紋理坐標(biāo)有時也會被稱為UV紋理坐標(biāo)


image-20211221171121512.png

當(dāng)我們想要把一個紋理應(yīng)用于一個三角形或一組三角形的時候,需要為每個頂點指定 ST 紋理坐標(biāo),以便OpenGL知道要用紋理的哪個部分畫到三角形上

加載紋理

把一個圖像文件的數(shù)據(jù)加載到一個OpenGL的紋理中

public class TextureHelper {
    private static final String TAG = "TextureHelper";

    public static int loadTexture(Context context, int resId) {
        // OpenGL不能直接讀取PNG或者JPEG文件的數(shù)據(jù),因為這些文件被編碼為特定的壓縮格式
        // OpenGL需要非壓縮形式的原始數(shù)據(jù),因此,需要用位圖解碼器BitmapFactory把圖像文件解壓縮為Bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 告訴BitmapFactory想要原始的圖像數(shù)據(jù),而不是這個圖像的縮放版本
        options.inScaled = false;
        // 獲取圖像原始bitmap數(shù)據(jù)
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
        if (bitmap == null) {
            Log.d(TAG, "loadTexture: decodeResource failed!");

            return 0;
        }

        // 生成一個新的紋理ID
        int textureIds[] = new int[1];
        glGenTextures(textureIds.length, textureIds, 0);
        // 檢查生成紋理是否成功,結(jié)果等于0就是失敗
        if (textureIds[0] == 0) {
            Log.d(TAG, "loadTexture: glGenTextures failed!");
            return 0;
        }

        // 綁定,告訴OpenGL后面的調(diào)用應(yīng)用于這個二維紋理
        // 第一個參數(shù)GL_TEXTURE_2D告訴OpenGL這應(yīng)該被作為一個二維紋理對待,第二個參數(shù)告訴 OpenGL要綁定到哪個紋理對象的ID
        glBindTexture(GL_TEXTURE_2D, textureIds[0]);
        //設(shè)置紋理過濾參數(shù),縮小和放大
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // 讀入bitmap的位圖數(shù)據(jù)到OpenGL,并復(fù)制到當(dāng)前紋理對象
        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
        // 釋放bitmap的位圖數(shù)據(jù)
        bitmap.recycle();
        // 生成mipmap貼圖
        glGenerateMipmap(GL_TEXTURE_2D);

        // 解綁
        glBindTexture(GL_TEXTURE_2D, 0);
        return textureIds[0];
    }
}

紋理過濾

當(dāng)紋理大小被擴大或者縮小時,我們還需要使用紋理過濾 (texture filtering) 明確說明會縮小和放大的規(guī)則

最近鄰過濾

這個方式為每個片段選擇最近的紋理元素

放大(鋸齒明顯) 縮?。毠?jié)丟失)
image-20211221172912954.png
image-20211221173218799.png

雙線性過濾

雙線性過濾使用雙線性插值平滑像素之間的過渡,而不是為每個片段使用最近的紋理元素,OpenGL會使用四個鄰接的紋理元素,并在它們之間用一個線性插值算法做插值,之所以叫它雙線性,是因為它是沿著兩個維度插值的

放大(效果較好) 縮小(細節(jié)丟失)
image-20211221173452985.png
image-20211221173653459.png

MIP貼圖

生成一組優(yōu)化過的不同大小的紋理。在渲染時,OpenGL 會根據(jù)每個片段的紋理元素數(shù)量為每個片段選擇最適合的級別,使用MIP貼圖,會占用更多的內(nèi)存,但是渲染也會更快,因為較小級別的紋理在GPU的紋理緩存中占用較少的空間


image-20211221174105702.png

三線性過濾

OpenGL 在不同的 MIP 貼圖級別之間來回切換,當(dāng)我們用雙線性過濾來使用 MIP 貼圖時,在其渲染的場景中,在不同級別的 MIP 貼圖切換時,有時候能看到明顯的跳躍或者線條,切換到三線性過濾( trilinear filtering),告訴 OpenGL 在兩個最鄰近的 MIP 貼圖級別之間也要插值,這樣,每個片段總共要使用 8 個紋理元素插值。這有助于消除每個 MIP 貼圖級別之間的過渡,并且得到一個更平滑的圖像

紋理過濾總結(jié)

過濾模式 對應(yīng)代碼
最近鄰過濾 GL_NEAREST
使用MIP貼圖的最近鄰過濾 GL_NEAREST_MIPMAP_NEAREST
使用MIP貼圖級別之間插值的最近鄰過濾 GL_NEAREST_MIPMAP_LINEAR
雙線性過濾 GL_LINEAR
使用MIP貼圖的雙線性過濾 GL_LINEAR_MIPMAP_NEAREST
三線性過濾(使用MIP貼圖級別之間插值的雙線性過濾) GL_LINEAR_MIPMAP_LINEAR

每種情況下允許的紋理過濾模式

情況 允許的模式
縮小 GL_NEAREST、GL_NEAREST_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_LINEAR、GL_LINEAR、GL_LINEAR_MIPMAP_NEAREST、GL_LINEAR_MIPMAP_LINEAR
放大 GL_NEAREST、GL_LINEAR

修改著色器

修改著色器,讓其可以接收紋理,并把它應(yīng)用在要繪制的片段上

修改頂點著色器

attribute vec4 a_Position;
uniform mat4 u_Matrix;

attribute vec2 a_TextureCoordinates;
varying vec2 v_Texture_Coordinates;

void main(){
    v_Texture_Coordinates=a_TextureCoordinates;
    gl_Position=u_Matrix*a_Position;
    gl_PointSize=10.0;
}

給紋理坐標(biāo)加了一個新的屬性 "a_TextureCoordinates" ,因為它有兩個分量:S坐標(biāo)和T坐標(biāo),所以被定義為一個vec2,因為要把紋理坐標(biāo)插值后傳遞給片段著色器,因此使用 varying 創(chuàng)建了 vec2 的 "v_Texture_Coordinates"

修改片段著色器

precision mediump float;
uniform sampler2D u_Texture_Unit;
varying vec2 v_Texture_Coordinates;
void main(){
    gl_FragColor=texture2D(u_Texture_Unit, v_Texture_Coordinates);
}

為了把紋理繪制到一個物體上,OpenGL會為每個片段都調(diào)用片段著色器,并且每個調(diào)用都接收v_Texture_Coordinates 的紋理坐標(biāo)

片段著色器也通過 uniform 的 u_Texture_Unit 接收實際的紋理數(shù)據(jù),u_TextureUnit 被定義為一個 sampler2D ,這個變量類型指的是一個二維紋理數(shù)據(jù)的數(shù)組

被插值的紋理坐標(biāo)和紋理數(shù)據(jù)被傳遞給著色器函數(shù) texture2D() ,它會讀人紋理中那個特定坐標(biāo)處的顏色值,然后把結(jié)果賦值給 gl_FragColor 設(shè)置片段的最終顏色

創(chuàng)建物體

創(chuàng)建Mallet類管理木槌的數(shù)據(jù),以及Table類管理桌子的數(shù)據(jù),并且每個類都會有一個 VertexArray類的實例,它用來封裝存儲頂點的 FloatBuffer

封裝存儲頂點類

public class VertexArray {
    private final FloatBuffer mVertexBuffer;

    /**
     * 創(chuàng)建頂點數(shù)組
     *
     * @param data
     */
    public VertexArray(float[] data) {
        mVertexBuffer = ByteBuffer.allocateDirect(data.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(data);
        mVertexBuffer.position(0);
    }


    /**
     * 綁頂點數(shù)據(jù)和OpenGL中的屬性
     *
     * @param dataOffset        頂點數(shù)據(jù)偏移量,定位到數(shù)組中的那個位置
     * @param attributeLocation 屬性位置
     * @param componentCount    此屬性所占位數(shù),例如位置(x,y)占兩位,位置(x,y,z)占3位
     * @param stride            所有屬性占位總和字節(jié)數(shù),例如(x,y,z,r,g,b)為6*4
     */
    public void setVertexAttribPointer(int dataOffset, int attributeLocation, int componentCount, int stride) {
        mVertexBuffer.position(dataOffset);
        glVertexAttribPointer(
                attributeLocation,
                componentCount,
                GL_FLOAT,
                false,
                stride,
                mVertexBuffer
        );
        glEnableVertexAttribArray(attributeLocation);
        mVertexBuffer.position(0);
    }
}

桌子類

public class Table {
    private static final int POSITION_COMPONENT = 2;
    private static final int TEXTURE_COORDINATES_COMPONENT = 2;
    private static final int STRIDE = (POSITION_COMPONENT + TEXTURE_COORDINATES_COMPONENT) * Constants.BYTES_PER_FLOAT;
    // 桌子的頂點數(shù)據(jù)
    private VertexArray mVertexArray;
    // 要注意,x、y范圍是 [-1,1](如果不想橫屏被裁減的話),s、t范圍是[0,1]
    // 這里并沒有使用圖像的所有數(shù)據(jù),在t方向上只使用了0.1~0.9的范圍
    // 因為桌子的寬高比是1:1.6,為了避免壓縮,裁減圖像的邊緣
    public static final float[] VERTEX_DATA = new float[]{
            // x,y,s,t
            0f, 0f, 0.5f, 0.5f,
            -0.5f, -0.8f, 0f, 0.1f,
            0.5f, -0.8f, 1f, 0.1f,
            0.5f, 0.8f, 1f, 0.9f,
            -0.5f, 0.8f, 0f, 0.9f,
            -0.5f, -0.8f, 0f, 0.1f
    };

    public Table() {
        mVertexArray = new VertexArray(VERTEX_DATA);
    }

    /**
     * 綁定頂點數(shù)據(jù)到OpenGL程序
     */
    public void bindData(TextureProgram textureProgram) {
        mVertexArray.setVertexAttribPointer(
                0,
                textureProgram.getAPosition(),
                POSITION_COMPONENT,
                STRIDE
        );

        mVertexArray.setVertexAttribPointer(
                POSITION_COMPONENT,
                textureProgram.getATextureCoordinates(),
                TEXTURE_COORDINATES_COMPONENT,
                STRIDE
        );
    }

    /**
     * 畫出桌子
     */
    public void draw() {
        glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
    }
}

上面代碼中的 TextureProgram 類,用于生成程序,獲取著色器中 attribute 和 uniform 的位置,并提供設(shè)置 uniform 的方法和 attribute 的方法方法,如下

public class TextureProgram extends BaseProgram {

    private final int mATextureCoordinates;
    private final int mUTextureUnit;

    private final int mAPosition;
    private final int mUMatrix;

    public TextureProgram(Context context) {
        super(context, R.raw.buildobjects6_vertex, R.raw.buildobjects6_fragment);

        mATextureCoordinates = glGetAttribLocation(mProgram, A_TEXTURE_COORDINATES);
        mUTextureUnit = glGetUniformLocation(mProgram, U_TEXTURE_UNIT);

        mAPosition = glGetAttribLocation(mProgram, A_POSITION);
        mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
    }

    public void setUniforms(float[] matrix, int textureId) {
        glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);

        //激活紋理單元0
        glActiveTexture(GL_TEXTURE0);
        //將紋理textureId綁定到這個單元
        glBindTexture(GL_TEXTURE_2D, textureId);
        //把被選定的紋理單元0傳遞給片段著色器中的mUTextureUnit
        glUniform1i(mUTextureUnit, 0);
    }

    public int getATextureCoordinates() {
        return mATextureCoordinates;
    }

    public int getAPosition() {
        return mAPosition;
    }
}

BaseProgram

public class BaseProgram {
    public static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";
    public static final String A_POSITION = "a_Position";
    public static final String A_COLOR = "a_Color";

    public static final String U_TEXTURE_UNIT = "u_Texture_Unit";
    public static final String U_MATRIX = "u_Matrix";
    public final int mProgram;

    public BaseProgram(Context context, int vertexResId, int fragmentResId) {
        int vertexId = ShaderHelper.compileVertexShader(context, vertexResId);
        int fragmentId = ShaderHelper.compileFragmentShader(context, fragmentResId);
        mProgram = ProgramHelper.getProgram(vertexId, fragmentId);
    }

    /**
     * 調(diào)用此方法,告訴OpenGL接下來的渲染使用此程序
     */
    public void useProgram() {
        glUseProgram(mProgram);
    }
}

木槌類

用和桌子類同樣的方法創(chuàng)建木槌類,以及它對應(yīng)的程序類

public class Mallet {
    private static final int POSITION_COMPONENT = 2;
    private static final int COLOR_COMPONENT = 3;
    private static final int STRIDE = (POSITION_COMPONENT + COLOR_COMPONENT) * Constants.BYTES_PER_FLOAT;
    //頂點數(shù)據(jù)
    private VertexArray mVertexArray;
    private static final float[] VERTEX_DATA = new float[]{
            // x,y,r,g,b
            0f, 0.4f, 1f, 0f, 0f,
            0f, -0.4f, 0f, 1f, 0f
    };

    public Mallet() {
        mVertexArray = new VertexArray(VERTEX_DATA);
    }

    /**
     * 頂點數(shù)據(jù)綁定到OpenGL程序
     *
     * @param colorProgram
     */
    public void bindData(ColorProgram colorProgram) {
        mVertexArray.setVertexAttribPointer(
                0,
                colorProgram.getAPosition(),
                POSITION_COMPONENT,
                STRIDE
        );
        mVertexArray.setVertexAttribPointer(
                POSITION_COMPONENT,
                colorProgram.getAColor(),
                COLOR_COMPONENT,
                STRIDE
        );
    }

    /**
     * 畫點
     */
    public void draw() {
        glDrawArrays(GL_POINTS, 0, 2);
    }
}
public class ColorProgram extends BaseProgram {
    private final int mAColor;
    private final int mAPosition;
    private final int mUMatrix;

    public ColorProgram(Context context) {
        super(context, R.raw.texture5_color_vertex, R.raw.texture5_color_fragment);
        mAColor = glGetAttribLocation(mProgram, A_COLOR);
        mAPosition = glGetAttribLocation(mProgram, A_POSITION);
        mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
    }

    public void setUniforms(float[] matrix) {
        glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);
    }

    public int getAColor() {
        return mAColor;
    }

    public int getAPosition() {
        return mAPosition;
    }
}

修改渲染器

修改渲染器,在其中使用定義好的物體類(桌子和木槌)以及他們對應(yīng)的程序類

public class MyRenderer implements GLSurfaceView.Renderer {
    private Context mContext;
    private float[] projectionMatrix = new float[16];
    private float[] modelMatrix = new float[16];

    private TextureProgram mTextureProgram;
    private ColorProgram mColorProgram;
    private Table mTable;
    private Mallet mMallet;
    private int mTextureId;

    public MyRenderer(Context context) {
        mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        glClearColor(1f, 1f, 1f, 1f);
        mTextureProgram = new TextureProgram(mContext);
        mColorProgram = new ColorProgram(mContext);
        mTable = new Table();
        mMallet = new Mallet();
        mTextureId = TextureHelper.loadTexture(mContext, R.drawable.air_hockey_surface);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        glViewport(0, 0, width, height);
        MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / height, 1, 10);

        Matrix.setIdentityM(modelMatrix, 0);
        Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
        Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

        float[] temp = new float[16];
        Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
        System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        // 清空渲染表面
        glClear(GL_COLOR_BUFFER_BIT);
        // 畫桌子
        // 告訴OpenGL使用這個程序
        mTextureProgram.useProgram();
        // 把uniform傳遞進去
        mTextureProgram.setUniforms(projectionMatrix, mTextureId);
        // 把頂點數(shù)組數(shù)據(jù)和著色器程序綁定起來
        mTable.bindData(mTextureProgram);
        // 繪制桌子
        mTable.draw();

        // 畫木槌
        mColorProgram.useProgram();
        mColorProgram.setUniforms(projectionMatrix);
        mMallet.bindData(mColorProgram);
        mMallet.draw();
    }
}

效果

image-20211221205503896.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一常用函數(shù) 改變像素存儲?方式 從顏?色緩存區(qū)內(nèi)容作為像素圖直接讀取 載?紋理 常用: glTexImage2D ...
    只寫B(tài)ug程序猿閱讀 211評論 0 1
  • 一、什么是紋理? 藝術(shù)家和程序員更喜歡使用紋理(Texture)。紋理是一個2D圖片(甚至也有1D和3D的紋理),...
    Sheisone閱讀 369評論 0 0
  • 1、了解紋理 圖像的存儲空間 = 圖片width * 圖片height * 每個像素的字節(jié)數(shù) OpenGL紋理文件...
    紫水依閱讀 3,690評論 0 0
  • 紋理可以理解為一張圖片,OpenGL渲染圖片會將圖片的像素保存在紋理緩存中。 OpenGL常用紋理函數(shù) 載入紋理 ...
    逃避不面對閱讀 412評論 0 0
  • 一.先看看整體效果 二.繪制流程 在OpenGl綜合案例(地板,大小球,公轉(zhuǎn),自轉(zhuǎn),移動)文章中,我們繪制了大小球...
    楓紫_6174閱讀 582評論 0 1

友情鏈接更多精彩內(nèi)容