三角形怎么画,三角形的正确画法顺序

通过阅读本文,你将获得以下收获:1.什么是着色器2.顶点着色器代码的基本外观以及着色器语言GLSL介绍3.OpenGL的坐标系统4.如何使用着色器上篇回顾主要讲了Android平台上开发OpenGLe

通过阅读本文,你将获得以下收获:

1.什么是着色器

2.顶点着色器代码的基本外观以及着色器语言GLSL介绍

3.OpenGL的坐标系统

4.如何使用着色器

上篇回顾

主要讲了Android平台上开发OpenGL es应用项目的搭建以及EGL配置相关,万丈高楼平地起,上一篇博文已经帮我们打好了地基,那我们的渲染程序,就可以如雨后春笋一般破土而出了。

当前我们已经知道图形渲染管线的第一步是顶点着色器(不清楚的读者可以看下和 )

那么我们此时的问题就是:

1.顶点着色器的真面目是什么样的?

2.作为客户端的C++程序如何将数据传给着色器?

记住,阅读本文的时候,一定要时刻记得每一个步骤都是在图形渲染管线中进行,一定要明确数据的从哪里来到哪里去,这也是我前面文章所强调的大局观。

着色器(Shader)

Shader官方文档的定义是:

A Shader is a user-defined program designed to run on some stage of a graphics processor. Shaders provide the code for certain programmable stages of the rendering pipeline. They can also be used in a slightly more limited form for general,on-GPU computation.

1、首先通过迅捷画图工具创建一份思维导图(新建空白思维导图/套用思维导图模板)2、将全等三角形的相关知识内容以层级的方式梳理至思维导图;3、利用图标、样式、主题、公式等编辑功能优化内容或进一步完善思维导图;。

着色器就是一个运行在图形渲染管线中某个阶段的一段开发者写的程序,通过着色器,我们可以自定义渲染的效果。由于图形渲染管线是运行在硬件中(一般是GPU),所以着色器一般就是运行在GPU中,这和大家以前写的运行在CPU的代码就必然有所不同。

C++音视频学习资料免费获取方法:关注音视频开发T哥,+即可免费获取2023年最新C++音视频开发进阶独家免费学习大礼包!

关于着色器在图形渲染管线中的阶段,官方文档描述如下:

The rendering pipeline defines certain sections to be programmable. Each of these sections,or stages,represents a particular type of programmable processing. Each stage has a set of inputs and outputs,which are passed from prior stages and on to subsequent stages (whether programmable or not).

Shaders are written in the OpenGL Shading Language. The OpenGL rendering pipeline defines the following shader stages,with their enumerator name:

Vertex Shaders: GL_VERTEX_SHADER

Tessellation Control and Evaluation Shaders: GL_TESS_CONTROL_SHADER and GL_TESS_EVALUATION_SHADER. (requires GL 4.0 or ARB_tessellation_shader)

Geometry Shaders: GL_GEOMETRY_SHADER

Fragment Shaders: GL_FRAGMENT_SHADER

Compute Shaders: GL_COMPUTE_SHADER. (requires GL 4.3 or ARB_compute_shader)

主要就2个重点:

1.上游阶段的着色器可以传递数据给下游阶段的着色器。

2.目前图形渲染管线已有的着色器有6种,分别是……(上文列出来的那几种),目前我们只需要关注Vertex Shaders(顶点着色器)和Fragment Shaders(片段着色器)即可。

如果我们控制了着色器,就控制了图形渲染管线的半壁江山,从而就一定程度控制了整个绘制的效果。于是接下来,Vertex Shaders(顶点着色器)和Fragment Shaders(片段着色器)两大主角将粉墨登场。

很多博文讲着色器Shader可能就直接将 Shader官方文档 叙述或者罗列下重点,这样子我觉得可能效果还不如直接看官方文档来的有效果,接下来,我想用自己的方式讲解这部分内容。

顶点着色器

我们再用类似轻松入门OpenGL ES——图形渲染管线的那些事想想要画一个三角形的步骤。

第一步,当然就是确定三个顶点的位置,所以我们要将三个顶点位置传给图形渲染管线,那么图形渲染管线的第一个阶段便义无反顾地承担起了这个任务,于是第一个阶段也就是处理顶点的阶段——顶点着色器。

简单来说,顶点着色器的核心功能就是完成将3维坐标中的点,通过变换和投影,转换为2维的屏幕上。

作为渲染管线的开端,顶点着色器不止承担着接收顶点的任务,作为一段拥有具体逻辑的应用程序,它还承受着处理顶点位置以便完成一些效果等任务(比如位置变换、调整形状,或者三维变换),另外还起着传输各种从客户端程序传入的各种数据(比如颜色、变换矩阵、时间参数等)并将数据传递给后面阶段的任务。

先看下它长什么模样:

//指定GLSL版本#version 300 es//输入的顶点坐标,会在客户端程序将数据传入到该字段 in vec4 aPosition;void main() {//直接把传入的坐标值作为输出传入渲染管线下一个阶段。gl_Position是OpenGL内置的变量gl_Position = aPosition;}

相当于上面程序啥都没做,只是无脑的数据传送机。那它有什么价值呢?当然有,勇敢作为排头兵,接收外头丢过来的“烫手山芋”就是它最大的贡献。 (当然你可别认为顶点着色器就这么没用,毕竟这只是一个最简单的顶点着色器程序,真正“成熟”的顶点着色器程序技能可是很爆表的。

因为在顶点着色器中,你已经拿到了对每个顶点的实质控制权,可以尽情发挥想象力去操纵每一个顶点)

GLSL

接下来就是具体讲解着色器代码的时刻了。首先要知道的是,着色器用的语言是一种特殊的语言,叫做GLSL,首先看下GLSL官方定义:

The OpenGL Shading Language is a C-style language,so it covers most of the features you would expect with such a language. Control structures (for-loops,if-else statements,etc) exist in GLSL,including the switch statement.

GLSL是一种C语言风格的语言,它包含大部分编程语言的特性。

所以它是和C语言类似的,只要你能看懂C语言,就能看懂它。只要你写过C语言程序,那基本也能写GLSL程序。

上面的实例代码为GLSL3.30所写,那么这里就以现在最主流的GLSL3.30为例,逐行级别讲解着色器。

我不太喜欢文档式地罗列语法点,所以关于着色器还是遵循边用边学原则去讲。

第一行:

OpenGL es和GLSL对应的版本关系如下图所示:

第二行:

in vec4 aPosition;

这是定义一个变量aPosition,但是和我们熟悉的C语言又有所不同。

迎面而来的是in关键字,表示这个变量是接收外面传来数值的,当然有in就会有out,表示变量是传给下一个阶段。(可能有的童鞋已经接触过GLSL2.0版本的代码了,熟悉的是arrtibute、varying等关键字,但这些在3.0已经被in、out取代)

接下来看到vec4 aPosition,vec4表示变量类型,aPosition表示变量名。这个格式大家都很熟悉,不过vec4这种类型可能有点陌生。其实就是表示4维向量。

三角形的画法 知道三边,其中一边的两个端点作为两个圆心,以另外两个边作为半径化两个圆,焦点连接起来就是三角。知道一角两遍也是同理利用圆规,不论是哪个角,但是会出现两种三角形画法,因为不知道这个角是不是已知角。

4维向量可以当做4个数的组合,GLSL特定这里指的是浮点数,用法也非常灵活:

vec4 myVec4 = vec4(1.0); // myVec4 = {1.0,1.0,1.0,1.0}vec3 myVec3 = vec3(1.0,0.0,0.5);// myVec3 = {1.0,0.0,0.5}vec3 temp = vec3(myVec3); //构造器也可以接收向量,表示 temp = myVec3vec2 myVec2 = vec2(myVec3); // myVec2 = {myVec3.x,myVec3.y} 如果构造器传入的向量维度更大,则进行截取。myVec4 = vec4(myVec2,temp,0.0); // myVec4 = {myVec2.x,myVec2.y ,temp,0.0 }

以上我相信加上注释大家都能看懂,这里就不做赘述。

C++音视频学习资料免费获取方法:关注音视频开发T哥,+即可免费获取2023年最新C++音视频开发进阶独家免费学习大礼包!

但是这样客户端C++程序和着色器内部的名字强行绑定,以我们多年的编程经验来看,并非好的做法,万一某天哪个小可爱不小心改了一下着色器的变量名,突然整个程序就挂了。

于是乎layout便应运而生,关于这个就有点意思了,layout是GLSL的几个修饰符中的一个,一般用来指定变量布局的,可以说是着色器和客户端程序或者其他阶段着色器的通信接口,详细的依旧可以看官方文档Layout Qualifier (GLSL)

layout (location = 0) in vec4 aPosition;

以下是layout的通用语法格式:

立体三角形的画法如下:工具/原料:铅笔、彩笔。步骤:1、首先画出一个三角形。2、然后在三角形的右侧画一个斜的侧三角形。3、接着在两个三角形上画三条横线。4、然后用黑色水笔勾边并涂上颜色,这样一个立体三角形就。

layout(qualifier1,qualifier2 = value,...) variable definition

其中qualifier表示具体的修饰符,最常见的就是代码中的“location”,可以理解为变量的位置。

何为变量的位置?你可以想象每一个着色器程序是一层楼,每个需要对接外部的变量住在一个个房间中,假如你现在是客户端的C++程序,你想给着色器里面某个变量传参,然后你又不知道变量名字,如果此时你知道变量所在的房间编号,那你照样还是可以将数据传给变量,就像一个蹑手蹑脚的间谍,偷偷打开对应location的房门,偷偷将一份机密文件交给里面的人(location对应变量)。所以layout加location的作用已经很明显了,你不需要房间里面住着谁(不用知道变量名称),只需要知道房间号(location),也能按成变量数值的传递。

至于客户端C++程序怎么传数据给location对应变量,先别急,后面小节还会讲。

第三行:

void main() {

这个各位太熟悉了,自从第一次写了hello world程序之后,对main函数应该烂熟于心了吧。

既然GLSL是类似C的语言,那么当然也是以main函数作为入口函数的。

main函数究竟做了什么:

gl_Position = aPosition;

直接把传入的坐标值作为输出传入渲染管线下一个阶段。gl_Position是OpenGL内置的vec4变量,表示当前顶点最终的值。

1、用直尺画一条线段;2、用圆规放在线段的一个端点,以线段长为半径画圆;3、用圆规放在线段的另外一个端点,同样以线段长为半径画圆;4、两圆有两个交点五、连接两个交点即可画成三角形;5、正三角形,又称等边三角。

所以说到底,顶点着色器最重要的目标就是将从客户端程序传入的数据经过逻辑处理之后,再赋值给gl_Position。这里gl_Position表示一个顶点的最终值。

最后有个问题:顶点着色器会执行多少次呢?

我们想下,顶点着色器是用来处理顶点的,也就是每个顶点都要经过它的处理,那么也就是传入多少顶点就执行多少次。

坐标系统

那么传入的vec4类型aPosition具体是什么样的呢?我们知道在三维空间中表达一个点,就是用一个3维的坐标x、y、z。

齐次坐标

因为本文并不是用来讲解数学的,所以这里只是简单解释下其次坐标的作用,假如2维坐标系中有一个点(x,y),如果要对该点进行缩放和旋转,我们是可以通过线性变换,即乘上一个矩阵的方式来计算:

如果要对该点进行平移,则是要能够以下方式表示:

那如果对该点即做缩放和旋转,又做平移,则要用如下方式表示:

这就不是线性变换了,那么如果我们坚持一定要用线性变换来表示呢?不用担心,数学家已经帮我们实现了,不过要再增加一个维度w就可以了:

这方面要详细了解数学推导的话,可以看GAMES101-现代计算机图形学入门-闫令琪

OpenGL中的坐标:

虽然OpenGL的坐标是一个3维空间坐标,但是本系列教程针对的是2维空间,即我们可以直接忽略z坐标,即z始终为0,而OpenGL用的是标准化设备坐标,即x、y坐标的范围会限定在-1到1以内,超出的就会被裁剪(在轻松入门OpenGL ES——图形渲染管线的那些事一文的“图元装配”一章,已经提到了裁剪这个步骤),这种坐标处理方式也叫作归一化则OpenGL的坐标会变为如下:

这种坐标下,假如有个手机屏幕是720*1080,则x方向的1个单位长度表示720个像素,而y方向的1个单位长度表示1080个像素。

为什么要这样处理呢?因为不同的屏幕分辨率相差甚多,如果要用绝对的像素点作为坐标点的话,那么针对不同的屏幕可能就需要传一套坐标点给顶点着色器,而通过标准化设备坐标的归一化,坐标点的都是用比例标记,这样显示的屏幕中也是按照比例显示,也就是OpenGL帮我们做好了屏幕适配工作。

编译、链接着色器

前面说了这么多,那么到底要怎么给图形渲染管线“喂”数据呢?在“喂”数据之前,还有一个手续需要办理一下,那就是编译、链接着色器程序。

再看下上一篇文章(轻松入门OpenGL ES——这或许是你遇过最难画的三角形(一))中的绘制三角形的代码:

extern &34;JNIEXPORT void JNICALLJava_com_example_openglstudydemo_YuvPlayer_drawTriangle(JNIEnv *env,jobject thiz, jobject surface) {/**此处开始EGL的配置**/ANativeWindow *nwin = ANativeWindow_fromSurface(env,surface); EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);if (display == EGL_NO_DISPLAY) {LOGD(&34;);return;}if (EGL_TRUE != eglInitialize(display,0,0)) {LOGD(&34;);return;} EGLConfig eglConfig;EGLint configNum;EGLint configSpec[] = {EGL_RED_SIZE,8, EGL_GREEN_SIZE,8, EGL_BLUE_SIZE,8, EGL_SURFACE_TYPE,EGL_WINDOW_BIT, EGL_NONE};if (EGL_TRUE != eglChooseConfig(display,configSpec,&eglConfig,1,&configNum)) {LOGD(&34;);return;} EGLSurface winSurface = eglCreateWindowSurface(display,eglConfig,nwin,0);if (winSurface == EGL_NO_SURFACE) {LOGD(&34;);return;}const EGLint ctxAttr[] = {EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE}; EGLContext context = eglCreateContext(display,eglConfig,EGL_NO_CONTEXT,ctxAttr);if (context == EGL_NO_CONTEXT) {LOGD(&34;);return;} if (EGL_TRUE != eglMakeCurrent(display,winSurface,winSurface,context)) {LOGD(&34;);return;}/**此处结束EGL的配置**/ /**此处开始加载着色器程序 **/GLint vsh = initShader(vertexSimpleShape,GL_VERTEX_SHADER);GLint fsh = initShader(fragSimpleShape,GL_FRAGMENT_SHADER); GLint program = glCreateProgram();if (program == 0) {LOGD(&34;);return;} glAttachShader(program,vsh);glAttachShader(program,fsh); glLinkProgram(program);GLint status = 0;glGetProgramiv(program,GL_LINK_STATUS,&status);if (status == 0) {LOGD(&34;);return;}LOGD(&34;);glUseProgram(program); /**此处加载着色器程序结束**//**此处开始将数据传入图形渲染管线**/static float triangleVer[] = {0.8f,-0.8f,0.0f, -0.8f,-0.8f,0.0f, 0.0f,0.8f,0.0f, };GLuint apos = static_cast<GLuint>(glGetAttribLocation(program,&34;));glEnableVertexAttribArray(apos);glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,0,triangleVer); /**此处结束将数据传入图形渲染管线**/ /**此处开始将图像渲染到屏幕**/glClearColor(1.0f,1.0f,1.0f,1.0f);glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLE_STRIP,0,3); eglSwapBuffers(display,winSurface); /**此处结束图像渲染**/}

可以看到,在上篇文章讲的配置EGL环境之后,就进入到加载着色器程序阶段,而此阶段最主要的操作,就是编译、链接着色器程序。着色器程序是在我们整个程序运行时编译,因为我们必须在程序运行时将着色器的逻辑安插到图形渲染管线对应的阶段里面,就像一条工厂流水线有那么2、3个工序是空的,我们在启动流水线之前,必须将空的工序一个个填补上去。

关于加载着色器程序的基本流程如下图所示:

创建着色器对象着色器对象加载着色器代码编译着色器对象创建着色器程序关联着色器对象和着色器程序链接着色器程序使用着色器程序

这里有2个概念,一个是着色器对象,表示一段具体的着色器代码的抽象,另一个是着色器程序,表示整个图形渲染管线的着色器程序集合。

接着是令大家兴奋的代码演示,代码就是根据上面的流程图一步步走的。首先将编译链接着色器程序的代码抽出来,逐行分析,首先是initShader方法:

GLint initShader(const char *source,GLint type) {//创建着色器对象,type表示着色器类型,比如顶点着色器为GL_VERTEX_SHADER,片段着色器为GL_FRAGMENT_SHADER。返回值为一个类似引用的数字。GLint sh = glCreateShader(type);if (sh == 0) {//返回值sh为0则表示创建着色器失败LOGD(&34;,type);return 0;}//着色器对象加载着色器对象代码sourceglShaderSource(sh,1,//shader数量 &source,0);//代码长度,传0则读到字符串结尾//编译着色器对象glCompileShader(sh);//以下为打印出编译异常信息GLint status;glGetShaderiv(sh,GL_COMPILE_STATUS,&status);if (status == 0) {LOGD(&34;,type);LOGD(&34;,source);auto *infoLog = new GLchar[512];GLsizei length;glGetShaderInfoLog(sh,512,&length,infoLog);LOGD(&34;,infoLog);return 0;}LOGD(&34;,type);return sh;}

1.首先用glCreateShader方法根据传入的type参数创建对应的着色器对象,type的可选范围为GL_COMPUTE_SHADER,GL_VERTEX_SHADER,GL_TESS_CONTROL_SHADER,GL_TESS_EVALUATION_SHADER,GL_GEOMETRY_SHADER,or GL_FRAGMENT_SHADER。顶点着色器为 GL_VERTEX_SHADER,片段着色器为 GL_FRAGMENT_SHADER。

2.此时的着色器对象还是空的,只是一个壳,所以我们需要通过glShaderSource方法将着色器代码注入进去。

3.代码注入进去了,就要类似C程序一样需要一个编译过程,用的是glCompileShader方法。

4.当然有编译就有可能有编译错误,由于着色器程序是爱GPU中编译运行的,所以目前并不像我们平时写在CPU运行的程序那样有详细的编译错误信息以及可以断点调试这些高端操作,但是至少还是可以通过glGetShaderiv方法看到一些报错信息的。

//分别创建和编译顶点着色器、片段着色器对象GLint vsh = initShader(vertexSimpleShape,GL_VERTEX_SHADER);GLint fsh = initShader(fragSimpleShape,GL_FRAGMENT_SHADER);//创建着色器程序对象GLint program = glCreateProgram();if (program == 0) {LOGD(&34;);return;}//将上面创建的着色器对象关联到着色器程序对象上glAttachShader(program,vsh);glAttachShader(program,fsh);//链接着色器程序glLinkProgram(program);//打印出链接异常信息GLint status = 0;glGetProgramiv(program,GL_LINK_STATUS,&status);if (status == 0) {LOGD(&34;);return;}LOGD(&34;);//使用着色器程序glUseProgram(program);

1.通过glCreateProgram方法创建一个着色器程序对象。

2.此时着色器程序对象还是空空如也,所以需要glAttachShader方法,将前面创建的着色器对象关联给它,它才是一个有实质内容的着色器程序对象。

3.此时着色器程序还是没有真正和我们的OpenGL程序关联上,所以需要通过glLinkProgram方法链接着色器程序,类似我们C语言程序链接一个动态链接库一样,去关联上。

4.当然,链接可能会发生异常,所以通过glGetProgramiv方法打印异常信息。

三角形怎么画

5.最后再用glUseProgram,名正言顺宣告,该着色器程序将被当前OpenGL程序使用。

接下来就和 一看就懂的OpenGL ES教程——再谈OpenGL工作机制 中所讲的状态机相关了,此时OpenGL es程序处于已经处于激活着色器程序的状态,后面的操作都可以针对着色器进行操作。

传递数据给着色器

前面说过顶点着色器作为图形渲染管线的开端,会接收包括顶点数据在内的多种渲染管线需要的数据,这里我们就先从只传顶点数据讲起。

OpenGL客户端程序传数据到OpenGL(服务端)内部的操作也是让无数初学者数夜辗转反侧的一个点,毕竟OpenGL是面向过程的,而且为了灵活性,就必须牺牲一些易用性,所以不好意思,肯定不像Java老司机梦想的那种传数据方式:

Point point1 = new Point(x,y,z);...boonlean isSuccess = input(point1,point2,point3);

OpenGL传输数据是这样的:

/三角形坐标static float triangleVer[] = {0.8f,-0.8f,0.0f, -0.8f,-0.8f,0.0f, 0.0f,0.8f,0.0f, };//指定接收三角形坐标的变量名,program为上面的编译链接好的着色器程序GLuint apos = static_cast<GLuint>(glGetAttribLocation(program,&34;));//真正传输数据的地方glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,0,triangleVer);glEnableVertexAttribArray(apos);

如果你是第一次看到,可能会出现以下表情:

不过不用担心,经过我一番“感性”的解释,你一定能彻底搞懂。

首先,前面已经客户端程序要传入OpenGL的数据,即使是专门传给顶点的数据,除了顶点坐标数据,还有其他数据,比如变换矩阵、颜色等,所以它不拘留于具体格式,干脆打平用一个数组传,你们要什么数据都往这个数组里面塞进去,到时候开发者根据需要自己取。比如三角形的三个顶点坐标,那就让三个点坐标的x、y、z依次排成一队:

static float triangleVer[] = {0.8f,-0.8f,0.0f, -0.8f,-0.8f,0.0f, 0.0f,0.8f,0.0f, };

1、首先通过迅捷画图工具创建一份思维导图(新建空白思维导图/套用思维导图模板)2、将全等三角形的相关知识内容以层级的方式梳理至思维导图;3、利用图标、样式、主题、公式等编辑功能优化内容或进一步完善思维导图;

这个数组再OpenGL有个专有名词,叫做“顶点属性数组”。(注意,不是“顶点数组”,中间还有“属性”二字,即该数组包含除了顶点坐标以外,还有其他顶点相关的属性)

回头看下顶点着色器:

//指定GLSL版本#version 300 es//输入的顶点坐标,会在客户端程序将数据传入到该字段 in vec4 aPosition;void main() {//直接把传入的坐标值作为输出传入渲染管线下一个阶段。gl_Position是OpenGL内置的变量gl_Position = aPosition;}

我们的目标是要把顶点坐标传到变量“aPosition”中,所以先指定接收的变量名:

GLuint apos = static_cast<GLuint>(glGetAttribLocation(program,&34;));

接下来是重点,就是告诉OpenGL如何解析传入的顶点属性数组数据:

glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,0,triangleVer);

官网对该API的解释是:

glVertexAttribPointer specify the location and data format of the array of generic vertex attributes at index index to use when rendering. size specifies the number of components per attribute and must be 1,2,3,4,or GL_BGRA. type specifies the data type of each component,and stride specifies the byte stride from one attribute to the next,allowing vertices and attributes to be packed into a single array or stored in separate arrays.

就是说glVertexAttribPointer方法指定了顶点属性数组要传到着色器哪个变量中(这里的变量指的是顶点着色器中被in修饰的变量),另外就是指定传入的顶点属性数组的格式以及着色器如何去取顶点属性数组数据。

函数定义为:

void glVertexAttribPointer(GLuint index,GLint size,GLenum type,GLboolean normalized,GLsizei stride,const void * pointer``);

任意方向找个顶点开始画。1、左边画一条线。右边画另一条线,与前一条线有点相连,形成一个倒V形,画一条横线,三角形画好了。三角形在生活中很是常见,起到支撑固定的作用。1笔、2笔和3笔都能很容易的画出来。

index:表示着色器中要接收数据的变量的引用。即着色器中的layout。

size:表示每一个顶点属性需要用多少个数组元素表示。比如一个3维坐标是xyz表示,那么size就是3,即3个数可以表示一个点坐标。

type:每一个数组元素的格式是什么,比如GL_HALF_FLOAT,GL_FLOAT,GL_DOUBLE等。

normalized:是否需要归一化,即是否需要将数据范围映射到-1到1的区间内。

stride:步长,一个重要概念,表示前一个顶点属性的起始位置到下一个顶点属性的起始位置在数组中有多少字节。如果传0,则说明顶点属性数据是紧密挨着的。

这么一说恐怕懵逼二字写在每个人脸上,下面用一张图来说明(图来源于(learnopengl-cn.github.io/01%20Gettin…)

以数组起始位置为地址0来看,第一个顶点属性VERTEX1的开端是地址0,因为数据类型为Float,每个Float大小为4个字节,而一个Vertex需要3个元素,则第二个顶点属性VERTEX2开端是地址12,那么此时stride就是12。

这种情况属于元素之间紧密挨着的,所以stride传0,OpenGL也能自己处理。但是如果顶点属性不止顶点坐标数据,还有颜色属性,如下图:

每个顶点属性Vertex由一个坐标点和一个RGB颜色数据组成,这样子就必须告诉OpenGL,从一个坐标点数据到下一个坐标点数据有多“远”的stride,这个它才知道怎么取对应的数据。

所以这行代码

glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,0,triangleVer);复制代码

可以解释为: 将triangleVer数组的每个元素用Float类型来看待,每3个元素为一个属性,每间隔12个字节(stride传0告诉OpenGL顶点属性都是紧密挨着)属性依次传给着色器中apos引用的变量。

所以,你看OpenGL挺傻的,我们需要苦口婆心地详细告诉它怎么解析数据它才知道如何解析,但是这样的好处是我们自由度高,可以非常灵活地传递数据,后面就会展示需要传递颜色数据的情况。

那么结合实例,我们传入的数组此时就应该这样看待:

{0.8f,-0.8f,0.0f, //第一个顶点-0.8f,-0.8f,0.0f, //第二个顶点0.0f,0.8f,0.0f, //第三个顶点}

然后OpenGL就会一个一个顶点坐标传入顶点着色器执行,顶点着色器执行完之后,数据就会输出给下一个阶段——图元装配(千万别以为是直接到片段着色器啊,不然前面的文章就白写了= =)

最后的:

glEnableVertexAttribArray(apos);

这就是一个开关方法,就是打开着色器中apos这个变量,想象下你还是那个间谍,你需要偷偷打开对应变量的房门,才能偷偷将一份机密文件交给里面的人。

总结

不知不觉又是写了几千字,OpenGL的东西细讲真的很多,要入门还真不容易。不过我们已经翻过了图形渲染管线以及着色器入门2座大山了,后面的路会相对稍微平坦一些了,下一篇文章一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(三)将继续讲下一个阶段——片段着色器,敬请期待~~

参考

Shader官方文档 Core Language (GLSL) 你好,三角形

在开发的路上你不是一个人,欢迎加入C++音视频开发交流群大家庭讨论交流!

上一篇 2023年03月22 16:44
下一篇 2023年06月04 15:24

相关推荐

  • HermanMelville《Moby-Dick》作品简介与读书感悟

    Gamut“申请笔记”系列的作者们,都不约而同在文章中提及阅读。大量的阅读不仅对于备考SAT大有成效,更是为出国后的语言环境、学习生活做充分的准备。来看看他们是怎么说的~@AliceGuo准备SAT的

    2022年12月14 213
  • 壁画怎么画,夸张壁画怎么画

    最后本篇中介绍的几种绘画方法,不仅是传统意义上的画画,更有一些偏向diy的有趣方法,没有任何美术功底的人都可以完成的哦~~~希望看完大家能去试试~还是按难度来划分,大家依次对号入座,先从准备材料开始:

    2023年05月10 225
  • 为什么说二胡拉断腰,什么小小胡琴拉断腰

    有句话这么说“三年琴五年萧,什么小小胡琴拉断腰,一把二胡拉断腰”。弓弦乐是所有乐器里最难学的,它的音准,音高,音色都难以控制。二胡具有歌唱性,声音宛转悠扬,深受人们的喜爱。二胡上手练习容易,想要精通难

    2023年04月20 227
  • 剪影怎么拍

    剪影摄影:基础知识剪影是传达戏剧、神秘、情感和情绪的绝佳方式。由于它们的简单性以及它们传达的故事,它们经常脱颖而出。第1步:主题几乎任何物体都可以做成剪影。但是,有些对象比其他对象更好。选择具有强烈且

    2023年01月12 236
  • 青蛙怎样画

    图片介绍:1、先画一个椭圆表示青蛙的头,然后左右画2个半圆是青蛙的耳朵和眼睛。2、椭圆里面画一条线表示青蛙的嘴巴,然后画出脚和身体。脚和身体要比画得比青蛙的嘴巴小。3、再画出青蛙的左右两条后腿。4、最

    2023年01月02 207
  • 立体字怎么画,三步画吓人伤口

    三大面·五大调物体沐浴在自然光、阳光、月光、灯光等光源里,由于光的照射角度不同,产生了明暗色调变化,便有了我们眼前所见的立体模样。在素描学习中,掌握素描调子的基本规律是非常重要的,物体调子的规律可归纳

    2023年01月17 290
  • 歌剧之王是谁,tm歌剧王

    From/罗博报告2012.9月号正刊文/玉君歌剧之王PlacidoDomingo作为知名的“世界三大男高音”之一,普拉西多·多明戈(PlacidoDomingo)自1988年首次跟随西班牙萨苏艾拉歌

    2023年05月04 248
  • BrunoMunari《Design as Art》作品简介与读书感悟

    从米兰回国后,秦曼担当起了回力设计师。她支持本土设计。并将“好好生活、活在当下”的态度,融入到作品。此外,她将设计风格带入到人生购买的第一套房。盐系、自然、注重节奏感、如呼吸般自由。所有这些美好的元素

    2022年12月14 280
  • 怎么数拍子,一首歌怎么数拍子

    舞蹈中怎么数节拍,教你几点立刻见效的绝招舞蹈中数节拍就是根据音乐的节奏来记忆,学舞蹈离不开的就是对音乐的感觉,你音感不好就是你学习舞蹈的硬伤,因为一个舞蹈的灵魂就是对音乐的卡点和音乐的把控,节拍根据音

    2023年05月11 205
  • PaulMoorhouse《Bridget Riley》作品简介与读书感悟

    有一些展览是可以改变一个人对于艺术的品位的,“画布上的抽象艺术”就是这样的一个展览。这个展览分别在英国伦敦维多利亚米罗的两处画廊同时举办,展品囊括了一个世纪以来从早期的构成主义到后数字时代的代表性抽象

    2022年12月16 241
  • 色谱图怎么看,液相色谱图数据怎么看

    使用色谱仪器检测样品时,样品流经色谱柱和检测器,所得到的信号-时间曲线称为色谱图。液相色谱图数据怎么看,色谱图根据保留时间对未知化合物进行定性,色谱图上测得的峰高或峰面积进行定量,根据各峰不同位置及峰

    2023年05月25 230
  • 迎多少笔画,迎字的100种写法

    一、姓氏笔画:二划之姓:“迎”字共有7画,笔画顺序为:撇、竖提、横折钩、竖、点、横折折撇、捺【基本释义】1.迎接:欢迎,迎宾,迎新会。2.对着;冲(chòng)着:迎面,迎风,迎上去打招呼。【组词】(

    2023年03月15 216
  • cad五角星怎么画,cad画带箭头的直线

    我们来说一下在CAD梦想画图软件中的两种合并对象的方式。操作工具方法/步骤首先选择F8正交。然后按快捷键,POL,空格。输入边数,5,空格,建立一个五边形。然后绘制直线L,通过直线依次连接。五边形的各

    2023年01月20 202
关注微信