初探GLSL
管线概述:
1>. 三维渲染管线:
简单说,实时图像学的“管线”描述的是图形数据在专门的硬件上,即显卡(GPU),
经过多步工序的运算处理,最终在屏幕上输出一张二维图像的过程。
2>. 为什么管线运算要放在GPU上执行?
管线运算也可以放在CPU上执行,称为“软渲染”,如早期的游戏引擎,和一些渲图软件。
随着图形技术的发展,这种渲染方式在实时图像学领域显得效率不足,且耗费巨大。由于CPU
天生是为通用运算而设计的,并发能力不足,而恰恰管线又有很固定的规则和时序性,且单位
时间内需要经过管线的数据量巨大,很适合并发操作。人们开始思考将图形管线运算放到一个
新的独立硬件执行,通过并发,极大提高系统吞吐量,从而达到“硬件加速”的目的。
3>. 可编程的含义:
Opengl是客户端/服务器模式: 客户端是CPU和内存,服务器是图形硬件。
客户端提交命令,服务器执行指令。
固定管线的客户端服务器模式:
服务端是封闭的,预制的,即无法对图形硬件中的管线进行新的定义和其它灵活的干预。
预定义好一个数学模型,然后通过传递参数进行微调。
“可编程”的好处:
为图形开发者提供一种机制,在客户端可以提交几段预编译的代码片段,插入到服务器端管线中
的特定位置,以实现非常灵活的控制。
如可以改变管线特定操作的数学模型,实现自定义的效果。
由此图形开发者进入了“可编程”时代。
4>. OpenGL发展史:
opengl是跨语言,跨平台的专业图形编程接口。
opengl 1.0 ~ opengl 1.5 是经典的固定管线时代
opengl 2.0 ~ opengl 2.1 是固定管线和可编程管线并存的时代
opengl 3.0 ~ opengl 4.x 开始是可编程管线崛起的时代
5>. 顶点着色器
逐定点处理: 所有顶点(顶点是坐标,颜色,法线等的集合)
它的输入与输出(以顶点为单位)
输入顶点坐标的坐标系
MVP: Model/view/Projection Matrix (三个都是矩阵)
输出顶点坐标到坐标系
输出自动归一化,变换到NDC空间下,进行裁剪
6>. 片元着色器:
逐片元(像素)处理
所有片元(一个图元在屏幕上所占的所有像素)
片元是在其对应的图元中插值得到的数据
常见操作
是根据纹理坐标得到纹理的颜色
与其他颜色进行混合
7>. OpenGL GLSL渲染环境搭建
所需要的开发环境
显卡驱动支持opengl3.2及以上版本
glut库: 很早的库
freeglut库: glut库的开源版本
glew库: opengl 扩展库为C/C++开发
//--------------------------------
初识着色语言
1>. GLSL简介:
opengl 着色语言(opengl shading language),是一种类C语言,运行在图形卡的GPU(Graphic Processor
Unit 图形处理单元)上。
与C语言拥有相同的语法,相同的词法和标识符规则,也有基本的整形,无符号整形,浮点型的定义和
运算。循环和条件分支语句也是相同的,包括switch。
在GPU硬件支持上,有大量的内建函数,可以高速运行大量的矩阵运算和向量运算。又由于为图形学设计,
对噪声,纹理,片元的操作有很好的支持。
需要注意与C语言有区别的地方:
1.共享命名空间(Shared Namespace):
Shaders操作是相互独立的,但是为了方便shaders间通信,在链接成一个shader pargram时共享变量的名字。
2.GLSL 没有char char* 和string 数据类型,也没有字符串操作。
3.不支持隐式类型转换。转换方式 使用如:int (arg)// arg 转换为int数值。
4.vec数据访问: Vec4. (r,g,b,a /x,y,z,w/ s,t,p,q)。
vec3.xy = vec2
5.新的变量类型: Attibute Uniform Varying
2>. Shader 的编译与链接
Program 对象 ----> 链接Program --->使生效 --->使用
Shader对象(顶点shader) Shader对象(片元shader)
加载shader
编译shader
编译之后 shader对象与Program对象进行绑定 attach 然后链接 然后把Program对象注册进opengl状态机
渲染一个场景时不仅仅使用一个Program对象 ,可以使用多个Program对象对不同物体进行渲染。
//创建一个Program对象 返回整形(Program 标识) 通过opengl状态机分配一个Program句柄
//这函数调用多次 返回的值 不相同
GLuint shaderProgram = glCreateProgram();
//创建一个Shader对象 返回整形(Shader标识)
GLuint shaderObj = glCreateShader(shaderType);
//加载shader
glShaderSource(shaderObj,。。。。。)
//编译shader
glCompileShader(shaderObj);
//----
//加调试信息 编译出问题 输出出错信息
GLint success;
glGetShaderiv(shaderObj, GL_COMPILE_STATUS, &success);
if(!success){
GLchar infoLog[1024];
glGetShaderInfoLog(shaderObj, 1024, NULL, infoLog);
fprintf(stderr, "Error compileing shader type %d: %s \n",shaderType,infoLog);
exit(1);
}
//----
//shaderObj 和Program对象绑定
glAttachShader(shaderProgram, shaderObj)
//链接
glLinkProgram(shaderProgram);
//----
//加调试信息 链接出问题 输出出错信息
GLint success = 0;
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success){
GLchar ErrorLog[1024];
glGetProgramInfoLog(shaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error linking shader type %s \n",ErrorLog);
exit(1);
}
//----
//生效
glValidateProgram(shaderProgram);
//----
//加调试信息 生效出问题 输出出错信息
GLint success = 0;
glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &success);
if(!success){
GLchar ErrorLog[1024];
glGetProgramInfoLog(shaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error validate shader type %s \n",ErrorLog);
exit(1);
}
//----
//Program对象 注册到opengl状态机里
glUseProgram(shaderProgram);
3>. Attibute 变量
是一种shader变量修饰语
可用来获取通过opengl API传递过来的每一个顶点。
只能修饰float vec mat 且不能声明为数组和结构体
只能被顶点着色器(Vertex shader)只读(read-only)
只能在函数体外的全局作用域下定义
有内置的变量,如:gl_Position
//------------------------------------------
//创建顶点缓存区
void createVertexBuffer()
{
Vector3f vertices[3];//存储三角形的三个顶点
vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);
//创建一个VBO
glGenBuffers(1, &VBO);
//绑定该缓冲区
glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定的类型是数组类型
//绑定后 接下来操作都会针对绑定的buff进行操作 如传递数据
//下面的函数是传递数据 vertices数据在C++内存中 传递到GPU申请的VBO缓存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
//------------------------------------------
//------------------------------------------
//渲染 对vbo中顶点数据进行渲染
glEnableVertexAttribArray(0);//启动顶点数组单元
//开始渲染
//绑定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*4, 0);//解析顶点数据
glDrawArrays(GL_TRIANGLES, 0,3);//调用绘制命令
glDisableVertexAttribArray(0);//渲染结束后 关闭顶点数组单元
//------------------------------------------
4>. Uniform 变量
是一种shader变量修饰语
在一个图元绘制中,是全局的常量(在顶点着色器和片元着色器程序中都可以使用)
所有的基本数据类型都可以使用(包括array 和 struct)
链接Program后,通过opengl api赋值,所有shader 可以访问(read-only)
//获取shader程序中 Uniform 变量 “gScale” 位置
GLuint gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
//为 shader程序中 Uniform 变量 “gScale” 赋值
static float Scale = 0.0f;
Scale += 0.001;
glUniformif(gScaleLocation, Scale); // opengl api 中给 "gScale” 赋值
Shader.vert 程序片段
//------------------------------------------
#version 330
layout(Location = 1) in vec3 Position;
uniform float gScale;
void main()
{
gl_Position = vec4(Position.x * gScale, Position.y * gScale, Position.z, 1.0);
}
//------------------------------------------
Shader.frag 程序片段
//------------------------------------------
#version 330
out vec4 FragColor;
uniform float gScale;
void main()
{
FragColor = vec4(1.0 * gScale 0.0, 0.0, 0.1);
}
//------------------------------------------
5>. varying 变量
专门在Shader之间传递数据的
//--------------------------------
OpenGL 纹理贴图
1>. 纹理贴图基础
纹理贴图就是将任意格式的图片应用到三维模型的一个或多个表面上,这样的图片通常是砖块,
围栏,地板等等,可以极大增强三维场景的真实感。
要实现纹理贴图需要做以下三件事:
1. 加载纹理
2. 提供伴随着每一个顶点的纹理坐标
3. 纹理采样,得到每一个像素的颜色
纹理坐标:
贴图附着在三角形上,三角形可能被平移,旋转和缩放,GPU要保证图片跟随顶点运动,这样才有
真实感。
通常的规范使用U, V来表示纹理坐标,相对2D笛卡尔坐标系,U对应X, V对应Y。
0,1 ------ 1,1
^ | |
| V| |
| | |
| | |
0,0 ------ 1,0
U
---->
纹理采样:
GPU在光栅化时,会对每一个三角面的纹理坐标进行插值,采样的结果是一个理数(纹理中的一个像素)
的颜色值。
opengl提供多种类型的纹理,如:1D 2D 3D cube等等。(cube 用于天空盒),重点讲解2D纹理
2D纹理有宽,高,将宽*高等于这张纹理的像素个数。
2>. 纹理贴图准备
要在opengl程序中实现纹理贴图还需要了解几个概念:
过滤(filtering)
纹理坐标被映射到[0,1]区间内,如果乘以纹理的长和宽纹理坐标变成(152.34, 745,14)怎么办?
一般的回答是将坐标降到(152, 745), 但是这样得到的结果在一些特殊情况下不是很好。
更好效果的解决方案是,通过四个纹理坐标(152, 745), (153, 745), (152, 744)和(153, 744),对他们的
颜色进行线性插值来得到最终结果。
这样得到最终像素值的方法就叫,过滤。
简单地选取整数采样的方法 ----nearest filtering
复杂一点地选取临近四个值插值的方法 ----linear filtering
opengl提供多种采样方式,可以通过API进行选择,是效率与效果的权衡。
在opengl中实现贴图,需要知道以下四个名词: 纹理对象,纹理单元,采样对象和采样Uniform变量。
“纹理对象”包含了纹理的信息(texel), 有多种类型(1D , 2D, 3D, etc),不同的尺寸和不同的格式(RGB RGBA etc)
纹理对象并不是直接绑定给shader(采样贴图实际发生的地方), 而是通过“纹理单元"一个index进行传递。Shader通过
纹理单元而得到相应的纹理对象。
可以同时访问多个纹理单元,具体数量由你的显卡决定。
如: 先激活纹理单元“0”,绑定纹理对象“A”,然后激活纹理单元“1”,绑定纹理对象“B”。
3>. 纹理贴图实现
//创建一个纹理对象 textureObj 为纹理句柄
glGenTextures(1, &textureObj);
//绑定一个纹理 textureType为纹理类型 textureObj为纹理句柄
glBindTexture(textureType, textureObj);
//传递数据给纹理对象 imageBinData为二进制数据
glTexImage2D(textureType, 0, GL_RGBA, image->width, image->hight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageBinData);
//设置采样方式
glTexParameterf(textureType, GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameterf(textureType, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//....设置纹理坐标 对应 空间坐标
//--------------
//激活纹理单元
glActiveTexture(textureUnit);
//绑定一个纹理对象
glBindTexture(textureType, textureObj);
管线概述:
1>. 三维渲染管线:
简单说,实时图像学的“管线”描述的是图形数据在专门的硬件上,即显卡(GPU),
经过多步工序的运算处理,最终在屏幕上输出一张二维图像的过程。
2>. 为什么管线运算要放在GPU上执行?
管线运算也可以放在CPU上执行,称为“软渲染”,如早期的游戏引擎,和一些渲图软件。
随着图形技术的发展,这种渲染方式在实时图像学领域显得效率不足,且耗费巨大。由于CPU
天生是为通用运算而设计的,并发能力不足,而恰恰管线又有很固定的规则和时序性,且单位
时间内需要经过管线的数据量巨大,很适合并发操作。人们开始思考将图形管线运算放到一个
新的独立硬件执行,通过并发,极大提高系统吞吐量,从而达到“硬件加速”的目的。
3>. 可编程的含义:
Opengl是客户端/服务器模式: 客户端是CPU和内存,服务器是图形硬件。
客户端提交命令,服务器执行指令。
固定管线的客户端服务器模式:
服务端是封闭的,预制的,即无法对图形硬件中的管线进行新的定义和其它灵活的干预。
预定义好一个数学模型,然后通过传递参数进行微调。
“可编程”的好处:
为图形开发者提供一种机制,在客户端可以提交几段预编译的代码片段,插入到服务器端管线中
的特定位置,以实现非常灵活的控制。
如可以改变管线特定操作的数学模型,实现自定义的效果。
由此图形开发者进入了“可编程”时代。
4>. OpenGL发展史:
opengl是跨语言,跨平台的专业图形编程接口。
opengl 1.0 ~ opengl 1.5 是经典的固定管线时代
opengl 2.0 ~ opengl 2.1 是固定管线和可编程管线并存的时代
opengl 3.0 ~ opengl 4.x 开始是可编程管线崛起的时代
5>. 顶点着色器
逐定点处理: 所有顶点(顶点是坐标,颜色,法线等的集合)
它的输入与输出(以顶点为单位)
输入顶点坐标的坐标系
MVP: Model/view/Projection Matrix (三个都是矩阵)
输出顶点坐标到坐标系
输出自动归一化,变换到NDC空间下,进行裁剪
6>. 片元着色器:
逐片元(像素)处理
所有片元(一个图元在屏幕上所占的所有像素)
片元是在其对应的图元中插值得到的数据
常见操作
是根据纹理坐标得到纹理的颜色
与其他颜色进行混合
7>. OpenGL GLSL渲染环境搭建
所需要的开发环境
显卡驱动支持opengl3.2及以上版本
glut库: 很早的库
freeglut库: glut库的开源版本
glew库: opengl 扩展库为C/C++开发
//--------------------------------
初识着色语言
1>. GLSL简介:
opengl 着色语言(opengl shading language),是一种类C语言,运行在图形卡的GPU(Graphic Processor
Unit 图形处理单元)上。
与C语言拥有相同的语法,相同的词法和标识符规则,也有基本的整形,无符号整形,浮点型的定义和
运算。循环和条件分支语句也是相同的,包括switch。
在GPU硬件支持上,有大量的内建函数,可以高速运行大量的矩阵运算和向量运算。又由于为图形学设计,
对噪声,纹理,片元的操作有很好的支持。
需要注意与C语言有区别的地方:
1.共享命名空间(Shared Namespace):
Shaders操作是相互独立的,但是为了方便shaders间通信,在链接成一个shader pargram时共享变量的名字。
2.GLSL 没有char char* 和string 数据类型,也没有字符串操作。
3.不支持隐式类型转换。转换方式 使用如:int (arg)// arg 转换为int数值。
4.vec数据访问: Vec4. (r,g,b,a /x,y,z,w/ s,t,p,q)。
vec3.xy = vec2
5.新的变量类型: Attibute Uniform Varying
2>. Shader 的编译与链接
Program 对象 ----> 链接Program --->使生效 --->使用
Shader对象(顶点shader) Shader对象(片元shader)
加载shader
编译shader
编译之后 shader对象与Program对象进行绑定 attach 然后链接 然后把Program对象注册进opengl状态机
渲染一个场景时不仅仅使用一个Program对象 ,可以使用多个Program对象对不同物体进行渲染。
//创建一个Program对象 返回整形(Program 标识) 通过opengl状态机分配一个Program句柄
//这函数调用多次 返回的值 不相同
GLuint shaderProgram = glCreateProgram();
//创建一个Shader对象 返回整形(Shader标识)
GLuint shaderObj = glCreateShader(shaderType);
//加载shader
glShaderSource(shaderObj,。。。。。)
//编译shader
glCompileShader(shaderObj);
//----
//加调试信息 编译出问题 输出出错信息
GLint success;
glGetShaderiv(shaderObj, GL_COMPILE_STATUS, &success);
if(!success){
GLchar infoLog[1024];
glGetShaderInfoLog(shaderObj, 1024, NULL, infoLog);
fprintf(stderr, "Error compileing shader type %d: %s \n",shaderType,infoLog);
exit(1);
}
//----
//shaderObj 和Program对象绑定
glAttachShader(shaderProgram, shaderObj)
//链接
glLinkProgram(shaderProgram);
//----
//加调试信息 链接出问题 输出出错信息
GLint success = 0;
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success){
GLchar ErrorLog[1024];
glGetProgramInfoLog(shaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error linking shader type %s \n",ErrorLog);
exit(1);
}
//----
//生效
glValidateProgram(shaderProgram);
//----
//加调试信息 生效出问题 输出出错信息
GLint success = 0;
glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &success);
if(!success){
GLchar ErrorLog[1024];
glGetProgramInfoLog(shaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error validate shader type %s \n",ErrorLog);
exit(1);
}
//----
//Program对象 注册到opengl状态机里
glUseProgram(shaderProgram);
3>. Attibute 变量
是一种shader变量修饰语
可用来获取通过opengl API传递过来的每一个顶点。
只能修饰float vec mat 且不能声明为数组和结构体
只能被顶点着色器(Vertex shader)只读(read-only)
只能在函数体外的全局作用域下定义
有内置的变量,如:gl_Position
//------------------------------------------
//创建顶点缓存区
void createVertexBuffer()
{
Vector3f vertices[3];//存储三角形的三个顶点
vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);
//创建一个VBO
glGenBuffers(1, &VBO);
//绑定该缓冲区
glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定的类型是数组类型
//绑定后 接下来操作都会针对绑定的buff进行操作 如传递数据
//下面的函数是传递数据 vertices数据在C++内存中 传递到GPU申请的VBO缓存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
//------------------------------------------
//------------------------------------------
//渲染 对vbo中顶点数据进行渲染
glEnableVertexAttribArray(0);//启动顶点数组单元
//开始渲染
//绑定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*4, 0);//解析顶点数据
glDrawArrays(GL_TRIANGLES, 0,3);//调用绘制命令
glDisableVertexAttribArray(0);//渲染结束后 关闭顶点数组单元
//------------------------------------------
4>. Uniform 变量
是一种shader变量修饰语
在一个图元绘制中,是全局的常量(在顶点着色器和片元着色器程序中都可以使用)
所有的基本数据类型都可以使用(包括array 和 struct)
链接Program后,通过opengl api赋值,所有shader 可以访问(read-only)
//获取shader程序中 Uniform 变量 “gScale” 位置
GLuint gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
//为 shader程序中 Uniform 变量 “gScale” 赋值
static float Scale = 0.0f;
Scale += 0.001;
glUniformif(gScaleLocation, Scale); // opengl api 中给 "gScale” 赋值
Shader.vert 程序片段
//------------------------------------------
#version 330
layout(Location = 1) in vec3 Position;
uniform float gScale;
void main()
{
gl_Position = vec4(Position.x * gScale, Position.y * gScale, Position.z, 1.0);
}
//------------------------------------------
Shader.frag 程序片段
//------------------------------------------
#version 330
out vec4 FragColor;
uniform float gScale;
void main()
{
FragColor = vec4(1.0 * gScale 0.0, 0.0, 0.1);
}
//------------------------------------------
5>. varying 变量
专门在Shader之间传递数据的
//--------------------------------
OpenGL 纹理贴图
1>. 纹理贴图基础
纹理贴图就是将任意格式的图片应用到三维模型的一个或多个表面上,这样的图片通常是砖块,
围栏,地板等等,可以极大增强三维场景的真实感。
要实现纹理贴图需要做以下三件事:
1. 加载纹理
2. 提供伴随着每一个顶点的纹理坐标
3. 纹理采样,得到每一个像素的颜色
纹理坐标:
贴图附着在三角形上,三角形可能被平移,旋转和缩放,GPU要保证图片跟随顶点运动,这样才有
真实感。
通常的规范使用U, V来表示纹理坐标,相对2D笛卡尔坐标系,U对应X, V对应Y。
0,1 ------ 1,1
^ | |
| V| |
| | |
| | |
0,0 ------ 1,0
U
---->
纹理采样:
GPU在光栅化时,会对每一个三角面的纹理坐标进行插值,采样的结果是一个理数(纹理中的一个像素)
的颜色值。
opengl提供多种类型的纹理,如:1D 2D 3D cube等等。(cube 用于天空盒),重点讲解2D纹理
2D纹理有宽,高,将宽*高等于这张纹理的像素个数。
2>. 纹理贴图准备
要在opengl程序中实现纹理贴图还需要了解几个概念:
过滤(filtering)
纹理坐标被映射到[0,1]区间内,如果乘以纹理的长和宽纹理坐标变成(152.34, 745,14)怎么办?
一般的回答是将坐标降到(152, 745), 但是这样得到的结果在一些特殊情况下不是很好。
更好效果的解决方案是,通过四个纹理坐标(152, 745), (153, 745), (152, 744)和(153, 744),对他们的
颜色进行线性插值来得到最终结果。
这样得到最终像素值的方法就叫,过滤。
简单地选取整数采样的方法 ----nearest filtering
复杂一点地选取临近四个值插值的方法 ----linear filtering
opengl提供多种采样方式,可以通过API进行选择,是效率与效果的权衡。
在opengl中实现贴图,需要知道以下四个名词: 纹理对象,纹理单元,采样对象和采样Uniform变量。
“纹理对象”包含了纹理的信息(texel), 有多种类型(1D , 2D, 3D, etc),不同的尺寸和不同的格式(RGB RGBA etc)
纹理对象并不是直接绑定给shader(采样贴图实际发生的地方), 而是通过“纹理单元"一个index进行传递。Shader通过
纹理单元而得到相应的纹理对象。
可以同时访问多个纹理单元,具体数量由你的显卡决定。
如: 先激活纹理单元“0”,绑定纹理对象“A”,然后激活纹理单元“1”,绑定纹理对象“B”。
3>. 纹理贴图实现
//创建一个纹理对象 textureObj 为纹理句柄
glGenTextures(1, &textureObj);
//绑定一个纹理 textureType为纹理类型 textureObj为纹理句柄
glBindTexture(textureType, textureObj);
//传递数据给纹理对象 imageBinData为二进制数据
glTexImage2D(textureType, 0, GL_RGBA, image->width, image->hight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageBinData);
//设置采样方式
glTexParameterf(textureType, GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameterf(textureType, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//....设置纹理坐标 对应 空间坐标
//--------------
//激活纹理单元
glActiveTexture(textureUnit);
//绑定一个纹理对象
glBindTexture(textureType, textureObj);
//....设置纹理坐标 对应 空间坐标
参考: