二、GLSL著色器程序的數(shù)據(jù)輸入與傳遞(一):attribute修飾符

基于OpenGL ES2.0 for Android。

一個(gè)程序需要有數(shù)據(jù)輸入,否則沒(méi)有實(shí)際意義,正如下面這段代碼。

    void main() {
        gl_Position = vec4(0);
    }

盡管這個(gè)著色器是“格式良好的”,但是在實(shí)際應(yīng)用中,我們應(yīng)避免這種“寫(xiě)死在程序里”的數(shù)據(jù)。那么,如何往GLSL著色器中寫(xiě)入數(shù)據(jù)呢?

與C語(yǔ)言一樣,GLSL可以通過(guò)變量來(lái)接受應(yīng)用程序輸入的變量。但是GLSL中只有特定的修飾符修飾的變量才可以用于數(shù)據(jù)的輸入輸出。這些特定修飾符主要有attributevarying,uniform。

首先是attribute修飾符。按照官方文檔介紹,attribute修飾的變量用于聲明基于每個(gè)頂點(diǎn)從OpenGL ES傳遞到頂點(diǎn)著色器的變量。這句話如何理解呢?我們知道,頂點(diǎn)著色器定義了圖形上頂點(diǎn)的位置,而各種圖形中除了“點(diǎn)”只有一個(gè)頂點(diǎn),其他圖形都具有一個(gè)以上的頂點(diǎn)。attribute修飾的變量就會(huì)根據(jù)頂點(diǎn)的數(shù)量依序設(shè)置和獲取頂點(diǎn)屬性數(shù)組的值。attribute所修飾的變量通常用來(lái)輸入頂點(diǎn)坐標(biāo)、法線等數(shù)據(jù)。

一、attribute的主要特性

  1. attribute修飾符只能用于頂點(diǎn)著色器中的變量,不能用于片元著色器中的變量。
//在fragment shader中使用attribute修飾符
attribute vec4 fColor;
void main() {
    gl_FragColor = fColor;
}

attribute用于修飾片元著色器中的變量,編譯著色器源碼時(shí)會(huì)報(bào)錯(cuò)S0044: Attribute qualifier only allowed in vertex shaders。

  1. attribute只能修飾 float, vec2, vec3, vec4, mat2, mat3, 和 mat4等類(lèi)型。

attribute變量只能用于修飾以上幾種變量類(lèi)型,不能用于修飾其他變量。

  1. 就頂點(diǎn)著色器而言,attribute所修飾的變量是只讀的。只能通過(guò)OpenGL ES API來(lái)傳入和修改變量值。
//在vertex shader對(duì)attribute修飾符修飾的變量進(jìn)行賦值或修改
attribute vec4 vPosition;
void main() {
    vPosition = vec4(1.0);
    gl_Position = vPosition;
}

在著色器內(nèi)部對(duì)attribute修飾的變量賦值或修改,頂點(diǎn)著色器在編譯階段報(bào)錯(cuò): S0027: Cannot modify an input variable。

  1. attribute修飾的變量具有數(shù)量和內(nèi)存空間限制。

類(lèi)似于其他語(yǔ)言變量,attribute同樣有變量地址空間大小的限制。attribute變量通過(guò)一個(gè)很小的固定的存儲(chǔ)空間傳遞數(shù)據(jù),因此,GLSL規(guī)定了所有非矩陣類(lèi)型都有一個(gè)確定的不大于4個(gè)float大小(四個(gè)浮點(diǎn)數(shù)所需存儲(chǔ)空間)的存儲(chǔ)大小。矩陣類(lèi)型變量的存儲(chǔ)大小等于矩陣的行數(shù)乘以 4個(gè)float大小。

相應(yīng)地,由于總存儲(chǔ)空間大小固定且每個(gè)變量大小固定,GLSL限制了attribute所修飾的變量的最大計(jì)數(shù)(請(qǐng)注意,計(jì)數(shù)≠數(shù)量)。最大計(jì)數(shù)的值由提供的固定存儲(chǔ)空間的大小決定,但GLSL規(guī)定了此最大計(jì)數(shù)的最小值(GLSL規(guī)定此最大計(jì)數(shù)最小值為8)。這個(gè)最大計(jì)數(shù)是一個(gè)編譯時(shí)就確定的常量:gl_MaxVertexAttribs,我們可以通過(guò)OpenGL ES API獲取這個(gè)常量參數(shù)的值。

GLSL是如何計(jì)數(shù)地呢?我們可以簡(jiǎn)單地理解為,最大計(jì)數(shù)的值等于總存儲(chǔ)空間大小除以 4個(gè)float大小。所有非矩陣變量都計(jì)數(shù)1,即使這個(gè)變量小于4個(gè)float大小,矩陣類(lèi)型計(jì)數(shù)為矩陣的行數(shù)。如:一個(gè)float變量和一個(gè)vec4變量具有同樣的計(jì)數(shù)1,一個(gè)mat2變量計(jì)數(shù)為2,一個(gè)mat4變量計(jì)數(shù)為4。一個(gè)最大計(jì)數(shù)為8的OpenGL ES實(shí)現(xiàn)能夠分別存放8個(gè)float,四個(gè)mat2,兩個(gè)mat4。因此,將4個(gè)不相干的浮點(diǎn)數(shù)變量打包放到一個(gè)vec4變量中能夠最大限度利用硬件存儲(chǔ)空間。

另外,只聲明未使用的變量不計(jì)數(shù)。

我們可以簡(jiǎn)單的理解為,attribute變量存儲(chǔ)在一個(gè)由硬件廠商決定的固定大小的內(nèi)存緩沖區(qū)。它的最小計(jì)量單位為vec4

通過(guò)OpenGL ES glGet* API可以獲取內(nèi)部常量參數(shù),下面的代碼獲取的是gl_MaxVertexAttribs(attribute修飾符修飾的變量的最大計(jì)數(shù))的值。

    val parameters = IntArray(1)
    GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_ATTRIBS, parameters, 0)
    Log.e("parameters", parameters.contentToString())

打印結(jié)果parameters: [16]。表明此設(shè)備上的OpenGL ES內(nèi)置的最大attribute變量計(jì)數(shù)為16?,F(xiàn)在我們通過(guò)代碼測(cè)試超過(guò)這個(gè)計(jì)數(shù)會(huì)發(fā)生什么。

    val vertexCode = 
        "attribute mat4 a;\n" + 
        "attribute mat4 b;\n" + 
        "attribute mat4 c;\n" + 
        "attribute mat4 d;\n" + 
        "attribute vec4 e;\n" + 
        "void main() {\n" + 
        "    gl_Position = a*b*c*d*e;\n" + 
        "}"
    val fragmentCode =
        "void main() {\n" +
        "    gl_FragColor = vec4(0.5);\n" +
        "}"
    linkProgram(vertexCode, fragmentCode)

執(zhí)行報(bào)錯(cuò),L0004 Not able to fit attributes。錯(cuò)誤代碼L0004,表示Linker(鏈接器)第四號(hào)錯(cuò)誤。我們可以去官方文檔查找一下它的詳細(xì)介紹。

L0004: Too many attribute values.正是attribute計(jì)數(shù)超過(guò)限制。

現(xiàn)在我們簡(jiǎn)單地修改一下void main()函數(shù),將" gl_Position = a*b*c*d*e;\n"修改為" gl_Position = a*b*c*e;\n",去掉了參數(shù)d的使用,將使用參數(shù)計(jì)數(shù)減少到13。執(zhí)行結(jié)果表明一切正常。

  1. attribute變量不能以“gl_”為前綴。
    val vertexCode = """
        attribute vec4 gl_a;
        void main() {
            gl_Position = gl_a;
        }
    """
    val fragmentCode = """
        void main() {
            gl_FragColor = vec4(0.5);
        }
    """
    linkProgram(vertexCode, fragmentCode)

鏈接時(shí)報(bào)錯(cuò):L0006: Illegal identifier name 'gl_a'

  1. attribute變量作用域必須是全局的。

attribute的作用域必須是全局的(即必須在方法外聲明),否則編譯時(shí)會(huì)報(bào)錯(cuò):S0045: Attribute declared inside a function

二、attribute變量的應(yīng)用

了解了attribute修飾符的特性后,我們來(lái)學(xué)習(xí)如何使用它。

1. 獲取attribute變量的地址索引

我們可以通過(guò)glGetAttribLocation(int program, String name)獲取一個(gè)attribute變量的地址索引。

int glGetAttribLocation(int program, String name)
program:已鏈接的SL程序?qū)ο蟆?br> name:attribute變量名。
return:返回attribute變量表中的位置。

請(qǐng)注意,這個(gè)方法必須在glLinkProgram(int program)之后調(diào)用才有效,否則返回索引值為-1。另外,當(dāng)attribute變量未處于active狀態(tài)(只聲明未使用或使用無(wú)意義,這種情況的attribute變量在編譯時(shí)會(huì)被優(yōu)化忽略),glGetAttribLocation(int program, String name)返回值也是-1。

    val vertexCode = """ 
        attribute mat4 a;
        attribute mat4 b;
        attribute float c;
        attribute mat4 d;
        attribute vec4 e;
        attribute vec4 f;
        void main() {
            vec4 g = f;
            gl_Position = a*c*d*e;
        }
    """
    val fragmentCode = """
        void main() {
           gl_FragColor = vec4(0.5);
        }
    """
    val program = linkProgram(vertexCode, fragmentCode)
    val locations = ('a'..'f').joinToString {
        "location${it.toUpperCase()}: ${GLES20.glGetAttribLocation(program, it.toString())}"
    }
    Log.e("locations", locations)

打印結(jié)果locations: locationA: 0, locationB: -1, locationC: 8, locationD: 4, locationE: 9, locationF: -1。

從這個(gè)結(jié)果我們可以看到,只聲明未使用的變量b和使用無(wú)意義的變量f返回的索引均為-1。

另外,仔細(xì)觀察其他幾個(gè)變量的索引我們會(huì)發(fā)現(xiàn)它符合上面講到的第五條特性。并且,如果地址索引在一定程度上反應(yīng)了變量在物理空間的相對(duì)位置,那么我們可以大膽猜測(cè),OpenGL ES/GLSL為了更好的利用硬件存儲(chǔ)空間,并沒(méi)有按照我們聲明的順序存儲(chǔ)變量,而是按地址空間大小從大到小存儲(chǔ)。此處,我們暫且不去討論OpenGL ES或者GLSL規(guī)定的存儲(chǔ)方式,留待以后再討論。

2. 手動(dòng)設(shè)置attribute變量的地址索引

除了在鏈接時(shí)自動(dòng)為attribute變量分配地址索引外,GLSL還允許我們?cè)阪溄映绦蚯巴ㄟ^(guò)glBindAttribLocation(int, int, String)手動(dòng)為attribute變量設(shè)置地址索引。

void glBindAttribLocation(int program, int index, String name)
program:未鏈接的SL程序?qū)ο蟆?br> index:想要的設(shè)置的attribute變量在attribute變量表中的位置,即glGetAttribLocation(int program, String name)的返回結(jié)果。它必須是一個(gè)0gl_MaxVertexAttribs-1的值,否則此次方法調(diào)用無(wú)效。
name:attribute變量名。

這個(gè)方法必須在glLinkProgram(int program)調(diào)用前使用,否則設(shè)置不會(huì)生效。

    val vertexCode = """
                attribute mat4 a;
                attribute mat4 b;
                attribute mat4 c;
                void main() {
                    gl_Position = a[0]+b[0]+c[0];
                }
                """
    val fragmentCode = """
                void main() {
                    gl_FragColor = vec4(0.5);
                }
                """
    val program = attatchShaders(vertexCode, fragmentCode)
    //valid
    GLES20.glBindAttribLocation(program, 2, "a")

    //exceed,gl_MaxVertexAttribs = 16
    GLES20.glBindAttribLocation(program, 18, "b")

    //linking
    GLES20.glLinkProgram(program)

    //valid, but after link
    GLES20.glBindAttribLocation(program, 1, "c")

    val locations = ('a'..'c').joinToString {
        "location${it.toUpperCase()}: ${GLES20.glGetAttribLocation(program, it.toString())}"
    }
    Log.e("locations", locations)

打印結(jié)果locations: locationA: 2, locationB: 6, locationC: 10。

可以看到,b雖然在程序鏈接前綁定,但index越界。c在程序鏈接后綁定,此時(shí)attribute變量表已經(jīng)生成,改變無(wú)效。只有對(duì)變量a的設(shè)置是有效的,bc的地址索引依然是鏈接器分配的索引。

3. 修改attribute變量的值

OpenGL ES提供了兩個(gè)API為attribute變量設(shè)置變量值,它們分別是glVertexAttribPointer( int indx, int size, int type, boolean normalized, int stride, int offset )glVertexAttribPointer( int indx, int size, int type, boolean normalized, int stride, java.nio.Buffer ptr ),其中前一個(gè)方法是從VertexBufferObject為attribute變量賦值,后一個(gè)方法是從主內(nèi)存為attribute變量賦值,前一個(gè)方法留到后面講,此處我們主要了解后一個(gè)方法。

void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, java.nio.Buffer ptr)

index:所要設(shè)置的變量的地址位置。由glGetAttribLocation(int program, String name)得到。
size:每個(gè)頂點(diǎn)所需要的元素個(gè)數(shù)。必須是1,2,3,4中的一個(gè)值,默認(rèn)為4。
type:表示數(shù)組中每個(gè)元素的數(shù)據(jù)類(lèi)型。必須是GL_BYTE,GL_UNSIGNED_BYTE,GL_SHORT,GL_UNSIGNED_SHORT,GL_FIXEDGL_FLOAT中的一個(gè),默認(rèn)為GL_FLOAT。
normalized:表示數(shù)據(jù)是否需要在傳入到頂點(diǎn)數(shù)組前進(jìn)行歸一化處理。
stride:表示ptr中兩個(gè)連續(xù)元素之間的偏移字節(jié)數(shù)。如果stride為0,那么則表示ptr中個(gè)元素是緊密貼合在一起的。
ptr:需要設(shè)置的數(shù)據(jù)的內(nèi)存緩存。為了最大限度提高效率,建議使用ByteBuffer裝載數(shù)據(jù)。需要注意的是,不同存儲(chǔ)設(shè)備存取字節(jié)順序有兩種不同方式,分別是Big-Endian(高位優(yōu)先)或Little-Endian(低位優(yōu)先),Android中是Little-Endian。因此,我們需要使用當(dāng)前運(yùn)行設(shè)備上的存取字節(jié)順序,這可以通過(guò)ByteOrder.nativeOrder()方法獲得。

另外,在OpenGL訪問(wèn)前還需要調(diào)用glEnableVertexAttribArray(int)方法啟用該通用頂點(diǎn)數(shù)組。否則glVertexAttribPointer()調(diào)用無(wú)效,OpenGL會(huì)使用靜態(tài)頂點(diǎn)屬性值。

    fun vertexAttribPointer(program: Int, name: String, size: Int, pType: Int, 
                                         normalized: Boolean, stride: Int, ptr: FloatArray): Int {
        return glGetAttribLocation(program, name).also {
            
            //為頂點(diǎn)屬性傳入數(shù)據(jù)
            glVertexAttribPointer(it, size, pType, normalized, stride, ptr.toBuffer())
            glEnableVertexAttribArray(it)
        }
    }
    fun FloatArray.toBuffer(): FloatBuffer {
        return ByteBuffer.allocateDirect(size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer()
                  .put(this).position(0) as FloatBuffer
    }

三、本文使用的工具方法

    fun loadShader(type: Int, code: String): Int {
        val shader = GLES20.glCreateShader(type)
        GLES20.glShaderSource(shader, code)
        GLES20.glCompileShader(shader)
        Log.e("loadShader", "type: $type, shader: $shader,  error: ${GLES20.glGetShaderInfoLog(shader)}")
        Log.e("loadShader", "shaderSource: ${GLES20.glGetShaderSource(shader)}")
        return shader
    }

    fun attatchShaders(vertexShader: Int, fragmentShader: Int): Int {
        val program = GLES20.glCreateProgram()
        GLES20.glAttachShader(program, vertexShader)
        Log.e("attatchShaders", "shader: $vertexShader,  error: ${GLES20.glGetProgramInfoLog(program)}")
        GLES20.glAttachShader(program, fragmentShader)
        Log.e("attatchShaders", "shader: $fragmentShader,  error: ${GLES20.glGetProgramInfoLog(program)}")
        return program
    }

    fun attatchShaders(vertexCode: String, fragmentCode: String): Int {
        val vertexHandle = loadShader(GLES20.GL_VERTEX_SHADER, vertexCode)
        val fragmentHandle =loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode)
        return attatchShaders(vertexHandle, fragmentHandle)
    }

    fun linkProgram(vertexShader: Int, fragmentShader: Int): Int {
        val program = attatchShaders(vertexShader, fragmentShader)
        GLES20.glLinkProgram(program)
        Log.e("linkProgram", "program: $program, error: ${GLES20.glGetProgramInfoLog(program)}")
        return program
    }

    fun linkProgram(vertexCode: String, fragmentCode: String): Int  {
        val program = attatchShaders(vertexCode, fragmentCode)
        GLES20.glLinkProgram(program)
        Log.e("linkProgram", "program: $program,  error: ${GLES20.glGetProgramInfoLog(program)}")
        return program
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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