OpenGL(十六)- 着色器统一值

前言

属性是每个顶点位置、表面法现和纹理坐标等都需要的,而统一值则用于为整个图元批次向保持不变的(统一(uniform)的)着色器传递数据。

任何着色器变量都可以指定为一个统一值,而统一值可以在3个着色器阶段中的任何一个阶段中(当前只讨论顶点着色器和片段着色器)。

声明统一值

创建一个统一值非常简单,只需在变量声明开始时放置一个uniform关键词。

cpp 复制代码
uniform float fTime;
uniform int iIndex;
uniform vec4 vColorValue;
uniform mat4 mvpMatrix;

统一值不能被标记为in或out,它们也不能在着色器阶段之间进行插值(虽然可以将它们复制到经过插值的变量中),并且它们总是只读的。

寻找统一值

在一个着色器进行编译和连接之后,我们必须在着色器中寻找统一值位置。

可以使用glGetUniformLocation函数来定位着色器代码中的统一值位置:

cpp 复制代码
GLint glGetUniformLocation(GLuint shaderID, const GLchar* varName);

这个函数返回一个有符号的整数,代表在shaderID指定的着色器中由varName命名的变量的位置。

例如,为了获取一个名为vColorValue的统一值的位置,我们应该进行如下操作:

cpp 复制代码
GLint iLocation = glGetUniformLocation(myShader, "vColorValue");

着色器变量名是区分大小写的,如果glGetUniformLocation的返回值是-1,就说明统一值的名称在着色器中不能被定位。

我们应当牢记,即使着色器编译正确,如果不是在着色器中直接使用,那么一个统一值名称仍然可能在这个着色器中"消失"。

如果声明了一个统一值而不去使用它,那么编译器将会丢弃它。

设置统一值

在通过glGetUniformLocation得到着色器中统一值的位置后,就可以针对统一值的类型设置各种数据值。

设置标量和向量统一值

一个单独的标量和向量数据类型可以在glUniform函数中使用下面的变量进行设置。

cpp 复制代码
void glUniform1f(GLint location, GLfloat v0);
void glUniform2f(GLint location, Glfloat v0, GLfloat v1);
void glUniform3f(GLint location, GLfloat v0, GLfloat v1,
GLfloat v2);
void glUniform4f(GLint location, GLfloat v0, GLfloat v1,
GLfloat v2, GLfloat v3);
void glUniform1i(GLint location, GLint v0);
void glUniform2i(GLint location, GLint v0, GLint v1);
void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2,
GLint v3);

例如,考虑在一个着色器中声明的4个变量,如下所示。

cpp 复制代码
uniform float fTime;
uniform int iIndex;
uniform vec4 vColorValue;
uniform bool bSomeFlag;

为了在着色器中寻找并设置这些值,我们的C/C++代码可能会像下面这样。

cpp 复制代码
GLint locTime, locIndex, locColor, locFlag;
locTime = glGetUniformLocation(myShader, "fTime");
locIndex = glGetUniformLocation(myShader, "iIndex");
locColor = glGetUniformLocation(myShader, "vColorValue");
locFlag = glGetUniformLocation(myShader, "bSomeFlag");
......
......
glUseProgram(myShader);
glUniform1f(locTime, 45.2f);
glUniform1i(locIndex, 42);
glUniform4f(locColor, 1.0f, 0.0f, 0.0f, 1.0f);
glUniform1i(locFlag, GL_FALSE);

注意,我们是在使用一个整数版本的glUniform来传递一个bool值。

布尔值也可以作为浮点值进行传递,0.0代表假,1.0则代表真。

设置统一数组

glUniform函数还接受一个指针,假定指向一个数值数组。

cpp 复制代码
void glUniform1fv(GLint location, GLuint count, GLfloat* v);
void glUniform2fv(GLint location, GLuint count, Glfloat* v);
void glUniform3fv(GLint location, GLuint count, GLfloat* v);
void glUniform4fv(GLint location, GLuint count, GLfloat* v);
void glUniform1iv(GLint location, GLuint count, GLint* v);
void glUniform2iv(GLint location, GLuint count, GLint* v);
void glUniform3iv(GLint location, GLuint count, GLint* v);
void glUniform4iv(GLint location, GLuint count, GLint* v);

在这里,count值代表每个含有x个分量的数组中有多少个元素,其中x是位于函数名末尾的数字。例如,如果我们有一个4分量的统一值。

cpp 复制代码
uniform vec4 vColor;

在C/C++中,我们可以将它表示为一个浮点数组。

cpp 复制代码
GLfloat vColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };

但是,这是一个包含4个值的单个数组,所以会像下面这样将它传递到着色器中。

cpp 复制代码
glUniform4fv(iColorLocation, 1, vColor);

如果我们在着色器中有一个颜色值数组uniform vec4vColors2;,那么在C/C++中,我们可以像下面这样表示和传递一个数组。

cpp 复制代码
GLfloat vColors[2][4] = {{ 1.0f, 1.0f, 1.0f, 1.0f },
{ 1.0f, 0.0f, 0.0f, 1.0f }};
......
glUniform4fv(iColorLocation, 2, vColors);

在最简单的情况下,我们可以像下面这样设置一个单个浮点统一值。

cpp 复制代码
GLfloat fValue = 45.2f;
glUniform1fv(iLocation, 1, &fValue);

设置统一矩阵

最后,我们来看一看如何设置一个矩阵统一值。

着色器矩阵数据类型只有浮点类型,这样变量就减少了很多。

下面的函数分别用来加载一个2 x 2、3 x 3和4 x 4矩阵。

cpp 复制代码
glUniformMatrix2fv(GLint location, GLuint count, GLboolean
transpose, const GLfloat *m);
glUniformMatrix3fv(GLint location, GLuint count, GLboolean
transpose, const GLfloat *m);
glUniformMatrix4fv(GLint location, GLuint count, GLboolean
transpose, const GLfloat *m);

变量count代表指针参数m中存储的矩阵数量(没错,我们可以使用矩阵数组)。

如果矩阵已经按照列优先排序(OpenGL推荐的方式)进行存储,布尔值标记transpose将被设为GL_TRUE。

将这个值设置为GL_FALSE会导致这个矩阵在复制到着色器中时发生变换。

例如,在着色器中声明统一矩阵变量:

cpp 复制代码
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;

在C/C++代码中获取变量位置并设置矩阵值:

cpp 复制代码
locMVP = glGetUniformLocation(ADSTextureShader, "mvpMatrix");
locMV = glGetUniformLocation(ADSTextureShader, "mvMatrix");
locNM = glGetUniformLocation(ADSTextureShader, "normalMatrix");
// ...

glUniformMatrix4fv(locMVP, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glUniformMatrix4fv(locMV, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
glUniformMatrix3fv(locNM, 1, GL_FALSE, transformPipeline.GetNormalMatrix());

平面着色器

现在让我们来看一个使用统一值的示例着色器。

在存储着色器中,有一个平面着色器,它的功能就是将几何图形进行变换并将其设置为一个单色。

它使用的顶点属性只有顶点位置。它还需要使用两个统一值、一个变换矩阵和一个颜值。

顶点着色器代码如下:

cpp 复制代码
#version 130

uniform mat4 mvpMatrix;

in vec4 vVertex;

void main()
{
	gl_Position = mvpMatrix * vVertex;
}

可以看到在顶点着色器代码中声明了一个统一值变量:变化矩阵,然后在主函数中应用变换。

片段着色器代码如下:

cpp 复制代码
#version 130

uniform vec4 vColorValue;

out vec4 vFragColor;

void main()
{
	vFragColor = vColorValue;
}

和之前一样没有多大变化,只是简单的颜色传递。

C/C++代码如下:

cpp 复制代码
#include "flatshader/flatshader.h"

#include <GLTools.h>
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>

#include <math.h>

#ifndef  FREEGLUT_STATIC
#define FREEGLUT_STATIC
#endif

#include <GL/glut.h>

namespace flatshader
{
	GLFrame viewFrame;
	GLFrustum viewFrustum;

	GLTriangleBatch torusBatch;

	GLMatrixStack modelViewMatrix;
	GLMatrixStack projectionMatrix;

    GLGeometryTransform transformPipeline;

	GLuint flatShader;		// 自定义的平面着色器

	GLint locMVP;			// 模型视图投影矩阵常量的位置
	GLint locColor;			// 颜色值均匀分布的位置


	void SetupRC()
	{
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

		glEnable(GL_DEPTH_TEST);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

		viewFrame.MoveForward(4.0f);

		gltMakeTorus(torusBatch, 0.80f, 0.25f, 52, 26);

		flatShader = gltLoadShaderPairWithAttributes(
			"./src/chapter_6/rewrite/flatshader/flatshader.vp"
			, "./src/chapter_6/rewrite/flatshader/flatshader.fp"
			, 1
			, GLT_ATTRIBUTE_VERTEX
			, "vVertex"
			);

		locMVP = glGetUniformLocation(flatShader, "mvpMatrix");
		locColor = glGetUniformLocation(flatShader, "vColorValue");

	}

	void ShutdownRC()
	{

	}

	void RenderScene()
	{
		static CStopWatch rotTimer;

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		modelViewMatrix.PushMatrix(viewFrame);
		{
			modelViewMatrix.Rotate(rotTimer.GetElapsedSeconds() * 10.f, 0.0f, 1.0f, 0.0f);

			GLfloat vColor[] = { 0.1f, 0.1f, 1.0f, 1.0f };

			glUseProgram(flatShader);
			glUniform4fv(locColor, 1, vColor);
			glUniformMatrix4fv(locMVP, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
		}
		torusBatch.Draw();
		modelViewMatrix.PopMatrix();

		glutSwapBuffers();
		glutPostRedisplay();

	}

	void ChangeSize(int w, int h)
	{
		if (h == 0)
            h = 1;

		glViewport(0, 0, w, h);

		viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 1000.0f);

		projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
        transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
	}

	int flatshader_test(int argc, char* argv[])
	{
		gltSetWorkingDirectory(argv[0]);

		glutInit(&argc, argv);
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
		glutInitWindowSize(800, 600);
		glutCreateWindow("Simple Transformed Geometry");
		glutReshapeFunc(ChangeSize);
		glutDisplayFunc(RenderScene);

		GLenum err = glewInit();
		if (GLEW_OK != err) {
			fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
			return 1;
		}

		SetupRC();
		glutMainLoop();
		ShutdownRC();
		return 0;
	}
}

在RenderScene中,图形颜色的统一值和变换矩阵的统一值将在花托对象上调用Draw函数之前进行设置。

最终效果如下:

相关推荐
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-35有理数均值(C++语言实现)
开发语言·数据结构·c++·算法·均值算法
wangchen_02 小时前
C++正则表达式
开发语言·c++·正则表达式
民乐团扒谱机2 小时前
【微实验】倒谱算法(Cepstrum)深度解析:原理、数学推导与代码实现
人工智能·算法·语音识别
嵌入式阿蔡2 小时前
HAL库 vs LL库 vs 寄存器:到底用哪个?
c语言·stm32·单片机·嵌入式硬件·嵌入式实时数据库
Yyyyyy~3 小时前
【C】C语言常见概念
c语言
Nil2083 小时前
leetcode 189轮转数组
数据结构·算法·leetcode
-dzk-3 小时前
【动态规划】LC 118.杨辉三角
算法·动态规划
恣逍信点3 小时前
《凌微经》静态自悖——变化之自因
人工智能·科技·算法·机器学习·业界资讯·拓扑学·哲学
吃着火锅x唱着歌3 小时前
LeetCode 3885.设计事件管理器
算法·leetcode·职场和发展
土司大王4 小时前
LeetCode hto100——字母异位词分组
java·算法·leetcode