網(wǎng)站定制的公司哪家好百度模擬搜索點(diǎn)擊軟件
一、前言:
OpenGL 1.0采用固定管線,OpenGL 2.0以上版本重要的改變就是采用了可編程管線,Shader 編程是指使用著色器(Shader)編寫代碼來控制圖形渲染管線中特定階段的處理過程。在圖形渲染中,著色器是在 GPU 上執(zhí)行的小型程序,用于定義圖形渲染管線中不同階段的處理邏輯,以實(shí)現(xiàn)各種視覺效果,那么渲染管線哪個階段可編程呢?
就是上圖的渲染管線藍(lán)色部分。
二、Shader介紹:
1、Vertex Shader和Fragment Shader:
在現(xiàn)代的圖形渲染中,通常會使用兩種主要類型的著色器:頂點(diǎn)著色器(Vertex Shader)和片元著色器(Fragment Shader)。這兩種著色器分別負(fù)責(zé)處理圖形的頂點(diǎn)數(shù)據(jù)和片元(像素)數(shù)據(jù),通過編寫這些著色器程序,開發(fā)人員可以實(shí)現(xiàn)各種復(fù)雜的圖形效果和渲染技術(shù)。
下面是對頂點(diǎn)著色器和片元著色器的簡要介紹:
- 頂點(diǎn)著色器:
- 頂點(diǎn)著色器用于處理圖形的頂點(diǎn)數(shù)據(jù),如位置、顏色、法線等。
- 主要作用包括對頂點(diǎn)位置的變換(如模型變換、視圖變換、投影變換)、法線變換、頂點(diǎn)著色等。
- 頂點(diǎn)著色器的輸出通常是裁剪空間坐標(biāo)或者屏幕空間坐標(biāo)。
- 片元著色器:
- 片元著色器用于處理圖元的片元(像素)數(shù)據(jù),負(fù)責(zé)計(jì)算最終的顏色輸出。
- 在片元著色器中,開發(fā)人員可以實(shí)現(xiàn)光照、紋理映射、陰影、透明度等效果。
- 片元著色器的輸出通常是片元的顏色、深度值、法線等。
2、圖元:
在Shader編程中,“圖元”(Primitive)指的是基本的幾何圖形單元,通常是指在3D圖形渲染中的基本幾何形狀,如點(diǎn)、線、三角形、四邊形等。這些基本的幾何圖形單元是構(gòu)成復(fù)雜場景的基礎(chǔ),它們通過渲染管線進(jìn)行處理和轉(zhuǎn)換,最終呈現(xiàn)在屏幕上。
在Shader編程中,圖元是渲染管線處理的基本單位,Shader程序會對每個圖元進(jìn)行相應(yīng)的處理,包括頂點(diǎn)變換、光照計(jì)算、紋理映射等操作,最終將圖元渲染到屏幕上。常見的幾何圖元有以下幾種:
- 點(diǎn)(Point):最簡單的圖元,通常用于表示粒子、光源等。
- 線(Line):由兩個點(diǎn)組成的圖元,可用于繪制線條、邊緣等。
- 三角形(Triangle):由三個頂點(diǎn)組成的圖元,是最基本的多邊形,是3D圖形學(xué)中最重要的圖元之一,因?yàn)樗袕?fù)雜的表面都可以由三角形網(wǎng)格構(gòu)成。
- 四邊形(Quadrilateral):由四個頂點(diǎn)組成的圖元,通常被拆分為兩個三角形處理。
Shader程序通過對這些基本圖元進(jìn)行處理和變換,最終形成了復(fù)雜的場景和圖像。在Shader編程中,開發(fā)人員可以通過編寫頂點(diǎn)著色器和片元著色器來對這些圖元進(jìn)行處理,實(shí)現(xiàn)各種視覺效果和渲染技術(shù)。處理圖元是Shader程序中的一個重要任務(wù),它直接影響著最終的渲染效果和性能。
3、三角形:
上面的四個圖元中,其實(shí)最重要的是三角形,我們把大多數(shù)復(fù)雜的圖形都可以用三角形拼起來,就像我小時(shí)候(不敢說你們00后小時(shí)候)糊燈籠一樣。
比如,我之前文章提到的這個復(fù)雜的圖,也都是由眾多小三角形構(gòu)成:
三、重要坐標(biāo)系:
在 OpenGL ES 中,“標(biāo)準(zhǔn)設(shè)備坐標(biāo)系”(Normalized Device Coordinates)和“屏幕坐標(biāo)系”(Screen Coordinates)是兩種不同的坐標(biāo)系,它們在圖形渲染過程中扮演不同的角色。
- 標(biāo)準(zhǔn)設(shè)備坐標(biāo)系(Normalized Device Coordinates):
- 標(biāo)準(zhǔn)設(shè)備坐標(biāo)系是一個抽象的坐標(biāo)系,它是一個以屏幕空間的中心為原點(diǎn),范圍從 -1 到 1 的立方體空間。
- 在標(biāo)準(zhǔn)設(shè)備坐標(biāo)系中,坐標(biāo) (0, 0) 表示屏幕中心,(-1, -1) 表示左下角,(1, 1) 表示右上角。
- 所有的頂點(diǎn)數(shù)據(jù)在通過
VertexShader
處理后都會被映射到標(biāo)準(zhǔn)設(shè)備坐標(biāo)系,這是 OpenGL ES 中進(jìn)行圖形變換和裁剪的標(biāo)準(zhǔn)坐標(biāo)系。
- 屏幕坐標(biāo)系(Screen Coordinates):
- 屏幕坐標(biāo)系是實(shí)際顯示設(shè)備的坐標(biāo)系,它通常以左上角為原點(diǎn),向右為 x 軸正方向,向下為 y 軸正方向。
- 屏幕坐標(biāo)系的坐標(biāo)值通常是以像素為單位的整數(shù)值,用來確定在屏幕上繪制圖像和文本等元素的位置。
在 OpenGL ES 渲染過程中,頂點(diǎn)數(shù)據(jù)首先被定義在對象坐標(biāo)系中,然后通過模型變換、視圖變換和投影變換將其轉(zhuǎn)換到標(biāo)準(zhǔn)設(shè)備坐標(biāo)系中,最終在屏幕上繪制出來。
總結(jié)來說,標(biāo)準(zhǔn)設(shè)備坐標(biāo)系是為了方便進(jìn)行圖形變換和裁剪而定義的坐標(biāo)系,而屏幕坐標(biāo)系則是實(shí)際顯示設(shè)備上的坐標(biāo)系,用于確定最終圖像的位置。OpenGL ES 中的渲染過程涉及將頂點(diǎn)數(shù)據(jù)從對象坐標(biāo)系轉(zhuǎn)換到標(biāo)準(zhǔn)設(shè)備坐標(biāo)系,最終映射到屏幕坐標(biāo)系進(jìn)行顯示。
四、GLSL語言:
1、概念:
著色器是使用一種叫GLSL的類C語言寫成的。GLSL是為圖形計(jì)算量身定制的,它包含一些針對向量和矩陣操作的有用特性。
著色器的開頭總是要聲明版本,接著是輸入和輸出變量、uniform和main函數(shù)。每個著色器的入口點(diǎn)都是main函數(shù),在這個函數(shù)中我們處理所有的輸入變量,并將結(jié)果輸出到輸出變量中。
一個典型的著色器有下面的結(jié)構(gòu):
#version version_number
in type in_variable_name;
in type in_variable_name;out type out_variable_name;uniform type uniform_name;void main()
{// 處理輸入并進(jìn)行一些圖形操作...// 輸出處理過的結(jié)果到輸出變量out_variable_name = weird_stuff_we_processed;
}
當(dāng)我們特別談?wù)摰巾旤c(diǎn)著色器的時(shí)候,每個輸入變量也叫頂點(diǎn)屬性(Vertex Attribute)。我們能聲明的頂點(diǎn)屬性是有上限的,它一般由硬件來決定。OpenGL確保至少有16個包含4分量的頂點(diǎn)屬性可用,但是有些硬件或許允許更多的頂點(diǎn)屬性,你可以查詢GL_MAX_VERTEX_ATTRIBS來獲取具體的上限:
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
通常情況下它至少會返回16個,大部分情況下是夠用了。
2、數(shù)據(jù)傳遞:
- 可以將Prog想象成一個芯片,有很多的引腳,比如這兒的1綁定的是VertextShader,2綁定的是FragmentShader。
- 頂點(diǎn)著色器Vertex負(fù)責(zé)確定3D圖形的三個頂點(diǎn),片元著色器Fragment負(fù)責(zé)確定每個像素的顏色;
- 數(shù)據(jù)傳給1的時(shí)候,就會自動傳給
vPosition
;看看具體怎么傳遞的:- 通過
glGetAttribLocation
獲取vPositon
的引腳; glEnableVertextAttribArray
傳入的參數(shù)就是上一步獲取的ID,這樣,就可以打開這個引腳;- 通過
glVertexAttribPointer
將準(zhǔn)備好的頂點(diǎn)數(shù)據(jù)Vertex Buffer Object
傳遞給引腳1,這樣vPosition
就收到了;
- 通過
- 數(shù)據(jù)傳給2的時(shí)候,就會自動傳給
vColor
;- 通過
glGetUniformLocation
獲取引腳; - 通過
glUniform4fv
傳遞給vColor
即可;
- 通過
3、變量:
1)vertex shader變量:
- 輸入變量有:Attribute類型、Uniforms類型(unform變量相當(dāng)于全局變量)、Samplers類型;
- 輸出變量有:Varying類型(Vertex Shader的輸出,可以作為后續(xù)的Shader的輸入)
- 內(nèi)部變量有:gl_Position(這個最重要);
- 以gl開頭的變量都是內(nèi)部變量;
- vertex shader需要給gl_Position賦值來確定頂點(diǎn)的位置;
- 渲染管線會根據(jù)gl_Position進(jìn)行圖元裝配;
2)fragment shader變量:
- Varying類型:作為Fragment Shader的輸入,和Vertex Shader的輸出一一對應(yīng);
- gl_FragColor:內(nèi)部變量,是Fragment Shader的輸出,保存每個像素的顏色;
- 確定每個像素的最終顏色;
- 可以和紋理結(jié)合,實(shí)現(xiàn)紋理的映射;
- 還可以通過它實(shí)現(xiàn)光照、高亮等顏色特效;
五、繪制一個三角形:
1、步驟:
-
使用
GLSurfaceView
創(chuàng)建OpenGL ES環(huán)境; -
定義頂點(diǎn)著色器和片元著色器以及OpenGL ES程序;
-
編譯頂點(diǎn)著色器和片元著色器;
- 使用
GLES30.glCreateShader()
創(chuàng)建Shader對象; - 使用
GLES30.glShaderSource()
綁定Shader和其源代碼; - 使用
GLES30.glCompileShader()
編譯Shader;
- 使用
-
鏈接OpenGL ES程序;
- 使用
GLES30.glCreateProgram()
創(chuàng)建OpenGL ES程序; - 使用
GLES30.glAttachShader()
綁定Shader到OpenGL ES程序; - 使用
GLES30.glLinkProgram()
鏈接整個OpenGL ES程序;
- 使用
-
使用OpenGL ES程序;
- 調(diào)用
GLES30.glUseProgram()
使用OpenGL ES程序; - 傳遞頂點(diǎn)數(shù)據(jù)和片元數(shù)據(jù)
- 調(diào)用
2、創(chuàng)建OpenGL ES環(huán)境:
定義GLSurfaceView
:
// 文件路徑:com/example/glsurfaceviewdemo/GLSurfaceViewTest.java
public class GLSurfaceViewTest extends GLSurfaceView {public GLSurfaceViewTest(Context context) {super(context);// 設(shè)置OpenGL ES版本(由于3.0兼容2.0,我們使用3.0)setEGLContextClientVersion(3);// 設(shè)置渲染器Renderer,函數(shù)調(diào)用后,里面會啟動一個新線程構(gòu)造EGL環(huán)境setRenderer(new GLRenderTest());}
}
MainActivity
使用GLSurfaceView
:
// 文件路徑:com/example/glsurfaceviewdemo/MainActivity.java
public class MainActivity extends AppCompatActivity {private GLSurfaceViewTest mGlSurfaceViewTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mGlSurfaceViewTest = new GLSurfaceViewTest(this);setContentView(mGlSurfaceViewTest);}
}
3、定義頂點(diǎn)著色器和片元著色器:
// 文件路徑:com/example/glsurfaceviewdemo/Triangle.java
// 定義的頂點(diǎn)著色器代碼private final String mVertexShaderCode ="attribute vec4 vPosition;" +"void main() {" +" gl_Position = vPosition;" +"}";// 定義的片段著色器代碼private final String mFragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +" gl_FragColor = vColor;" +"}";// 定義的三角形頂點(diǎn)坐標(biāo)數(shù)組private final float[] mTriangleCoords = new float[]{0.0f, 0.2f, 0.0f, // 頂部-0.5f, -0.5f, 0.0f, // 左下角0.5f, -0.5f, 0.0f // 右下角};// 定義的fragment的顏色數(shù)組,表示每個像素的顏色private final float[] mColor = new float[]{0.0f, 1.0f, 0.0f, 1.0f};
4、定義OpenGL ES程序:
// 文件路徑:com/example/glsurfaceviewdemo/Triangle.java
private int mProgram;
mProgram = GLES30.glCreateProgram();
5、編譯著色器:
// 文件路徑:com/example/glsurfaceviewdemo/Triangle.javapublic Triangle() {// ...// 2.加載并編譯vertexShader和fragmentShaderint vertexShader = Companion.compileShader(GLES30.GL_VERTEX_SHADER, mVertexShaderCode);int fragmentShader = Companion.compileShader(GLES30.GL_FRAGMENT_SHADER, mFragmentShaderCode);// ...}// 定義靜態(tài)內(nèi)部類public static class Companion {// 創(chuàng)建并編譯著色器public static int compileShader(int type, String shaderCode) {// 創(chuàng)建一個著色器int shader = GLES30.glCreateShader(type);// 將著色器代碼設(shè)置到著色器對象中GLES30.glShaderSource(shader, shaderCode);// 編譯著色器GLES30.glCompileShader(shader);return shader;}}
6、鏈接OpenGL ES程序:
// 文件路徑:com/example/glsurfaceviewdemo/Triangle.javapublic Triangle() {// ...// 4.attach兩個編譯好的著色器到program當(dāng)中GLES30.glAttachShader(mProgram, vertexShader);GLES30.glAttachShader(mProgram, fragmentShader);// 5.鏈接整個programGLES30.glLinkProgram(mProgram);}
7、使用OpenGL ES程序:
// 文件路徑:com/example/glsurfaceviewdemo/Triangle.javapublic void draw() {// 使用programGLES30.glUseProgram(mProgram);// 獲取頂點(diǎn)著色器的位置句柄mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");// 啟用頂點(diǎn)屬性數(shù)組GLES30.glEnableVertexAttribArray(mPositionHandle);// 準(zhǔn)備三角形坐標(biāo)數(shù)據(jù)// 重置緩沖區(qū)位置mVertexBuffer.position(0);// 指定頂點(diǎn)屬性數(shù)據(jù)的格式和位置GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, mVertexBuffer);// 獲取片元著色器的顏色句柄mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");// 設(shè)置繪制三角形的顏色GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);// 繪制三角形GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);// 禁用頂點(diǎn)屬性數(shù)組GLES30.glDisableVertexAttribArray(mPositionHandle);}
這就是一個繪制三角形的函數(shù);
8、渲染器調(diào)用:
在渲染器中周期性地調(diào)用上面的繪制函數(shù)。
// 文件路徑:com/example/glsurfaceviewdemo/GLRenderTest.java
public class GLRenderTest implements GLSurfaceView.Renderer {private Triangle mTriangle;@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);mTriangle = new Triangle();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES30.glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame(GL10 gl){GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);mTriangle.draw();}
}
9、運(yùn)行結(jié)果:
10、附:全部代碼:
文件路徑:com/example/glsurfaceviewdemo/MainActivity.java
package com.example.glsurfaceviewdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;public class MainActivity extends AppCompatActivity {private GLSurfaceViewTest mGlSurfaceViewTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mGlSurfaceViewTest = new GLSurfaceViewTest(this);setContentView(mGlSurfaceViewTest);}
}
文件路徑:com/example/glsurfaceviewdemo/GLSurfaceViewTest.java
package com.example.glsurfaceviewdemo;import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;public class GLSurfaceViewTest extends GLSurfaceView {public GLSurfaceViewTest(Context context) {super(context);// 設(shè)置OpenGL ES版本(由于3.0兼容2.0,我們使用3.0)setEGLContextClientVersion(3);// 設(shè)置渲染器Renderer,函數(shù)調(diào)用后,里面會啟動一個新線程構(gòu)造EGL環(huán)境setRenderer(new GLRenderTest());}
}
文件路徑:com/example/glsurfaceviewdemo/GLRenderTest.java
package com.example.glsurfaceviewdemo;import android.opengl.GLES30;
import android.opengl.GLSurfaceView;import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;public class GLRenderTest implements GLSurfaceView.Renderer {private Triangle mTriangle;@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);mTriangle = new Triangle();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES30.glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame(GL10 gl){GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);mTriangle.draw();}
}
文件路徑:com/example/glsurfaceviewdemo/Triangle.java
package com.example.glsurfaceviewdemo;import android.opengl.GLES30;import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;import javax.microedition.khronos.opengles.GL;public class Triangle {public Triangle() {// 1.初始化頂點(diǎn)緩沖區(qū),存儲三角形坐標(biāo)// 為頂點(diǎn)坐標(biāo)分配DMA內(nèi)存空間ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);// 設(shè)置字節(jié)順序?yàn)楸镜刈止?jié)順序(會根據(jù)硬件架構(gòu)自適應(yīng)大小端)byteBuffer.order(ByteOrder.nativeOrder());// 將字節(jié)緩沖區(qū)轉(zhuǎn)換為浮點(diǎn)緩沖區(qū)mVertexBuffer = byteBuffer.asFloatBuffer();// 將頂點(diǎn)三角形坐標(biāo)放入緩沖區(qū)mVertexBuffer.put(mTriangleCoords);// 設(shè)置緩沖區(qū)的位置指針到起始位置mVertexBuffer.position(0);// 2.加載并編譯vertexShader和fragmentShaderint vertexShader = Companion.compileShader(GLES30.GL_VERTEX_SHADER, mVertexShaderCode);int fragmentShader = Companion.compileShader(GLES30.GL_FRAGMENT_SHADER, mFragmentShaderCode);// 3.創(chuàng)建一個OpenGL程序mProgram = GLES30.glCreateProgram();// 4.attach兩個編譯好的著色器到program當(dāng)中GLES30.glAttachShader(mProgram, vertexShader);GLES30.glAttachShader(mProgram, fragmentShader);// 5.鏈接整個programGLES30.glLinkProgram(mProgram);}// 頂點(diǎn)數(shù)據(jù)是float類型,因此,使用這個存儲private FloatBuffer mVertexBuffer;private int mProgram;// 定義的頂點(diǎn)著色器代碼private final String mVertexShaderCode ="attribute vec4 vPosition;" +"void main() {" +" gl_Position = vPosition;" +"}";// 定義的片段著色器代碼private final String mFragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +" gl_FragColor = vColor;" +"}";// 定義的三角形頂點(diǎn)坐標(biāo)數(shù)組private final float[] mTriangleCoords = new float[]{0.0f, 0.2f, 0.0f, // 頂部-0.5f, -0.5f, 0.0f, // 左下角0.5f, -0.5f, 0.0f // 右下角};// 定義的fragment的顏色數(shù)組,表示每個像素的顏色private final float[] mColor = new float[]{0.0f, 1.0f, 0.0f, 1.0f};// 頂點(diǎn)著色器的位置句柄private int mPositionHandle = 0;// 片元著色器的位置句柄private int mColorHandle = 0;private final int COORDS_PER_VERTEX = 3;// 定義靜態(tài)內(nèi)部類public static class Companion {// 創(chuàng)建并編譯著色器public static int compileShader(int type, String shaderCode) {// 創(chuàng)建一個著色器int shader = GLES30.glCreateShader(type);// 將著色器代碼設(shè)置到著色器對象中GLES30.glShaderSource(shader, shaderCode);// 編譯著色器GLES30.glCompileShader(shader);return shader;}}public void draw() {// 使用programGLES30.glUseProgram(mProgram);// 獲取頂點(diǎn)著色器的位置句柄mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");// 啟用頂點(diǎn)屬性數(shù)組GLES30.glEnableVertexAttribArray(mPositionHandle);// 準(zhǔn)備三角形坐標(biāo)數(shù)據(jù)// 重置緩沖區(qū)位置mVertexBuffer.position(0);// 指定頂點(diǎn)屬性數(shù)據(jù)的格式和位置GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, mVertexBuffer);// 獲取片元著色器的顏色句柄mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");// 設(shè)置繪制三角形的顏色GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);// 繪制三角形GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);// 禁用頂點(diǎn)屬性數(shù)組GLES30.glDisableVertexAttribArray(mPositionHandle);}
}
六、總結(jié):
本文主要介紹了Shader以及GLSL語言,同時(shí)畫出了一個三角形,但是一般工程中GLSL語言會用單獨(dú)的文件寫,后續(xù)我們改進(jìn)下!