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

基于OpenGL ES2.0 for Android。

一個程序需要有數(shù)據(jù)輸入茄厘,否則沒有實際意義矮冬,正如下面這段代碼。

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

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

與C語言一樣恢筝,GLSL可以通過變量來接受應(yīng)用程序輸入的變量。但是GLSL中只有特定的修飾符修飾的變量才可以用于數(shù)據(jù)的輸入輸出巨坊。這些特定修飾符主要有attribute撬槽,varyinguniform趾撵。

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

一取试、attribute的主要特性

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

attribute用于修飾片元著色器中的變量想括,編譯著色器源碼時會報錯S0044: Attribute qualifier only allowed in vertex shaders陷谱。

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

attribute變量只能用于修飾以上幾種變量類型瑟蜈,不能用于修飾其他變量烟逊。

  1. 就頂點著色器而言,attribute所修飾的變量是只讀的铺根。只能通過OpenGL ES API來傳入和修改變量值宪躯。
//在vertex shader對attribute修飾符修飾的變量進行賦值或修改
attribute vec4 vPosition;
void main() {
    vPosition = vec4(1.0);
    gl_Position = vPosition;
}

在著色器內(nèi)部對attribute修飾的變量賦值或修改,頂點著色器在編譯階段報錯: S0027: Cannot modify an input variable位迂。

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

類似于其他語言變量,attribute同樣有變量地址空間大小的限制掂林。attribute變量通過一個很小的固定的存儲空間傳遞數(shù)據(jù)臣缀,因此,GLSL規(guī)定了所有非矩陣類型都有一個確定的不大于4個float大小(四個浮點數(shù)所需存儲空間)的存儲大小泻帮。矩陣類型變量的存儲大小等于矩陣的行數(shù)乘以 4個float大小精置。

相應(yīng)地,由于總存儲空間大小固定且每個變量大小固定锣杂,GLSL限制了attribute所修飾的變量的最大計數(shù)(請注意脂倦,計數(shù)≠數(shù)量)番宁。最大計數(shù)的值由提供的固定存儲空間的大小決定,但GLSL規(guī)定了此最大計數(shù)的最小值(GLSL規(guī)定此最大計數(shù)最小值為8)赖阻。這個最大計數(shù)是一個編譯時就確定的常量:gl_MaxVertexAttribs蝶押,我們可以通過OpenGL ES API獲取這個常量參數(shù)的值。

GLSL是如何計數(shù)地呢火欧?我們可以簡單地理解為棋电,最大計數(shù)的值等于總存儲空間大小除以 4個float大小。所有非矩陣變量都計數(shù)1苇侵,即使這個變量小于4個float大小离陶,矩陣類型計數(shù)為矩陣的行數(shù)。如:一個float變量和一個vec4變量具有同樣的計數(shù)1衅檀,一個mat2變量計數(shù)為2,一個mat4變量計數(shù)為4霎俩。一個最大計數(shù)為8的OpenGL ES實現(xiàn)能夠分別存放8個float哀军,四個mat2,兩個mat4打却。因此杉适,將4個不相干的浮點數(shù)變量打包放到一個vec4變量中能夠最大限度利用硬件存儲空間。

另外柳击,只聲明未使用的變量不計數(shù)猿推。

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

通過OpenGL ES glGet* API可以獲取內(nèi)部常量參數(shù),下面的代碼獲取的是gl_MaxVertexAttribs(attribute修飾符修飾的變量的最大計數(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變量計數(shù)為16。現(xiàn)在我們通過代碼測試超過這個計數(shù)會發(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í)行報錯坦喘,L0004 Not able to fit attributes。錯誤代碼L0004西设,表示Linker(鏈接器)第四號錯誤瓣铣。我們可以去官方文檔查找一下它的詳細(xì)介紹。

L0004: Too many attribute values.正是attribute計數(shù)超過限制贷揽。

現(xiàn)在我們簡單地修改一下void main()函數(shù)棠笑,將" gl_Position = a*b*c*d*e;\n"修改為" gl_Position = a*b*c*e;\n",去掉了參數(shù)d的使用擒滑,將使用參數(shù)計數(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)

鏈接時報錯:L0006: Illegal identifier name 'gl_a'

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

attribute的作用域必須是全局的(即必須在方法外聲明)淹冰,否則編譯時會報錯:S0045: Attribute declared inside a function

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

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

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

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

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

請注意,這個方法必須在glLinkProgram(int program)之后調(diào)用才有效牺勾,否則返回索引值為-1正罢。另外,當(dāng)attribute變量未處于active狀態(tài)(只聲明未使用或使用無意義驻民,這種情況的attribute變量在編譯時會被優(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回还。

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

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

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

除了在鏈接時自動為attribute變量分配地址索引外,GLSL還允許我們在鏈接程序前通過glBindAttribLocation(int, int, String)手動為attribute變量設(shè)置地址索引掏呼。

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

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

    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在程序鏈接后綁定饮焦,此時attribute變量表已經(jīng)生成怕吴,改變無效。只有對變量a的設(shè)置是有效的县踢,b转绷、c的地址索引依然是鏈接器分配的索引。

3. 修改attribute變量的值

OpenGL ES提供了兩個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 )议经,其中前一個方法是從VertexBufferObject為attribute變量賦值,后一個方法是從主內(nèi)存為attribute變量賦值谴返,前一個方法留到后面講爸业,此處我們主要了解后一個方法。

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

index:所要設(shè)置的變量的地址位置亏镰。由glGetAttribLocation(int program, String name)得到。
size:每個頂點所需要的元素個數(shù)拯爽。必須是1索抓,2,3毯炮,4中的一個值逼肯,默認(rèn)為4。
type:表示數(shù)組中每個元素的數(shù)據(jù)類型桃煎。必須是GL_BYTE篮幢,GL_UNSIGNED_BYTEGL_SHORT为迈,GL_UNSIGNED_SHORT三椿,GL_FIXEDGL_FLOAT中的一個,默認(rèn)為GL_FLOAT葫辐。
normalized:表示數(shù)據(jù)是否需要在傳入到頂點數(shù)組前進行歸一化處理搜锰。
stride:表示ptr中兩個連續(xù)元素之間的偏移字節(jié)數(shù)。如果stride為0耿战,那么則表示ptr中個元素是緊密貼合在一起的蛋叼。
ptr:需要設(shè)置的數(shù)據(jù)的內(nèi)存緩存。為了最大限度提高效率,建議使用ByteBuffer裝載數(shù)據(jù)狈涮。需要注意的是狐胎,不同存儲設(shè)備存取字節(jié)順序有兩種不同方式,分別是Big-Endian(高位優(yōu)先)或Little-Endian(低位優(yōu)先)歌馍,Android中是Little-Endian握巢。因此,我們需要使用當(dāng)前運行設(shè)備上的存取字節(jié)順序骆姐,這可以通過ByteOrder.nativeOrder()方法獲得镜粤。

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

    fun vertexAttribPointer(program: Int, name: String, size: Int, pType: Int, 
                                         normalized: Boolean, stride: Int, ptr: FloatArray): Int {
        return glGetAttribLocation(program, name).also {
            
            //為頂點屬性傳入數(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)容合作請聯(lián)系作者
  • 序言:七十年代末同规,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子窟社,更是在濱河造成了極大的恐慌券勺,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灿里,死亡現(xiàn)場離奇詭異关炼,居然都是意外死亡,警方通過查閱死者的電腦和手機匣吊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門儒拂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人色鸳,你說我怎么就攤上這事社痛。” “怎么了命雀?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵蒜哀,是天一觀的道長。 經(jīng)常有香客問我吏砂,道長撵儿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任狐血,我火速辦了婚禮统倒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘氛雪。我一直安慰自己房匆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浴鸿,像睡著了一般井氢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岳链,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天花竞,我揣著相機與錄音,去河邊找鬼掸哑。 笑死约急,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的苗分。 我是一名探鬼主播厌蔽,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摔癣!你這毒婦竟也來了奴饮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤择浊,失蹤者是張志新(化名)和其女友劉穎戴卜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琢岩,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡投剥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了担孔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片江锨。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖攒磨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汤徽,我是刑警寧澤娩缰,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站谒府,受9級特大地震影響拼坎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜完疫,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一泰鸡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧壳鹤,春花似錦盛龄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啊鸭。三九已至,卻和暖如春匿值,著一層夾襖步出監(jiān)牢的瞬間赠制,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工挟憔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钟些,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓绊谭,卻偏偏與公主長得像政恍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子龙誊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容