OpenGL ES 3.0學習實踐
- android平臺下OpenGL ES 3.0從零開始
- android平臺下OpenGL ES 3.0繪制純色背景
- android平臺下OpenGL ES 3.0繪制圓點晨继、直線和三角形
- android平臺下OpenGL ES 3.0繪制彩色三角形
- android平臺下OpenGL ES 3.0從矩形中看矩陣和正交投影
- android平臺下OpenGL ES 3.0著色語言基礎知識(上)
- android平臺下OpenGL ES 3.0著色語言基礎知識(下)
- android平臺下OpenGL ES 3.0實例詳解頂點屬性类少、頂點數(shù)組
- android平臺下OpenGL ES 3.0實例詳解頂點緩沖區(qū)對象(VBO)和頂點數(shù)組對象(VAO)
- android平臺下OpenGLES3.0繪制立方體的幾種方式
本篇整理自《OpenGL ES 3.0 編程指南第2版》
目錄
統(tǒng)一變量和屬性
統(tǒng)一變量(uniform
)是存儲應用程序通過OpenGL ES 3.0 API
傳遞給著色器的只讀常數(shù)值的變量。
統(tǒng)一變量
被組合成兩類統(tǒng)一變量塊肝箱。
- 第一類是命名統(tǒng)一變量塊屿聋,統(tǒng)一變量的值由所謂的統(tǒng)一變量緩沖區(qū)對象支持扛稽,命名統(tǒng)一變量塊被分配一個
統(tǒng)一變量塊索引
瓮孙。
uniform TransformBlock {
mat4 matViewProj;
mat3 matNormal;
mat3 matTexGen;
};
- 第二類是默認的統(tǒng)一變量塊浦楣,用于在命名統(tǒng)一變量塊之外聲明的統(tǒng)一變量。和
命名統(tǒng)一變量塊
不同火欧,默認統(tǒng)一變量塊沒有名稱或者統(tǒng)一變量塊索引棋电。
uniform mat4 matViewProj;
uniform mat3 matNormal;
uniform mat3 matTexGen;
獲取和設置統(tǒng)一變量
要査詢程序中活動統(tǒng)一變量
的列表茎截,首先要用GL_ACTIVE_UNIFORMS
參數(shù)苇侵,調用glGetProgramiv
。這樣可以獲得程序中活動統(tǒng)一變量的數(shù)量企锌。這個列表包含命名統(tǒng)一變量塊中的統(tǒng)一變量
榆浓、著色器代碼中聲明的默認統(tǒng)一變量塊中的統(tǒng)一變量以及著色器代碼中使用的內建統(tǒng)一變量
。如果統(tǒng)一變量被程序使用撕攒,就認為它是"活動"
的陡鹃。換言之烘浦,如果你在一個著色器中聲明了一個統(tǒng)一變量但是從未使用,鏈接程序可能會在優(yōu)化時將其去掉萍鲸,不在活動統(tǒng)一變量列表中返回
闷叉。獲取到統(tǒng)一變量和存儲統(tǒng)一變量名稱所需的字符數(shù)之后,我們可以用glGetActiveUniform
和glGetActiveUniformsiv
找出每個統(tǒng)一變量的細節(jié)脊阴。
使用glGetActiveUniform
握侧,可以確定幾乎所有統(tǒng)一變量的屬性。你可以確定統(tǒng)一變量的名稱和類型
嘿期。此外品擎,可以發(fā)現(xiàn)變量是不是數(shù)組以及數(shù)組中使用的最大元素。統(tǒng)一變量的名稱 對于找到統(tǒng)一變量的位置是必要的
备徐,要知道如何加載統(tǒng)一變量的數(shù)據(jù)萄传,需要統(tǒng)一變量的類型和大小。一旦有了統(tǒng)一變量的名稱蜜猾,就可以用glGetUniformLocation
找到它的位置秀菱。統(tǒng)一變量的位置是一個整數(shù)值,用于標識統(tǒng)一變量在程序中的位置(注意: 命名統(tǒng)一變量塊中的統(tǒng)一變量沒有指定位置
)瓣铣。這個位置值用于加載統(tǒng)一變量值的后續(xù)調用(例如: glUniformlf
)答朋。
public static native int glGetUniformLocation(
int program,
String name
);
這個函數(shù)將返回由name
指定的統(tǒng)一變量的位置。如果這個統(tǒng)一變量不是程序中的活動統(tǒng)一變量棠笑,返回值將為-1梦碗。有了統(tǒng)一變量的位置及其類型和數(shù)組大小,我們就可以加載統(tǒng)一變量的值蓖救。加載統(tǒng)一變量值有許多不同的函數(shù)洪规,每種統(tǒng)一變量類型都對應不同的函數(shù)。
public static native void glUniform1f(int location,float x);
public static native void glUniform1fv(int location,int count,float[] v,int offset);
......
加載統(tǒng)一變量所需的函數(shù)根據(jù)glGetActiveUniform
函數(shù)返回的type
確定循捺。例如斩例,如果返回的類型是GL_FLOAT_VEC4
, 那么可以使用glUniform4f
或glUnifomi4fv
。如果gIGetActiveUniform
返回的size
大于 1, 則使用glUnifrom4fv
在一次調用中加載整個數(shù)組念赶。如果統(tǒng)一變量不是數(shù)組,則可以使用glUniform4f
或glUniform4fv
注意: glUniform*
調用不以程序對象句柄作為參數(shù)恰力。原因是: glUniform*
總是在與glUseProgram
綁定的當前程序上操作停局。統(tǒng)一變量值本身保存在程序對象中码倦。也就是說,一旦在程序對象中設置一個統(tǒng)一變量的值擒抛,即使你讓另一個程序處于活動狀態(tài)运提,該值仍然保留在原來的程序對象中。從這個意義上闻葵,我們可以說統(tǒng)一變量值是程序對象局部所有的民泵。
下面來實踐一下査詢程序對象中的統(tǒng)一變量信息的方法。
新建一個UniformRenderer.java
文件:
/**
* @anchor: andy
* @date: 2018-11-02
* @description:
*/
public class UniformRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "UniformRenderer";
private int mProgram;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//設置背景顏色
GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
//編譯
final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_uniform_shader));
final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_uniform_shader));
//鏈接程序片段
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
//在OpenGLES環(huán)境中使用程序片段
GLES30.glUseProgram(mProgram);
final int[] maxUniforms = new int[1];
GLES30.glGetProgramiv(mProgram, GLES30.GL_ACTIVE_UNIFORM_MAX_LENGTH, maxUniforms, 0);
final int[] numUniforms = new int[1];
GLES30.glGetProgramiv(mProgram, GLES30.GL_ACTIVE_UNIFORMS, numUniforms, 0);
Log.d(TAG, "maxUniforms=" + maxUniforms[0] + " numUniforms=" + numUniforms[0]);
int[] length = new int[1];
int[] size = new int[1];
int[] type = new int[1];
byte[] nameBuffer = new byte[maxUniforms[0] - 1];
for (int index = 0; index < numUniforms[0]; index++) {
GLES30.glGetActiveUniform(mProgram, index, maxUniforms[0], length, 0, size, 0, type, 0, nameBuffer, 0);
String uniformName = new String(nameBuffer);
int location = GLES30.glGetUniformLocation(mProgram, uniformName);
Log.d(TAG, "uniformName=" + uniformName + " location=" + location + " type=" + type[0] + " size=" + size[0]);
}
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
}
}
頂點著色器
#version 300 es
uniform mat4 mMatrix4;
uniform mat3 mMatrix3;
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
out vec4 vColor;
void main() {
gl_Position = mMatrix4 * vPosition;
gl_PointSize = 10.0;
vColor = aColor;
}
片段著色器
#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor;
}
輸出如下日志:
11-07 11:46:22.099 28987-29005/? D/UniformRenderer: maxUniforms=9 numUniforms=1
11-07 11:46:22.099 28987-29005/? D/UniformRenderer: uniformName=mMatrix4 location=0 type=35676 size=1
為什么只輸出了
mMatrix4
,那mMatrix3
呢? 剛剛也說到了厢钧,雖然我們在頂點著色器中聲明了mMatrix3
,但是我們并沒有使用它早直,導致它被鏈接程序優(yōu)化掉了
統(tǒng)一變量塊
統(tǒng)一變量緩沖區(qū)對象
可以通過一個緩沖區(qū)對象
支持統(tǒng)一變量數(shù)據(jù)的存儲。統(tǒng)一變量緩沖區(qū)對象
在某些條件下比單獨的統(tǒng)一變量有更多優(yōu)勢糕韧。利用統(tǒng)一變量緩沖區(qū)對象
喻圃,統(tǒng)一變量緩沖區(qū)數(shù)據(jù)
可以在多個程序中共享,但只需要設置一次斧拍。而且統(tǒng)一變量緩沖區(qū)對象
一般可以存儲更大量的統(tǒng)一變量數(shù)據(jù)
。最后愚墓,在統(tǒng)一緩沖區(qū)對象
之間切換比一次單獨加載一個統(tǒng)一變最更高效昂勉。
統(tǒng)一緩沖區(qū)對象
可以在OpenGL ES
著色語言中通過應用統(tǒng)一變量塊使用。
uniform TransformBlock {
mat4 matViewProj;
mat3 matNormal;
mat3 matTexGen;
};
上述聲明了一個名為TransformBlock
且包含3個矩陣的統(tǒng)一變量塊硼啤。名稱TransformBlock
將供應用程序使用议经,統(tǒng)一緩沖區(qū)對象函數(shù)glGetUniformBlocklndex
中的blockName
參數(shù)。統(tǒng)一變量塊聲明中的變量在著色器中都可以訪問谴返,就像常規(guī)形式聲明的變量一樣煞肾。
#version 300 es
uniform TransformBlock {
mat4 matViewProj;
mat3 matNormal;
mat3 matTexGen;
};
layout(location = 0) in vec4 a_position;
void main{
gl_Position = matViewProj * a_position;
}
布局限定符
可用于指定支持統(tǒng)一變量塊的統(tǒng)一緩沖區(qū)對象在內存中的布局方式。布局限定符
可以提供給單獨的統(tǒng)一變量塊
嗓袱,或者用于所有統(tǒng)一變量塊籍救。在全局作用域內,為所有統(tǒng)一變量塊設置默認布局的方法如下:
layout(shared, column_major) uniform; // 如果未指定渠抹,則為默認
layout(packed, row_major) uniform;
單獨的統(tǒng)一變量塊也可以通過覆蓋全局作用域上的默認設置來設置布局蝙昙。此外統(tǒng)一變量塊中的單獨統(tǒng)一變量也可以指定布局限定符
layout(stdl40) uniform TransformBlock
{
mat4 matViewProj;
layout(row_major) mat3 matNormal;
mat3 matTexGen;
};
可以用于統(tǒng)一變量塊的所有布局限定符:
限定符 | 描述 |
---|---|
shared |
shared 限定符指定多個著色器或者多個程序中統(tǒng)一變量塊的內存布局相同。要使用這個限定符梧却,不同定義中的row_major/column_major 值必須相等奇颠。覆蓋stdl40 和packed (默認) |
packed |
packed 布局限定符指定編譯器可以優(yōu)化統(tǒng)一變量塊的內存布局。使用這個限定符時必須查詢偏移位置放航,而且統(tǒng)一變量塊無法在頂點/片段著色器或者程序間共享烈拒。覆蓋stdl40 和shared
|
stdl40 |
sldl40 布局限定符指定統(tǒng)一變童塊的布局基于OpenGL ES 3.0 規(guī)范中定義的一組標準規(guī)則。覆蓋shared 和packed
|
row_major |
矩陣在內存中以行優(yōu)先順序布局 |
column_major |
矩陣在內存中以列優(yōu)先順序布局(默認) |
頂點和片段著色器輸入/輸出
OpenGL ES
著色器語言的另一個特殊變量類型是頂點輸入(或者屬性)
變量广鳍。頂點輸入變量用于指定頂點著色器中每個頂點的輸入荆几,用in關鍵字
指定。它們通常存儲位置赊时、法線吨铸、 紋理坐標和顏色
這樣的數(shù)據(jù)。這里的關鍵是理解頂點輸入是為繪制的每個頂點指定的數(shù)據(jù)祖秒。
頂點著色器
#version 300 es
uniform mat4 u_matViewProjection;
//輸入
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec3 a_color;
out vec3 v_color;
void main{)
{
gl_Position = u_matViewProjection * a_position;
v_color = a_color;
}
片段著色器
#version 300 es
precision mediump float;
in vec4 vColor;
//輸出片段著色器
layout (location = 0) out vec4 fragColor;
void main() {
fragColor = vColor;
}
這個著色器的兩個頂點輸入變量a_position和a_color
的數(shù)據(jù)由應用程序加載诞吱。本質上,應用程序將為每個頂點創(chuàng)建一個頂點數(shù)組竭缝,該數(shù)組包含位置和顏色
狐胎。注意上面的例子中頂點輸入變量之前使用了layout限定符
握巢。這種情況下的布局限定符用于指定頂點屬性的索引暴浦。布局限定符是可選的歌焦,如果沒有指定,鏈接程序將自動為頂點輸入變量分配位置
。
和統(tǒng)一變量
—樣卵史,底層硬件通常在可輸入頂點著色器的屬性變量數(shù)目上有限制
以躯。OpenGL ES實現(xiàn)支持的最大屬性數(shù)量由內建變量gl_MaxVertexAttribs
給出(也可以使用glGetlntegerv
査詢GL_MAX_VERTEX_ATTRIBS
得到)忧设。OpenGL ES 3.0實現(xiàn)可支持的最小屬性為16個
址晕。 不同的實現(xiàn)可以支持更多變量斩箫,但是如果想要編寫保證能在任何OpenGL ES 3.0實現(xiàn)上運行的著色器乘客,則應該將屬性限制為不多于16個易核。
來自頂點著色器的輸出變量
由out關鍵字
指定牡直。上面的示例代碼中碰逸,v_color
變量被聲明為輸出變量饵史,其內容從a_color輸入變量
中復制而來。每個頂點著色器將在一個或者多個輸出變量中輸出需要傳遞給片段著色器的數(shù)據(jù)夭织。然后尊惰,這些變量也會在片段著色器中聲明為in變量
(相符類型),在光柵化階段中對圖元進行線性插值
鞋诗。
片段著色器
中與頂點著色器
的頂點輸出v_Color
相匹配的輸入聲明如下:
in vec3 v_color;
注意:
與頂點著色器輸入不同担孔,頂點著色器輸出/片段著色器輸入變量不能有布局限定符
。OpenGL ES
實現(xiàn)自動選擇位置啄育,與統(tǒng)一變量和頂點輸入屬性相同,底層硬件通常限制頂點著色器輸出/片段著色器輸入
(在硬件上墩崩,這些變量通常被稱作插值器)的數(shù)量铝阐。 OpenGL ES實現(xiàn)支持的頂點著色器輸出的數(shù)量由內建變量gl_MaxVertexOutputVectors
給出(用glGetlntegerv
査詢GL_MAX_VERTEX_OUTPUT_COMPONENTS
將提供總分量值數(shù)量徘键,而非向量數(shù)量)吹害。OpenGLES 3.0實現(xiàn)可以支持的最小頂點輸出向量數(shù)為16
它呀。與此類似钟些,OpenGL ES 3.0實現(xiàn)支持的片段著色器輸入的數(shù)量由gl_MaxFragmentInputVectors
給出(用glGetlntegerv
查詢GL_MAX_FRAGMENT_INPUT_COMPONENTS
將提供總分量值數(shù)量,而非向量數(shù)量)篙耗。OpenGL ES 3.0實現(xiàn)可以支持的最小片段輸入向量數(shù)為15
。
上述的片段著色器將輸出一個或者多個顏色宗弯。在正常情況下辕棚,我們只渲染到一個顏色緩沖區(qū)逝嚎,在這種時候补君,布局限定符是可選的(假定輸出變量進入位置0)
挽铁。但是,當渲染到多個渲染目標(MRT)
時够掠,我們可以使用布局限定符指定每個輸出前往的渲染目標疯潭。對于這種情況,在片段著色器中會有個輸出變量脊僚,該值將是傳遞給管線逐片段操作部分的輸出顏色增淹。
插值限定符
上面的示例中虑润,我們聲明了自己的頂點著色器輸出和片段著色器輸入拳喻,沒有使用任何限定符钦勘。在沒有限定符時彻采,默認的插值行為是執(zhí)行平滑著色肛响。也就是說终惑,來自頂點著色器的輸出變量在圖元中線性插值,片段著色器接收線性插值之后的數(shù)值作為輸入
臼寄。我們可以明確地請求平滑著色,在這種情況下留攒,輸出/輸入如下:
// 頂點著色器輸出
smooth out vec3 v_color;
// 片段著色器輸入
smooth in vec3 v_color;
OpenGL ES 3.0
還引入了另一種插值——平面著色
。在平面著色中剪侮,圖元中的值沒有進行插值杰标,而是將其中一個頂點視為驅動頂點(Provoking Vertex腔剂,取決于圖元類型)驻仅,該頂點的值被用于圖元中的所有片段噪服。我們可以聲明如下的平面著色輸出/ 輸入:
// 頂點著色器輸出
flat out vec3 v_color;
// 片段著色器輸入
flat in vec3 v_color;
最后,可以用centroid
關鍵字在插值器中添加另一個限定符呻顽。使用多重采樣渲染時雹顺,centroid關鍵字
可用于強制插值發(fā)生在被渲染圖元內部(否則,在圖元的邊緣可能出現(xiàn)偽像)廊遍。
質心采樣
的輸出/輸入變量的方法嬉愧。
// 頂點著色器輸出
smooth centroid out vec3 v_color
// 頂點著色器輸出
smooth centroid in vec3 v_color
統(tǒng)一變量和插值器打包
底層硬件中可用于每個變量存儲的資源是固定的。統(tǒng)一變量通常保存在所謂的"常量存儲"
中喉前,這可以看作向量的物理數(shù)組没酣。頂點著色器輸出/片段著色器輸入一般保存在插值器中
,這通 常也保存為一個向量數(shù)組卵迂。著色器可能聲明各種類型的統(tǒng)一變量和著色器輸入/輸出见咒,包括標量视事、各種向量分量和矩陣犬性。但是鹤耍,這些變量聲明如何映射到硬件上的可用物理空間呢族购?換言之瑟幕,如果一個OpenGL ES 3.0
實現(xiàn)支持16個頂點著色器輸出向量排吴,那 么物理存儲實際上是如何使用的呢扯键?
在OpenGL ES 3.0中厉亏,這個問題通過打包規(guī)則處理窝趣,該規(guī)則定義插值器和統(tǒng)一變量映射到物理存儲空間的方式
。打包規(guī)則基于物理存儲空間被組織為一個每個存儲位置4列(每個向量分量一列)和1行的網格的概念儒将。打包規(guī)則尋求打包變量,使生成代碼的復雜度保持不變刚操。換言之,打包規(guī)則不進行重排序操作叭喜,而是試圖在不對運行時性能產生負面影響的情況下盯腌,優(yōu)化物理地址空間的使用甚淡。
uniform mat3 m;
uniform float f[6];
uniform vec3 v;
如果完全不進行打包,許多常量存儲空間將被浪費契吉。矩陣m
將占據(jù)3行佩憾,數(shù)組f
占據(jù)6行鬼廓,向量v
占據(jù)1行,共需要10行才能存儲這些變量萎河。
未打包的統(tǒng)一變量存儲
位置 | X | Y | Z | W |
---|---|---|---|---|
0 | m[0].x | m[0].y | m[0].z | m[0].w |
1 | m[1].x | m[1].y | m[1].z | m[1].w |
2 | m[2].x | m[2].y | m[2].z | m[2].w |
3 | f[0] | - | - | - |
4 | f[1] | - | - | - |
5 | f[2] | - | - | - |
6 | f[3] | - | - | - |
7 | f[4] | - | - | - |
8 | f[5] | - | - | - |
9 | v.x | v.y | v.z | -6 |
打包的統(tǒng)一變量存儲
位置 | X | Y | Z | W |
---|---|---|---|---|
0 | m[0].x | m[0].y | m[0].z | f[0] |
1 | m[1].x | m[1].y | m[1].z | f[1] |
2 | m[2].x | m[2].y | m[2].z | f[2] |
3 | v.x | v.y | v.z | f[3] |
4 | - | - | - | f[4] |
5 | - | - | - | f[5] |
在使用打包規(guī)則時,只需使用6個物理常量位置述呐。數(shù)組f
的元素會跨越行的邊界惩淳,原因是GPU通常會按照向量位置索引對常量存儲進行索引。打包必須使數(shù)組跨越行邊界乓搬,這樣索引才能夠起作用思犁。
所有打包對OpenGL ES著色語言的用戶都是完全透明的,除了一個細節(jié):打包影響統(tǒng)一變量和頂點著色器輸出/片段著色器輸入的計數(shù)方式
进肯。如果想要編寫保證能夠在所有OpenGL ES 3.0實現(xiàn)上運行的著色器激蹲,就不應該使用打包之后超過最小運行存儲大小的統(tǒng)一變量或者插值器。
精度限定符
梢度限定符使著色器創(chuàng)作者可以指定著色器變量的計算精度江掩。變量可以聲明為低学辱、中或者高精度。這些限定符用于提示編譯器允許在較低的范圍和精度上執(zhí)行變量計算环形。在較低的精度上策泣,有些OpenGLES實現(xiàn)在運行著色器時可能更快,或者電源效率更高抬吟。
當然萨咕,這種效率提升是以精度為代價的,在沒有正確使用精度限定符時可能造成偽像火本。
精度限定符可以用于指定任何基于浮點數(shù)或者整數(shù)的變量的精度
危队。指定精度的關鍵字是lowp、mediump和highp
钙畔。
highp vec4 position;
varying lowp vec4 color;
mediump float specularExp;
如果變量聲明時沒有使用精度限定符茫陆,它將擁有該類型的默認精度
。默認精度限定符在頂點或者片段著色器的開頭指定:
precision highp float;
precision mediump int;
為float類型
指定的精度將用作所有基于浮點值的變量的默認精度擎析。同樣簿盅,為int類型
指定的精度將用作所有基于整數(shù)的變量的默認精度。
在頂點著色器中叔锐,如果沒有指定默認精度挪鹏,則int和float的默認精度都為highp。也就是說愉烙,頂點著色器中所有沒用精度限定符聲明的變量都使用最高的精度
。片段著色器的規(guī)則與此不同解取。在片段著色器中步责,浮點值沒有默認的精度值,每個著色器必須聲明一個默認的float精度,或者為每個float變量指定精度蔓肯。
不變性
OpenGL ES著色語言中引入的invariant關鍵字
可以用于任何可變的頂點著色器輸出遂鹊。
由于著色器需要編譯,而編譯器可能進行導致指令重新排序的優(yōu)化蔗包。這種指令重排意味著兩個著色器之間的等價計算不能保證產生完全相同的結果
秉扑。這種不一致性在多遍著色器特效時尤其可能成為問題,在這種情況下调限,相同的對象用Alpha混合繪制在自身上方舟陆。如果用于計算輸出位置的數(shù)值的精度不完全一樣,精度差異就會導致偽像耻矮。
因為編譯器需要保證不變性秦躯,所以可能限制它所做的優(yōu)化。因此裆装,invariant限定符
應該只在必要時使用踱承,否則可能導致性能下降。
項目地址:
https://github.com/byhook/opengles4android
參考:
《OpenGL ES 3.0 編程指南第2版》