从这里就接触到了可编程图形渲染管线。

下面介绍使用Vertex Shader (顶点着色器)和 Fragment Shader(像素着色器)的方法。

我们的目标是使用这两个着色器给三角形填充绿色。

添加了一个cpp文件存放Shader文件,MyShaderCode.cpp:

 1 const char* vertexShaderCode =
2 " #version 430 \r\n"
3 " \r\n"
4 " in layout(location=0) vec2 position; \r\n"
5 " \r\n"
6 " \r\n"
7 " \r\n"
8 " void main() \r\n"
9 " { \r\n"
10 " gl_Position= vec4(position,0.0,1.0); \r\n"
11 " } \r\n"
12 " \r\n"
13 " \r\n";
14
15 const char* fragmentShaderCode =
16 " #version 430 \r\n"
17 " \r\n"
18 " out vec4 finalColor; \r\n"
19 " \r\n"
20 " \r\n"
21 " void main() \r\n"
22 " { \r\n"
23 " finalColor = vec4(0.0,1.0,0.0,1.0); \r\n"
24 " } \r\n"
25 " \r\n"
26 " \r\n";

书写Shader使用的是GLSL语言,和C语言语法非常相似。

Vertex Shader

#version 430 表示opengl的版本号,要使用显卡支持的版本,否则会编译出错

vertex Shader第四行的 in 关键字表示这里定义的是一个输入。layout(location=0)表示这里接收的是位置信息。vec2表示这里是一个2维向量。position是自己定义的接受数据的变量名称。

gl_Position是GLSL定义的关键字,使用在Vertex shader中就表示它将接收一个四维向量来表示顶点位置信息。

vec4(position,0.0,1.0)是GLSL的一种特殊语法,用于方便的构造一个四维向量,因为position是二维的,后面再添加两个数字就表示了四维向量。最后一位是1.0,表示没有缩放。

Fragment Shader

第18行使用out关键字,表示这里是一个输出。

而Fragment Shader在渲染管线中只有一个输出,就是每个Fragment(暂时可以理解为像素,实际上不等同于像素)的颜色。

在main中直接将所有的像素颜色都设置成绿色。

MyGlWindow.cpp

 #include <gl\glew.h>
#include "MyGlWindow.h" extern const char* vertexShaderCode;
extern const char* fragmentShaderCode; GLuint programID; void MyGlWindow::sendDataToOpenGL()
{
GLfloat verts[] =
{
+0.0f, +0.0f, //
+1.0f, +1.0f, //
-1.0f, +1.0f, //
-1.0f, -1.0f, //
+1.0f, -1.0f, //
}; GLuint vertexBufferID;
glGenBuffers(, &vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); GLushort indices[] =
{
,,,
,,,
};
GLuint indexBufferID;
glGenBuffers(, &indexBufferID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, , );
} void MyGlWindow::installShaders()
{
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vertexShaderID, , &vertexShaderCode, );
glShaderSource(fragmentShaderID, , &fragmentShaderCode, ); glCompileShader(vertexShaderID);
glCompileShader(fragmentShaderID); programID = glCreateProgram();
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID); glLinkProgram(programID); glDeleteShader(vertexShaderID);
glDeleteShader(fragmentShaderID); glUseProgram(programID);
} void MyGlWindow::initializeGL()
{
glewInit();
sendDataToOpenGL();
installShaders();
} void MyGlWindow::paintGL()
{
glViewport(, , width(), height());
glDrawElements(GL_TRIANGLES, , GL_UNSIGNED_SHORT, );
} MyGlWindow::~MyGlWindow()
{
glUseProgram();
glDeleteProgram(programID);
}

MyGlWindow.h

 #pragma once
#include <QtOpenGL\qgl.h>
class MyGlWindow :public QGLWidget
{
protected:
void sendDataToOpenGL();
void installShaders();
void initializeGL();
void paintGL(); public:
~MyGlWindow();
};

对MyGlWindow.cpp文件进行了一个重构,把上节initializeGL()中的内容除了第一句glewInit()之外全部封装进了sendDataToOpenGL()函数,因为这些代码都是向OpenGL函数发送数据的。

为了使用Shader,我们新增加了一个installShaders()函数,所有使用Shader的功能都写在其中。

在initializeGL函数中先调用sendDataToOpengGL(),然后调用installShaders()。

使用Shader的步骤清单

  1. 创建Shader并分配ID,glCreateShader
  2. 设置Shader内容, glSourceShader
  3. 编译Shader, glCompileShader
  4. 创建Program,glCreateProgram
  5. 附加Shader,glAttachShader
  6. 链接Shader, glLinkProgram
  7. 删除Shader,glDeleteShader
  8. 使用Program, glUseProgram
  9. 删除Program,glDeleteProgram

下面就按照这六个步骤逐步解析代码。

1. 创建Shader并分配ID

39 - 40 行使用glCreateShader分别创建了vertex shader和 fragment shader,并分别将ID保存在GLuint对象里(和前面使用GLuint存储Buffer对象类似)

函数的参数是固定的宏

2. 设置Shader内容

42 - 43 行使用glShaderSource函数给Shader提供具体内容。

我们额外增加了一个文件MyShaderCode用来存放Shader的代码,代码具体内容放在两个较长的字符串里。

然后在MyGlWindow.cpp的开头使用extern 声明了vertexShaderCode和fragmentShaderCode字符串变量。

glShaderSource的

  • 第一个参数表示Shader的ID
  • 第二个参数表示Shader内容的字符串有几个数组,这里只有1个
  • 第三个参数表示字符串数组本身,但是需要注意参数的类型是const char **,所以还要在前面增加一个取地址符号
  • 第四个参数使用0表示由OpenGL自己计算字符串的长度

3. 编译Shader

45 - 46 行使用glCompileShader分别对两个Shader进行编译

4.创建Program

进行后续操作之前需要创建一个Program,这里使用glCreateProgram()创建了一个Porgram,并将其ID保存在GLuint对象里。

5.附加Shader

glAttachShader有两个参数,第一个参数是Program的ID,第二个参数是Shader的ID

6.链接Shader

虽然通常称为“链接SHader”, 但其实是链接Program, 函数glLinkProgram的参数就是Program的ID

7.删除Shader

Shader使用完成后一定要删除Shader,第56和57行进行了这步操作。在useProgram之前进行也没问题,只要编译和链接了Shader就行。

8.使用Program

glUseProgram的参数是Program的ID

9. 删除Program

使用完成Program之后要删除Program,这里在析构函数里进行是比较合适的,分两步,如77和78行所示。

完成后编译,我们将得到两个绿色的三角形。

3D Computer Grapihcs Using OpenGL - 06 Vertex and Fragment Shaders的更多相关文章

  1. 3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)

    大部分OpenGL教程都会在一开始就讲解VAO,但是该教程的作者认为这是很不合理的,因为要理解它的作用需要建立在我们此前学过的知识基础上.因此直到教程已经进行了一大半,作者才引入VAO这个概念.在我看 ...

  2. 3D Computer Grapihcs Using OpenGL - 07 Passing Data from Vertex to Fragment Shader

    上节的最后我们实现了两个绿色的三角形,而绿色是直接在Fragment Shader中指定的. 这节我们将为这两个三角形进行更加自由的着色——五个顶点各自使用不同的颜色. 要实现这个目的,我们分两步进行 ...

  3. 3D Computer Grapihcs Using OpenGL - 09 Enable Depth Test

    启用Depth Test OpenGL是个3D绘图API,也就是说不只有xy坐标轴,还有第三个坐标轴z,z轴的方向是垂直于屏幕,指向屏幕内. 靠近人眼的方向是负方向,标准化设备坐标的最小值是-1, 最 ...

  4. 3D Computer Grapihcs Using OpenGL - 16 使用DrawElementsInstanced绘制立方体

    我们使用15节学到的知识来绘制14节的立方体. 在第14节我们使用了两次glDrawElements实现了OpenGL实例化,发现这样仍然不太方便,如果需要绘制成千上万的立方体,就需要手写成千上万次的 ...

  5. 3D Computer Grapihcs Using OpenGL - 11 Model View Projection Matrices

    本节我们将绘制一个3维物体,立方体. 如果要渲染3D物体,我们需要了解MVP(Model View Projection),它表示三个转换矩阵.实际上这个名字不够明确,更加确切的释义如下: Model ...

  6. 3D Computer Grapihcs Using OpenGL - 10 Color Buffer

    本节我们将尝试利用三角形制作一个“走马灯”效果. 一个三角形如图示方式,从左向右依次移动. 先看一下代码: MyGlWindow.cpp #include <gl\glew.h> #inc ...

  7. 3D Computer Grapihcs Using OpenGL - 20 结合Buffer

    在上一节的案例中,我们使用了四个Buffer Object,立方体的VertexBuffer,立方体的索引Buffer,四面体的VertexBuffer,四面体的索引Buffer. 我们这节尝试把两个 ...

  8. 3D Computer Grapihcs Using OpenGL - 15 Draw Element Instanced

    友情提示:继续本节之前,需要保存此前的代码,本节为了试验,会对代码做一些修改,但后续的修改需要我们把代码返回之前的进度. OpenGL内置支持Instancing,有专门的函数来处理这件事情. 为了方 ...

  9. 3D Computer Grapihcs Using OpenGL - 14 OpenGL Instancing

    如果我们需要绘制两个(或者多个)一样的立方体(或者物体),只是位置.缩放.旋转不一样,那么我们可以不需要多次将这个物体的顶点信息.颜色信息等发送到显卡,而是发送一次,绘制多次,仅仅是每次绘制之前应用不 ...

随机推荐

  1. linux最强编辑神器vim常用命令大全:编辑、插入、删除、替换、保存...

    我说vim是编辑器之神大家没有意见吧 下面分享一些vim常用命令,大家可以收藏一下 进入vim:  vim配置: vim中光标移动: vim中屏幕滚动: vim中插入文本类: 文本替换: 格式 : 范 ...

  2. 004 gcc 编译 C/C++ 默认使用哪个标准

    0. 前言 我挺久没碰 C,不想就这么忘了,最近重温了一些相关知识 1. C 语言的几种"方言" 简单地说,有这么几种常见的 年份 名称 1983 ANSI C 1987 C87 ...

  3. DateHandler日期处理工具(JSP中使用后台工具类)

    1.DateHandler.java package Utils.dateHandler; import java.text.ParseException; import java.text.Simp ...

  4. Eclipse连接SQL Server 2008数据库

    一.准备材料 要能够使用数据库就要有相应的JDBC,所以我们要去Microsoft官网下载 https://www.microsoft.com/zh-cn/download/details.aspx? ...

  5. 解决IDEA中自动生成返回值带final修饰的问题

    修改配置文件: Editor--Code Style--Java--Code Generation--将Make generated local variables final勾选上

  6. 【五一qbxt】day3 动态规划

    动态规划 引例: 斐波那契数列: 边界条件:f0=0: f1=1: 能够直接被求出值的状态 不需要计算其他斐波那契数列的值直接可以得到结果: 转移方程:fn=fn-1+fn-2如何用已有状态求出未知状 ...

  7. CF573E Bear and Bowling

    题目 我们设\(f_{i,j}\)表示前\(i\)个数中选\(j\)个的最大值. 那么显然有\(f_{i,j}=max(f_{i-1,j},f_{i-1,j-1}+j*a_i)\). 这个东西我们首先 ...

  8. Django使用Celery进行异步任务

    Celery Celery是一个功能完备即插即用的异步任务队列系统.它适用于异步处理问题,当发送邮件.或者文件上传, 图像处理等等一些比较耗时的操作,我们可将其异步执行,这样用户不需要等待很久,提高用 ...

  9. C++实现简单的日志记录

    C++实现简单的日志记录 //dlogger.h #ifndef DLOGGER_H #define DLOGGER_H #include <iostream> #include < ...

  10. 【问题解决方案】CentOS7替换yum的问题:使用yum makecache出现File contains no section headers

    参考链接 CentOS7替换yum的问题:使用yum时出现File contains no section headers centos安装网络repo源及错误说明 一.centos替换yum的步骤 ...