最近因为工作需要阅读《Cocos2D-X 3.X 3D图形学渲染技术讲解》这本书所做的笔记。
GPU 语言
- OpenGL : GLSL
- DirectX : HLSL
- Nvidia :CG
GLSL
构成
- Vertex Shader : 顶点着色器
- Fragment Shader : 片段着色器
- Geometry Shader :几何着色器(比较少见)
简述
- 顶点着色器中一般都有 out 变量,对应的片段着色器中用 in ,两个变量的命名相同,类型也必须相同。
- 片段着色器变量必须是 vec4。
- uniform 是全局的,也就是独一无二的,同时它可以被着色器程序的任意着色器在任意阶段访问。顶点着色器中不需要这个uniform,所以不需要在顶点着色器中定义。可以在C++中通过代码调用OpenGL的接口进行设置。
- GPU是多线程的,所以在shader编程中最好不用if else 、for、while等条件判断或循环语句,这样不利于GPU的多线程运行。
OpenGL编程
着色器
uniform
该字段申明的变量是外部程序传给顶点(Vertex)着色器、片段(Fragment)着色器的变量。
该字段是一种从CPU应用向GPU中的着色器发送数据的方式。它是全局的,故而在每个着色器程序中必须保证其对象是唯一的。
可以在任意着色器中的任意阶段中访问,它会一致保持它的数据直到被
该变量是常量,类似C++中的const,不能被shader程序修改,只能使用。
如果声明之后没有用过,那么编辑器会默认移除这个变量。(如果只是在着色器内用,不需要任何声明修饰,直接定义使用就可以了)
通过glGetUniformLocation 函数查询uniform变量,用glUniformxx() 函数赋值。
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "vertexColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColor,0.0f,1.0f,0.0f,1.0f);
作用: 一般用于变换矩阵、材质、光照参数、颜色等。
attribute
该变量只能在顶点(Vertex)着色器中申明和使用。
作用:一般用来表示一些顶点数据,例如:顶点坐标、法线、纹理坐标、顶点颜色等等
使用函数glBindAttribLocation来绑定每一个该变量的位置,接着使用函数glVertexAttribPointer为其赋值。
varying
该变量主要用于Vertex和Fragment之间传递数据的。
一般流程是在Vertex中修改改变量,在Fragment中使用该变量。必须声明一致。
main
每个着色器的入口都是main函数,在这个函数中处理所有的输入变量,并将结果输出到输出变量中。
in && out
定义输入和输出的。 每个着色器是独立的,但也是整体的一部分。也就是,每个都有自己的输出和输入。in 和 out的作用就是用来输入和输出。
只要一个着色器的输出变量和下一个着色器的输入变量匹配,这个数据就会一致传递下去。
顶点着色器需要从顶点数据中直接输入。
片段着色器需要一个vec4 的变量作为输出。目的是因为片段着色器需要输出最终的颜色,如果不指定颜色,那么就会默认输出黑色或者白色。
因此可以看出,如果需要从一个着色器向另一个着色器发送数据,则必须在发送方着色器和接收方着色器中声明一样类型一样名字的变量。
VBO (Vertex Buffer Object) 顶点缓冲对象
可以看出来,三个顶点的在内存中的排列顺序,是XYZ+RGB进行排列的。
//位置属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6 * sizeof(GLfloat),(GLvoid*) 0);
glEnableVertexAttribArray(0);
//颜色属性
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6 * sizeof(GLfloat),(GLvoid*) (3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
函数glVertexAttribPointer 的作用是指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置。
index:第几个属性,从0开始取,0,1,2,顺序自己定义,例如顶点位置,纹理,法线。
size:一个顶点所有数据的个数,坐标(XYZ)和颜色(RGB)都占3个float,所以是3
type:顶点描述数据的类型,这里position数组中的数据全部为float,所以是GL_FLOAT
normalized:是否需要显卡帮忙把数据归一化到-1到+1区间,这里不需要,所以设置GL_FALSE
stride:一个顶点占有的总的字节数。这里是6个float (xyzrgb),所以是6 * sizeof(GLfloat)
pointer:当前指针指向的vertex内部的偏离字节数,可以唯一的标识顶点某个属性的偏移量。第一个是0,顶点位置的属性。第二个是颜色,紧接着位置之后的,所以是 3 * sizeof(GLfloat)
矩阵变换算法
矩阵平移变换算法
原理: 在原始向量的基础上加上另一个向量,从而获得一个在不同位置的新向量。
三维坐标无法完成平移变换,需要将三维的点转换成四维的点。四维的点称为齐次坐标,本质就是在三维坐标的基础上加上一位w。
假设点为P(x,y,z)
,平移向量
T ⃗ ( T x , T y , T z ) \vec{T}(T_x,T_y,T_z) T(Tx,Ty,Tz)
可以用如下公式:
∣ 1 0 0 T x 0 1 0 T y 0 0 1 T z 0 0 0 1 ∣ ⋅ { x y z 1 } = { x + T x y + T y z + T z 1 } \left| \begin{matrix} 1 & 0 & 0 & T_x \\ 0 & 1 & 0 & T_y \\ 0 & 0 & 1 & T_z \\ 0 & 0 & 0 & 1 \end{matrix} \right| \cdot \left\{ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right\} = \left\{ \begin{matrix} x + T_x \\ y + T_y \\ z + T_z \\ 1 \end{matrix} \right\} ∣ ∣100001000010TxTyTz1∣ ∣⋅⎩ ⎨ ⎧xyz1⎭ ⎬ ⎫=⎩ ⎨ ⎧x+Txy+Tyz+Tz1⎭ ⎬ ⎫
cocos2dx中相关代码
//创建矩阵的平移函数
void Mat4::createTranslation(const Vec3& translation, Mat4* dst)
{
GP_ASSERT(dst);
memcpy(dst, &IDENTITY, MATRIX_SIZE);
dst->m[12] = translation.x;
dst->m[13] = translation.y;
dst->m[14] = translation.z;
}
void Mat4::createTranslation(float xTranslation, float yTranslation, float zTranslation, Mat4* dst)
{
GP_ASSERT(dst);
memcpy(dst, &IDENTITY, MATRIX_SIZE);
dst->m[12] = xTranslation;
dst->m[13] = yTranslation;
dst->m[14] = zTranslation;
}
矩阵旋转变换算法
原理:定义一个角,和旋转轴,物体沿着给定的轴旋转特定的角度,从而实现旋转功能。
轴
-
X轴:在屏幕上的方向从左指向右。
-
Y轴:在屏幕上的方向是从下指向上。
-
Z轴:在屏幕上分为两种:
- 屏幕里指向外,通常称为右手坐标系。
- 屏幕外指向内,通常称为左手坐标系。
旋转
主要分为3种。
- 绕X轴旋转
- 绕Y轴旋转
- 绕Z轴旋转
还有其他旋转组合,比如 XZ轴、XYZ轴等等
绕X轴
公式如下:
∣ 1 0 0 0 0 cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 ∣ ⋅ { x y z 1 } = { x y ⋅ cos θ − z ⋅ sin θ y ⋅ sin θ + z ⋅ cos θ 1 } \left| \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta &0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right| \cdot \left\{ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right\} = \left \{ \begin{matrix} x \\ y \cdot \cos\theta - z \cdot \sin\theta \\ y \cdot \sin\theta + z \cdot \cos\theta \\ 1 \end{matrix} \right\} ∣ ∣10000cosθsinθ00−sinθcosθ00001∣ ∣⋅⎩ ⎨ ⎧xyz1⎭ ⎬ ⎫=⎩ ⎨ ⎧xy⋅cosθ−z⋅sinθy⋅sinθ+z⋅cosθ1⎭ ⎬ ⎫
cocos2dx中代码:
void Mat4::createRotationX(float angle, Mat4* dst)
{
GP_ASSERT(dst);
memcpy(dst, &IDENTITY, MATRIX_SIZE);
float c = std::cos(angle);
float s = std::sin(angle);
dst->m[5] = c;
dst->m[6] = s;
dst->m[9] = -s;
dst->m[10] = c;
}
绕Y轴
公式如下:
∣ cos θ 0 sin θ 0 0 1 0 0 − sin θ 0 cos θ 0 0 0 0 1 ∣ ⋅ { x y z 1 } = { x ⋅ cos θ + z ⋅ sin θ y − x ⋅ sin θ + z ⋅ cos θ 1 } \left| \begin{matrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 &0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right| \cdot \left\{ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right\} = \left \{ \begin{matrix} x \cdot \cos\theta + z \cdot \sin\theta \\ y \\ -x \cdot \sin\theta + z \cdot \cos\theta \\ 1 \end{matrix} \right\} ∣ ∣cosθ0−sinθ00100sinθ0cosθ00001∣ ∣⋅⎩ ⎨ ⎧xyz1⎭ ⎬ ⎫=⎩ ⎨ ⎧x⋅cosθ+z⋅sinθy−x⋅sinθ+z⋅cosθ1⎭ ⎬ ⎫
cocos2dx中的代码如下:
void Mat4::createRotationY(float angle, Mat4* dst)
{
GP_ASSERT(dst);
memcpy(dst, &IDENTITY, MATRIX_SIZE);
float c = std::cos(angle);
float s = std::sin(angle);
dst->m[0] = c;
dst->m[2] = -s;
dst->m[8] = s;
dst->m[10] = c;
}
绕Z轴
公式如下:
∣ cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 0 0 0 0 1 ∣ ⋅ { x y z 1 } = { x ⋅ cos θ − y ⋅ sin θ x ⋅ sin θ + y ⋅ cos θ z 1 } \left| \begin{matrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 &0 \\ 0 & 0 & 0 & 1 \end{matrix} \right| \cdot \left\{ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right\} = \left \{ \begin{matrix} x \cdot \cos\theta - y \cdot \sin\theta \\ x \cdot \sin\theta + y \cdot \cos\theta \\ z \\ 1 \end{matrix} \right\} ∣ ∣cosθsinθ00−sinθcosθ0000100001∣ ∣⋅⎩ ⎨ ⎧xyz1⎭ ⎬ ⎫=⎩ ⎨ ⎧x⋅cosθ−y⋅sinθx⋅sinθ+y⋅cosθz1⎭ ⎬ ⎫
cocos2dx中的代码如下:
void Mat4::createRotationZ(float angle, Mat4* dst)
{
GP_ASSERT(dst);
memcpy(dst, &IDENTITY, MATRIX_SIZE);
float c = std::cos(angle);
float s = std::sin(angle);
dst->m[0] = c;
dst->m[1] = s;
dst->m[4] = -s;
dst->m[5] = c;
}
绕某个向量旋转
cocos2dx中的实现方法
void Mat4::createRotation(const Quaternion& q, Mat4* dst)
{
GP_ASSERT(dst);
float x2 = q.x + q.x;
float y2 = q.y + q.y;
float z2 = q.z + q.z;
float xx2 = q.x * x2;
float yy2 = q.y * y2;
float zz2 = q.z * z2;
float xy2 = q.x * y2;
float xz2 = q.x * z2;
float yz2 = q.y * z2;
float wx2 = q.w * x2;
float wy2 = q.w * y2;
float wz2 = q.w * z2;
dst->m[0] = 1.0f - yy2 - zz2;
dst->m[1] = xy2 + wz2;
dst->m[2] = xz2 - wy2;
dst->m[3] = 0.0f;
dst->m[4] = xy2 - wz2;
dst->m[5] = 1.0f - xx2 - zz2;
dst->m[6] = yz2 + wx2;
dst->m[7] = 0.0f;
dst->m[8] = xz2 + wy2;
dst->m[9] = yz2 - wx2;
dst->m[10] = 1.0f - xx2 - yy2;
dst->m[11] = 0.0f;
dst->m[12] = 0.0f;
dst->m[13] = 0.0f;
dst->m[14] = 0.0f;
dst->m[15] = 1.0f;
}
矩阵缩放变换算法
公式如下:
∣ S 1 0 0 0 0 S 2 0 0 0 0 S 3 0 0 0 0 1 ∣ ⋅ { x y z 1 } = { S 1 ⋅ x S 2 ⋅ y S 3 ⋅ z 1 } \left| \begin{matrix} S_1 & 0 & 0 & 0 \\ 0 & S_2 & 0 & 0 \\ 0 & 0 & S_3 &0 \\ 0 & 0 & 0 & 1 \end{matrix} \right| \cdot \left\{ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right\} = \left \{ \begin{matrix} S_1 \cdot x \\ S_2 \cdot y \\ S_3 \cdot z \\ 1 \end{matrix} \right\} ∣ ∣S10000S20000S300001∣ ∣⋅⎩ ⎨ ⎧xyz1⎭ ⎬ ⎫=⎩ ⎨ ⎧S1⋅xS2⋅yS3⋅z1⎭ ⎬ ⎫
其中S1
,S2
,S3
是放大系数。
cocos2dx中的代码
void Mat4::createScale(const Vec3& scale, Mat4* dst)
{
GP_ASSERT(dst);
memcpy(dst, &IDENTITY, MATRIX_SIZE);
dst->m[0] = scale.x;
dst->m[5] = scale.y;
dst->m[10] = scale.z;
}
void Mat4::createScale(float xScale, float yScale, float zScale, Mat4* dst)
{
GP_ASSERT(dst);
memcpy(dst, &IDENTITY, MATRIX_SIZE);
dst->m[0] = xScale;
dst->m[5] = yScale;
dst->m[10] = zScale;
}