图形图像顶点和片段像素的渲染,只能用固定管线或可编程着色器任何一种进行处理。无论是顶点还是片段都是只能处理固定管线中整个顶点或片段中的可以丰富自定义的部分,其它顶点或像素处理还是用硬件固定管线进行。


一、顶点着色器


顶点着色器,根据应用程序的设计,只是选择处理:


1.视觉空间变换(模型,法线,纹理).


2.主颜色和辅助颜色的计算生成(光照在摄像机坐标系中进行实时光照计算).


3.纹理坐标计算。


4.雾坐标设置和处理。


5.点大小。



新的顶点着色器或者支持更多的处理。并不是顶点管线的所有操作都可以用顶点着色器替代,下面的一些操作仍然是由固定管线处理:


1)透视除法。硬件实现。


2)视口映射变换。硬件实现。


3)图元装配,在4D裁剪空间中,进行视锥裁剪之前进行。


4)平截头体(视景体)和用户裁剪。


5)背面剔除,视口变换后,光栅化之前进行。


6)双面光照选择。


7)多边形模式处理。(可能是指凹凸模式,着色模式,三角带三角网格模式).


8)多边形偏移,在图元装配阶段进行偏移。


9)深度范围截取,glDepthRange设置后,固定管线内的实现代码进行设置。



顶点着色器,引入的更多实时光照计算,可控制的图形顶点变换效果(从CPU端移动到GPU端),纹理变换,雾,点大小设置可以有效的控制GPU Shader来实现;且其它的OpenGL状态设置,例如背面剔除,多次渲染Pass状态等更加方便的统一到Shader中进行设置。


二、片断着色器(像素着色器)


片段着色器之前,还进行了光栅化插值操作,也是硬件实现的。


片段着色器可以处理的操作是:


1.提取纹理单元,用于纹理贴图。


2.纹理应用。


3.雾.


4.主颜色和辅助颜色汇合。


不论是否使用片断着色器,OGL固定管线都要执行下面的操作:


1)单调或平滑着色(控制片断之间的插值,还需要一层内部过渡处理的)。


2)像素覆盖计算。根据圆形像素点大小,覆盖到的方形像素格子的大小。


像素位置是不是当前OGL context实例所有,被其它OGL实例窗口遮挡了则不是。


4)裁剪操作。像素级别还是要进行裁剪的,用户设置的scissor裁剪。


5)点画模式应用(实线虚线,OGL 3.0后已经废除了,OGL 3.1删除了)。


6)scissor test,裁剪测试。


7)alpha测试(OGL 3.1后删除了,并用片段着色器替代)。


8)模板测试。


9)深度测试,是基于像素上才能应用深度测试,视口变换后写入glDepthRange是顶点级别还不能确定)


10)alpha混合,blend操作,当前drawcall得到的颜色和原来在后台颜色缓存区中的颜色混合,混合后会在临时缓存会再写入后台颜色缓存,默认写入时GL_COPY。


11)对像素进行逻辑操作,glLogicOP默认是GL_COPY.


12)颜色值的抖动,混合周边的颜色,使得颜色更加丰富些,一般现代硬件都不需要,OGLES上还是需要的 默认开启。


13)颜色掩码操作glColorMask, 模板和深度缓存也是有Mask的。



片段着色器通过代码控制纹理提取和复杂的纹理动画,多重纹理映射,颜色组合,雾设置,颜色组合(纹理着色后和光照辅助颜色汇合),模板操作,alpha测试等很多都可以在片段着色器中处理了,和各种状态的统一设置。片段着色器承载的更多图像效果的实现。


三、GLSL程序逻辑


Shader 源码可以共用 Shader Object对象实现的功能,故需要链接。


Shader Program链接后是动态的可执行指令集,执行中可以更新如果有效那么会马上看到效果(shader object detach 后再编译后attach),删除也不是马上的到不再执行才删除。


1.Shader Object对象


//(1) 创建对象句柄,eShaderType是GL_VERTEX_SHADER或GL_FRAGMENT_SHADER 

 

  GLuint shader = glCreateShader(eShaderType); 

 

  //(2) 绑定shader字符串脚本源码, count是shader代码段个数,可以连接多个代码段 

 

  glShaderSource(shader, 1, &strFileData, NULL); 

 

  //(3) 编译shader,得到静态的着色器程序,应该是GPU汇编指令,绑定到shader标示符地址 

 

  glCompileShader(shader); 

 

  // (4) 检查编译状态 

 

  GLint status; 

 

  glGetShaderiv(shader, GL_COMPILE_STATUS, &status); 

 

  // 获取log长度 

 

  GLint infoLogLength; 

 

  glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); 

 

  // 获取出错信息 

 

  GLchar *strInfoLog = new GLchar[infoLogLength + 1]; 

 

  glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);


2.Shader Program程序


//(1) 创建program 

 

  GLuint programId = glCreateProgram(); 

 

  //(2) 绑定shader对象,一个着色器程序可以绑定多个着色器对象句柄 

 

  for (std::vector<GLuint>::size_type iLoop = 0; iLoop < shaderList.size(); iLoop++) 

 

  glAttachShader(programId, shaderList[iLoop]); 

 

  //(3) 链接shader,得到可运行的机器码 

 

  glLinkProgram(programId); 

 

  //(4) 检查program状态,如果有错误打印出错信息 

 

  GLint status; 

 

  glGetProgramiv(programId, GL_LINK_STATUS, &status); 

 

  GLint infoLogLength; 

 

  glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength); 

 

  GLchar *strInfoLog = new GLchar[infoLogLength + 1]; 

 

  glGetProgramInfoLog(programId, infoLogLength, NULL, strInfoLog);


使用着色器


// 使用着色器程序 
 
glBindVertexArray(vaoId);// glBindVertexArray和glUseProgram先后顺序,在glDrawXXX之前调用就行,不分先后的 
 
glUseProgram(programId); 
 
glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量发送到顶点着色器

drawcall调用驱动一次渲染管线流程:


glDrawArrays(GL_TRIANGLES, 0, 3);


四、Shader数据访问类型,以及app和Shader数据的交互


GLSL 1.3版本以前,也就是OpenglES2.0下顶点着色器的输入变量用attribute关键字来限定,attribute不能作为片段着色器的输入,app中用 glBindAttribLocation()得到变量字段,glVertexAttribPointer()为每个attribute变量赋值 。片段着色器的输入用varying关键字限定,顶点着色器修改varying变量的值,片段着色器使用varying字段的值。



GLSL 1.4版本中attribute和varying字段都删除了,都统一使用in out或inout关键字。


用:
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);指定输入。


用glGetUniformLocation(programId, "offset");获取unitform变量。


glUniform2f(offsetLocationId, fXOffset, fYOffset);设置app中设置的值。


Shader全局变量用uniform关键字修饰,uniform只能在app中修改,vertex和fragment shader只可以使用,GLSL1.3和1.4都一样, 如果uniform变量在vertex和fragment两者之间声明方式完全一样,则它可以在vertex和fragment共享使用,uniform变量一般用来表示:变换矩阵,材质各种光照颜色,颜色等信息 。顶点着色器的输入用:


// 输入参数对应顶点输入的类型下标,默认下标从0开始
 
glEnableVertexAttribArray(0);
 
// 说明数组类型下标,顶点属性成员数量,成员类型,法向量规范化,成员之间的跨度,数据块中该属性字节首地址
 
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer);
 
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
 

  例如: 

 

  //创建vertex array object对象 

 

  glGenVertexArrays(1, &vaoId); 

 

  glBindVertexArray(vaoId); 

 

  //创建vertex buffer object对象 

 

  glGenBuffers(1, &vboId); 

 

  glBindBuffer(GL_ARRAY_BUFFER, vboId); 

 

  glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW); 

 

  // 指定VAO如何去解析VBO数据块中的数据。 

 

  // 在Shader创建之前或创建之后指定都是可以的,因为glDrawXXX时候才真正去处理Opengl状态机中的设置。 

 

  //启用顶点位置属性索引,要在display中指定的(vao包装了切换vao即可),因为需要开启顶点属性索引才能绘制,特别是绘制物体多的时候,需要切换才能正确绘制。 

 

  // 也可以封装在VAO中,只负责启用glEnableVertexAttribArray不关闭即可。 

 

  glEnableVertexAttribArray(0); // 激活顶点属性数组 

 

  glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //指定Position顶点属性数据格式,大小会根据glDrawArrays截断。 

 

  //启用顶点颜色属性索引 

 

  glEnableVertexAttribArray(1); 

 

  glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);// 指定Color顶点属性数据格式,大小会根据glDrawArrays截断。 

 
 
 
 

  glBindBuffer(GL_ARRAY_BUFFER, 0); 

 

  // 结束一个vao的包装。 

 

  glBindVertexArray(NULL);


五、实例代码



Shader.h:



#ifndef _SHADER_H_
#define _SHADER_H_
#include <vector>
#include <string>
#include <cstring>
#include <GL/glew.h>
class Shader {
public:
	static GLuint createShader(GLenum eShaderType, const std::string &strShaderFile);
	static GLuint createShader(GLenum eShaderType, const char* fileName);
	static GLuint createProgram(const std::vector<GLuint> &shaderList);
};

#endif



Shader.cpp



#include <fstream>
#include <sstream>
#include "Shader.h"
//从字符串流构造着色器对象
GLuint Shader::createShader(GLenum eShaderType, const std::string &strShaderFile)
{
	GLuint shader = glCreateShader(eShaderType);//根据类型创建shader,顶点或者片段着色器
	const char * strFileData = strShaderFile.c_str();
	glShaderSource(shader, 1, &strFileData, NULL);//绑定shader字符串, count是shader代码段个数,可以连接多个代码段
	glCompileShader(shader);//编译shader,得到静态的汇编指令,绑定到shader标示符地址
	//检查shader状态
	GLint status;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (status == GL_FALSE)
	{
		GLint infoLogLength;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
		GLchar *strInfoLog = new GLchar[infoLogLength + 1];
		glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);
		const char * strShaderType = NULL;
		switch (eShaderType)
		{
		case GL_VERTEX_SHADER: strShaderType = "vertex"; break;
		case GL_GEOMETRY_SHADER: strShaderType = "geometry"; break;
		case GL_FRAGMENT_SHADER: strShaderType = "fragment"; break;
		}
		fprintf(stderr, "Compile failure in %s shader:\n%s\n", strShaderType, strInfoLog);
		delete[] strInfoLog;
	}
	return shader;
}
//从文件构造着色器对象
GLuint Shader::createShader(GLenum eShaderType, const char* fileName)
{
	std::ifstream infile(fileName);
	if (!infile)
	{
		fprintf(stderr, "Could not open file : %s for reading.", fileName);
		return 0;
	}
	std::stringstream  buffer;
	buffer << infile.rdbuf();
	infile.close();
	return Shader::createShader(eShaderType, buffer.str());
}
//构造着色器程序对象
GLuint Shader::createProgram(const std::vector<GLuint> &shaderList)
{
	GLuint programId = glCreateProgram();//创建program
	for (std::vector<GLuint>::size_type iLoop = 0; iLoop < shaderList.size(); iLoop++)
		glAttachShader(programId, shaderList[iLoop]);//绑定shader,一个着色器程序可以绑定多个着色器程序

	glLinkProgram(programId);//链接shader,得到可运行的机器码
	//检查program状态,如果有错误打印出错信息
	GLint status;
	glGetProgramiv(programId, GL_LINK_STATUS, &status);
	if (status == GL_FALSE)
	{
		GLint infoLogLength;
		glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength);

		GLchar *strInfoLog = new GLchar[infoLogLength + 1];
		glGetProgramInfoLog(programId, infoLogLength, NULL, strInfoLog);
		fprintf(stderr, "Linker failure: %s\n", strInfoLog);
		delete[] strInfoLog;
	}

	/*for (size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
		glDetachShader(programId, shaderList[iLoop]);*/
	return programId;
}



VAO_VBO_Shader.cpp:



//依赖库glew32.lib freeglut.lib
//使用着色器颜色插值绘制三角形
#include <string>
#include <vector>
#include <GL/glew.h>
#include <GL/freeglut.h>
#pragma  comment(lib, "glew32d.lib")
#include "shader.h"
using namespace std;

void userInit();
void reshape(int w, int h);
void display(void);
void keyboardAction(unsigned char key, int x, int y);


GLuint vboId;//vertex buffer object句柄
GLuint vaoId;//vertext array object句柄
GLuint programId;//shader program 句柄
GLuint offsetLocationId;

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(512, 512);
	glutCreateWindow("Triangle demo");

	glewInit();
	userInit();
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboardAction);
	glutMainLoop();
	return 0;
}
//自定义初始化函数
void userInit()
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glEnable(GL_DITHER);
	glDisable(GL_DITHER);
	//顶点位置和颜色数据
	const GLfloat vertexData[] = {
		-0.5f, 0.0f, 0.0f, 1.0f,
		0.5f, 0.0f, 0.0f, 1.0f,
		0.0f, 0.5f, 0.0f, 1.0f,
		1.0f, 0.0f, 0.0f, 1.0f,
		0.0f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.0f, 1.0f, 1.0f
	};
	//创建vertex array object对象
	glGenVertexArrays(1, &vaoId);
	glBindVertexArray(vaoId);
	//创建vertex buffer object对象
	glGenBuffers(1, &vboId);
	glBindBuffer(GL_ARRAY_BUFFER, vboId);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
	// 指定VAO如何去解析VBO数据块中的数据。
	// 在Shader创建之前或创建之后指定都是可以的,因为glDrawXXX时候才真正去处理Opengl状态机中的设置。
	//启用顶点位置属性索引,要在display中指定的(vao包装了切换vao即可),因为需要开启顶点属性索引才能绘制,特别是绘制物体多的时候,需要切换才能正确绘制。
	// 也可以封装在VAO中,只负责启用glEnableVertexAttribArray不关闭即可。
	glEnableVertexAttribArray(0); // 激活顶点属性数组
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //指定Position顶点属性数据格式,大小会根据glDrawArrays截断。
	//启用顶点颜色属性索引
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);// 指定Color顶点属性数据格式,大小会根据glDrawArrays截断。

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	// 结束一个vao的包装。
	glBindVertexArray(NULL);

	// 这里不能关闭,否则Shader取不到数据
	glDisableVertexAttribArray(0); // 去激活VAO的顶点属性
	glDisableVertexAttribArray(1);

	//从文件创建着色器
	/*std::vector<GLuint> idVector;
	string strPre = "E:\\OpenGL\\OpenGl7thEdition-master\\OpenGl7thEdition-master\\OpenGL_MyProject\\hello\\data";
	idVector.push_back(Shader::createShader(GL_VERTEX_SHADER, strPre + "\\vertex.glsl"));
	idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER, strPre + "\\fragment.glsl"));
	programId = Shader::createProgram(idVector);*/
	//从文件创建着色器
	std::vector<GLuint> idVector;
	const std::string vertexStr(
		"#version 330\n"
		"in vec4 pos;\n"
		"in vec4 incolor;\n"
		"uniform vec2 offset;\n"
		"smooth out vec4 thecolor;\n"
		"void main()\n"
		"{\n"
		"vec4 totalOffset = vec4(offset.x, offset.y, 0.0, 0.0);\n"
		"gl_Position = pos + totalOffset;\n"
		"thecolor = incolor;}\n"
		);
	const std::string fragmentStr(
		"#version 330\n"
		"smooth in vec4 thecolor;\n"
		"out vec4 outputColor;\n"
		"void main()\n"
		"{outputColor = thecolor;}\n"
		);

	idVector.push_back(Shader::createShader(GL_VERTEX_SHADER, vertexStr));// "data\\vertex.glsl"));
	idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER, fragmentStr));// "data\\fragment.glsl"));
	programId = Shader::createProgram(idVector);
	offsetLocationId = glGetUniformLocation(programId, "offset");
	//int nStereoSupport = 0;
	//glGetIntegerv(GL_STEREO, &nStereoSupport); // Win7 OGL 3.1不支持
	//int nDoubleFrameBufferSupport = 0;
	//glGetIntegerv(GL_DOUBLEBUFFER, &nDoubleFrameBufferSupport);// Win7 OGL 3.1支持
	//int nAluColorBuffer = 0;
	//glGetIntegerv(GL_AUX_BUFFERS, &nAluColorBuffer);// Win7 OGL 3.1不支持,只有0个颜色辅助缓存
}
//调整窗口大小回调函数
void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}

//根据时间计算偏移量
void ComputePositionOffsets(GLfloat &fXOffset, GLfloat &fYOffset)
{
	const GLfloat fLoopDuration = 5.0f;
	const GLfloat fScale = 3.14159f * 2.0f / fLoopDuration;

	GLfloat fElapsedTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;

	GLfloat fCurrTimeThroughLoop = fmodf(fElapsedTime, fLoopDuration);

	fXOffset = cosf(fCurrTimeThroughLoop * fScale) * 0.5f;
	fYOffset = sinf(fCurrTimeThroughLoop * fScale) * 0.5f;
}

绘制回调函数
//void display(void)
//{
//	glClear(GL_COLOR_BUFFER_BIT);
//	// 绑定到VAO状态,也就是封装了通过VAO 的glEnableVertexAttribArray,glVertexAttribPointer关联起来可以解释的VBO数据作为输入
//	// 这样通过VAO的切换,就可以在轻松的切换VBO数据源,且正确的解释VBO数据源作为Shader的输入,能够方便的进行绘制切换。
//	glBindVertexArray(vaoId); 
//	glUseProgram(programId);// 启用GPU中的Shader机器码程序
//	GLfloat fXOffset = 0.0f, fYOffset = 0.0f;
//	ComputePositionOffsets(fXOffset, fYOffset);
//	glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量发送到顶点着色器
//
//	//绘制三角形,用glDrawElemenets不能正确绘制,因为这里需要连续的
//	glDrawArrays(GL_TRIANGLES, 0, 3);
//	glUseProgram(0);
//	// 关闭GL_ARRAY_BUFFER,glDisableVertexAttribArray,也是可以正确绘制的,
//	// 说明glBindVertexArray(vaoId)是正确封装了需要关联了启用状态和索引关系的集合,直接glBindVertexArray切换绘制即可。
//	//glBindBuffer(GL_ARRAY_BUFFER, 0); // 去激活GPU中的该VBO
//	//glDisableVertexAttribArray(0); // 去激活VAO的顶点属性
//	//glDisableVertexAttribArray(1);
//	glutSwapBuffers();
//}

//绘制回调函数
void display(void)
{

	glClear(GL_COLOR_BUFFER_BIT);
	//计算偏移量
	GLfloat fXOffset = 0.0f, fYOffset = 0.0f;
	ComputePositionOffsets(fXOffset, fYOffset);

	// 使用着色器程序
	glBindVertexArray(vaoId);// glBindVertexArray和glUseProgram先后顺序,在glDrawXXX之前调用就行,不分先后的
	glUseProgram(programId);
	glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量发送到顶点着色器
	
	//glBindBuffer(GL_ARRAY_BUFFER, vboId);
	// Shader从app到GPU中的输入信息,可以在vbo,vao中指定,也可以在display时候修改数据后再指定。
	//启用顶点位置属性索引,一组属性代表了顶点,颜色,法向量,uv等,下标是从0开始
	//glEnableVertexAttribArray(0);
	//为顶点着色器位置信息赋值,positionSlot表示顶点着色器位置属性(即,Position);
	// 4表示每一个顶点信息由几个值组成,这个值必须位1,2,3或4;
	// GL_FLOAT表示顶点信息的数据类型;GL_FALSE表示不要将数据类型标准化(即fixed-point);
	// stride表示数组中每个元素的长度;pCoords表示数组的首地址
	//glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
	//绘制三角形,真正根据设置执行着色器程序
	glDrawArrays(GL_TRIANGLES, 0, 3);

	//glDisableVertexAttribArray(NULL);
	//glBindBuffer(GL_ARRAY_BUFFER, NULL);
	// 终止着色器程序执行绑定
	glUseProgram(NULL);
	
	glutSwapBuffers();

	glutPostRedisplay();//不断刷新
}

//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 033:  // Escape key
		exit(EXIT_SUCCESS);
		break;
	}
}