来自云龙湖轮廓分明的月亮 发表于 2024-12-9 02:48:58

Android显示体系(04)- OpenGL ES - Shader绘制三角形

Android显示体系(02)- OpenGL ES - 概述
Android显示体系(03)- OpenGL ES - GLSurfaceView的使用
Android显示体系(04)- OpenGL ES - Shader绘制三角形
Android显示体系(05)- OpenGL ES - Shader绘制三角形(使用glsl文件)
Android显示体系(06)- OpenGL ES - VBO和EBO和VAO
Android显示体系(07)- OpenGL ES - 纹理Texture
Android显示体系(08)- OpenGL ES - 图片拉伸
一、媒介:

OpenGL 1.0接纳固定管线,OpenGL 2.0以上版本告急的改变就是接纳了可编程管线,Shader 编程是指使用着色器(Shader)编写代码来控制图形渲染管线中特定阶段的处理过程。在图形渲染中,着色器是在 GPU 上实行的小型步伐,用于界说图形渲染管线中不同阶段的处理逻辑,以实现各种视觉结果,那么渲染管线哪个阶段可编程呢?
https://i-blog.csdnimg.cn/direct/dea8995ac06749e69138e966e420c1f1.png
就是上图的渲染管线蓝色部分。
二、Shader介绍:

1、Vertex Shader和Fragment Shader:

在当代的图形渲染中,通常会使用两种主要范例的着色器:极点着色器(Vertex Shader)和片元着色器(Fragment Shader)。这两种着色器分别负责处理图形的极点数据和片元(像素)数据,通过编写这些着色器步伐,开辟职员可以实现各种复杂的图形结果和渲染技能。
下面是对极点着色器和片元着色器的简要介绍:


[*]极点着色器:

[*]极点着色器用于处理图形的极点数据,如位置、颜色、法线等。
[*]主要作用包括对极点位置的变换(如模型变换、视图变换、投影变换)、法线变换、极点着色等。
[*]极点着色器的输出通常是裁剪空间坐标或者屏幕空间坐标。

[*]片元着色器:

[*]片元着色器用于处理图元的片元(像素)数据,负责盘算最终的颜色输出。
[*]在片元着色器中,开辟职员可以实现光照、纹理映射、阴影、透明度等结果。
[*]片元着色器的输出通常是片元的颜色、深度值、法线等。

2、图元:

在Shader编程中,“图元”(Primitive)指的是基本的几何图形单位,通常是指在3D图形渲染中的基本几何外形,如点、线、三角形、四边形等。这些基本的几何图形单位是构成复杂场景的基础,它们通过渲染管线举行处理和转换,最终呈如今屏幕上。
在Shader编程中,图元是渲染管线处理的基本单位,Shader步伐会对每个图元举行相应的处理,包括极点变换、光照盘算、纹理映射等操纵,最终将图元渲染到屏幕上。常见的几何图元有以下几种:

[*]点(Point):最简单的图元,通常用于表示粒子、光源等。
[*]线(Line):由两个点构成的图元,可用于绘制线条、边缘等。
[*]三角形(Triangle):由三个极点构成的图元,是最基本的多边形,是3D图形学中最告急的图元之一,因为所有复杂的表面都可以由三角形网格构成。
[*]四边形(Quadrilateral):由四个极点构成的图元,通常被拆分为两个三角形处理。
Shader步伐通过对这些基本图元举行处理和变换,最终形成了复杂的场景和图像。在Shader编程中,开辟职员可以通过编写极点着色器和片元着色器来对这些图元举行处理,实现各种视觉结果和渲染技能。处理图元是Shader步伐中的一个告急任务,它直接影响着最终的渲染结果和性能。
3、三角形:

上面的四个图元中,其实最告急的是三角形,我们把大多数复杂的图形都可以用三角形拼起来,就像我小时间(不敢说你们00后小时间)糊灯笼一样。
好比,我之前文章提到的这个复杂的图,也都是由浩繁小三角形构成:
https://i-blog.csdnimg.cn/direct/61bf739c447f4c4fab5d92856d26e090.png
三、告急坐标系:

在 OpenGL ES 中,“标准设备坐标系”(Normalized Device Coordinates)和“屏幕坐标系”(Screen Coordinates)是两种不同的坐标系,它们在图形渲染过程中扮演不同的角色。

[*]标准设备坐标系(Normalized Device Coordinates):

[*]标准设备坐标系是一个抽象的坐标系,它是一个以屏幕空间的中心为原点,范围从 -1 到 1 的立方体空间。
[*]在标准设备坐标系中,坐标 (0, 0) 表示屏幕中心,(-1, -1) 表示左下角,(1, 1) 表示右上角。
[*]所有的极点数据在通过VertexShader处理后都会被映射到标准设备坐标系,这是 OpenGL ES 中举行图形变换和裁剪的标准坐标系。

[*]屏幕坐标系(Screen Coordinates):

[*]屏幕坐标系是现实显示设备的坐标系,它通常以左上角为原点,向右为 x 轴正方向,向下为 y 轴正方向。
[*]屏幕坐标系的坐标值通常是以像素为单位的整数值,用来确定在屏幕上绘制图像和文本等元素的位置。

在 OpenGL ES 渲染过程中,极点数据起首被界说在对象坐标系中,然后通过模型变换、视图变换和投影变换将其转换到标准设备坐标系中,最终在屏幕上绘制出来。
https://i-blog.csdnimg.cn/direct/79380110b6554f20b48dd152ca24da33.png
总结来说,标准设备坐标系是为了方便举行图形变换和裁剪而界说的坐标系,而屏幕坐标系则是现实显示设备上的坐标系,用于确定最终图像的位置。OpenGL ES 中的渲染过程涉及将极点数据从对象坐标系转换到标准设备坐标系,最终映射到屏幕坐标系举行显示。
四、GLSL语言:

1、概念:

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形盘算量身定制的,它包罗一些针对向量和矩阵操纵的有效特性。
着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。
一个典型的着色器有下面的结构:
#version version_number
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

void main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
当我们特殊评论到极点着色器的时间,每个输入变量也叫极点属性(Vertex Attribute)。我们能声明的极点属性是有上限的,它一样平常由硬件来决定。OpenGL确保至少有16个包罗4分量的极点属性可用,但是有些硬件或许允许更多的极点属性,你可以查询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、数据传递:

https://i-blog.csdnimg.cn/direct/04655abb46ad402ca3c004429807b99d.png


[*]可以将Prog想象成一个芯片,有许多的引脚,好比这儿的1绑定的是VertextShader,2绑定的是FragmentShader。
[*]极点着色器Vertex负责确定3D图形的三个极点,片元着色器Fragment负责确定每个像素的颜色;
[*]数据传给1的时间,就会自动传给vPosition;看看具体怎么传递的:

[*]通过glGetAttribLocation获取vPositon的引脚;
[*]glEnableVertextAttribArray传入的参数就是上一步获取的ID,这样,就可以打开这个引脚;
[*]通过glVertexAttribPointer将准备好的极点数据Vertex Buffer Object传递给引脚1,这样vPosition就收到了;

[*]数据传给2的时间,就会自动传给vColor;

[*]通过glGetUniformLocation获取引脚;
[*]通过glUniform4fv传递给vColor即可;

3、变量:

1)vertex shader变量:

https://i-blog.csdnimg.cn/direct/d001f7cf2002420eb1dd3fd8d5aafcc5.png


[*]输入变量有:Attribute范例、Uniforms范例(unform变量相当于全局变量)、Samplers范例;
[*]输出变量有:Varying范例(Vertex Shader的输出,可以作为后续的Shader的输入)
[*]内部变量有:gl_Position(这个最告急);

[*]以gl开头的变量都是内部变量;
[*]vertex shader需要给gl_Position赋值来确定极点的位置;
[*]渲染管线会根据gl_Position举行图元装配;

2)fragment shader变量:

https://i-blog.csdnimg.cn/direct/c6666e84b990413dbaf4cab555d62968.png


[*]Varying范例:作为Fragment Shader的输入,和Vertex Shader的输出一一对应;
[*]gl_FragColor:内部变量,是Fragment Shader的输出,生存每个像素的颜色;

[*]确定每个像素的最终颜色;
[*]可以和纹理结合,实现纹理的映射;
[*]还可以通过它实现光照、高亮等颜色殊效;

五、绘制一个三角形:

1、步骤:



[*] 使用GLSurfaceView创建OpenGL ES情况;
[*] 界说极点着色器和片元着色器以及OpenGL ES步伐;
[*] 编译极点着色器和片元着色器;

[*]使用GLES30.glCreateShader()创建Shader对象;
[*]使用GLES30.glShaderSource()绑定Shader和其源代码;
[*]使用GLES30.glCompileShader()编译Shader;

[*] 链接OpenGL ES步伐;

[*]使用GLES30.glCreateProgram()创建OpenGL ES步伐;
[*]使用GLES30.glAttachShader()绑定Shader到OpenGL ES步伐;
[*]使用GLES30.glLinkProgram()链接整个OpenGL ES步伐;

[*] 使用OpenGL ES步伐;

[*]调用GLES30.glUseProgram()使用OpenGL ES步伐;
[*]传递极点数据和片元数据

2、创建OpenGL ES情况:

界说GLSurfaceView:
// 文件路径:com/example/glsurfaceviewdemo/GLSurfaceViewTest.java
public class GLSurfaceViewTest extends GLSurfaceView {
    public GLSurfaceViewTest(Context context) {
      super(context);

      // 设置OpenGL ES版本(由于3.0兼容2.0,我们使用3.0)
      setEGLContextClientVersion(3);

      // 设置渲染器Renderer,函数调用后,里面会启动一个新线程构造EGL环境
      setRenderer(new GLRenderTest());
    }
}
MainActivity使用GLSurfaceView:
// 文件路径:com/example/glsurfaceviewdemo/MainActivity.java
public class MainActivity extends AppCompatActivity {
    private GLSurfaceViewTest mGlSurfaceViewTest;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      mGlSurfaceViewTest = new GLSurfaceViewTest(this);
      setContentView(mGlSurfaceViewTest);
    }
}
3、界说极点着色器和片元着色器:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.java
// 定义的顶点着色器代码
    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;" +
                  "}";

    // 定义的三角形顶点坐标数组
    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的颜色数组,表示每个像素的颜色
    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.java
        public Triangle() {
      // ...

      // 2.加载并编译vertexShader和fragmentShader
      int vertexShader = Companion.compileShader(GLES30.GL_VERTEX_SHADER, mVertexShaderCode);
      int fragmentShader = Companion.compileShader(GLES30.GL_FRAGMENT_SHADER, mFragmentShaderCode);
      
       // ...
        }
    // 定义静态内部类
    public static class Companion {
      // 创建并编译着色器
      public static int compileShader(int type, String shaderCode) {
            // 创建一个着色器
            int shader = GLES30.glCreateShader(type);
            // 将着色器代码设置到着色器对象中
            GLES30.glShaderSource(shader, shaderCode);
            // 编译着色器
            GLES30.glCompileShader(shader);
            return shader;
      }
    }
6、链接OpenGL ES步伐:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.java
        public Triangle() {
      // ...
      // 4.attach两个编译好的着色器到program当中
      GLES30.glAttachShader(mProgram, vertexShader);
      GLES30.glAttachShader(mProgram, fragmentShader);
      // 5.链接整个program
      GLES30.glLinkProgram(mProgram);
    }
7、使用OpenGL ES步伐:

// 文件路径:com/example/glsurfaceviewdemo/Triangle.java
    public void draw() {
      // 使用program
      GLES30.glUseProgram(mProgram);
      // 获取顶点着色器的位置句柄
      mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
      // 启用顶点属性数组
      GLES30.glEnableVertexAttribArray(mPositionHandle);
      // 准备三角形坐标数据
      // 重置缓冲区位置
      mVertexBuffer.position(0);
      // 指定顶点属性数据的格式和位置
      GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, mVertexBuffer);

      // 获取片元着色器的颜色句柄
      mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
      // 设置绘制三角形的颜色
      GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);
      // 绘制三角形
      GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);
      // 禁用顶点属性数组
      GLES30.glDisableVertexAttribArray(mPositionHandle);
    }
这就是一个绘制三角形的函数;
8、渲染器调用:

在渲染器中周期性地调用上面的绘制函数。
// 文件路径:com/example/glsurfaceviewdemo/GLRenderTest.java
public class GLRenderTest implements GLSurfaceView.Renderer {
    private Triangle mTriangle;
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
      mTriangle = new Triangle();
    }

    @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);
      mTriangle.draw();
    }
}
9、运行结果:

https://i-blog.csdnimg.cn/direct/c84a21ac29ca4b7e9b1e1cf13dad80da.png
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;

    @Override
    protected 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);

      // 设置OpenGL ES版本(由于3.0兼容2.0,我们使用3.0)
      setEGLContextClientVersion(3);

      // 设置渲染器Renderer,函数调用后,里面会启动一个新线程构造EGL环境
      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;
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      GLES30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
      mTriangle = new Triangle();
    }

    @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);
      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.初始化顶点缓冲区,存储三角形坐标
      // 为顶点坐标分配DMA内存空间
      ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);
      // 设置字节顺序为本地字节顺序(会根据硬件架构自适应大小端)
      byteBuffer.order(ByteOrder.nativeOrder());
      // 将字节缓冲区转换为浮点缓冲区
      mVertexBuffer = byteBuffer.asFloatBuffer();
      // 将顶点三角形坐标放入缓冲区
      mVertexBuffer.put(mTriangleCoords);
      // 设置缓冲区的位置指针到起始位置
      mVertexBuffer.position(0);

      // 2.加载并编译vertexShader和fragmentShader
      int vertexShader = Companion.compileShader(GLES30.GL_VERTEX_SHADER, mVertexShaderCode);
      int fragmentShader = Companion.compileShader(GLES30.GL_FRAGMENT_SHADER, mFragmentShaderCode);
      // 3.创建一个OpenGL程序
      mProgram = GLES30.glCreateProgram();
      // 4.attach两个编译好的着色器到program当中
      GLES30.glAttachShader(mProgram, vertexShader);
      GLES30.glAttachShader(mProgram, fragmentShader);
      // 5.链接整个program
      GLES30.glLinkProgram(mProgram);
    }

    // 顶点数据是float类型,因此,使用这个存储
    private FloatBuffer mVertexBuffer;
    private int mProgram;
    // 定义的顶点着色器代码
    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;" +
                  "}";

    // 定义的三角形顶点坐标数组
    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的颜色数组,表示每个像素的颜色
    private final float[] mColor = new float[]{0.0f, 1.0f, 0.0f, 1.0f};
    // 顶点着色器的位置句柄
    private int mPositionHandle = 0;
    // 片元着色器的位置句柄
    private int mColorHandle = 0;
    private final int COORDS_PER_VERTEX = 3;

    // 定义静态内部类
    public static class Companion {
      // 创建并编译着色器
      public static int compileShader(int type, String shaderCode) {
            // 创建一个着色器
            int shader = GLES30.glCreateShader(type);
            // 将着色器代码设置到着色器对象中
            GLES30.glShaderSource(shader, shaderCode);
            // 编译着色器
            GLES30.glCompileShader(shader);
            return shader;
      }
    }

    public void draw() {
      // 使用program
      GLES30.glUseProgram(mProgram);
      // 获取顶点着色器的位置句柄
      mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
      // 启用顶点属性数组
      GLES30.glEnableVertexAttribArray(mPositionHandle);
      // 准备三角形坐标数据
      // 重置缓冲区位置
      mVertexBuffer.position(0);
      // 指定顶点属性数据的格式和位置
      GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, mVertexBuffer);

      // 获取片元着色器的颜色句柄
      mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
      // 设置绘制三角形的颜色
      GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);
      // 绘制三角形
      GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);
      // 禁用顶点属性数组
      GLES30.glDisableVertexAttribArray(mPositionHandle);
    }
}

六、总结:

本文主要介绍了Shader以及GLSL语言,同时画出了一个三角形,但是一样平常工程中GLSL语言会用单独的文件写,后续我们改进下!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Android显示体系(04)- OpenGL ES - Shader绘制三角形