基本纹理
纹理通常来说就是一张图片,我们为每一个顶点指定纹理坐标,然后就可以在Shader中获取相应的纹理像素点颜色了。一般使用uv
来表示纹理坐标,uv
是一个二维向量(u,v)
,u和v的取值从0到1。我在代码中为每个顶点数据增加了2个GLFloat
来表示uv
的值。
- (void)drawXPlanes { static GLfloat triangleData[] = { // X轴0.5处的平面 0.5, -0.5, 0.5f, 1, 0, 0, 0, 0, 0.5, -0.5f, -0.5f, 1, 0, 0, 0, 1, 0.5, 0.5f, -0.5f, 1, 0, 0, 1, 1, 0.5, 0.5, -0.5f, 1, 0, 0, 1, 1, 0.5, 0.5f, 0.5f, 1, 0, 0, 1, 0, 0.5, -0.5f, 0.5f, 1, 0, 0, 0, 0, // X轴-0.5处的平面 -0.5, -0.5, 0.5f, -1, 0, 0, 0, 0, -0.5, -0.5f, -0.5f, -1, 0, 0, 0, 1, -0.5, 0.5f, -0.5f, -1, 0, 0, 1, 1, -0.5, 0.5, -0.5f, -1, 0, 0, 1, 1, -0.5, 0.5f, 0.5f, -1, 0, 0, 1, 0, -0.5, -0.5f, 0.5f, -1, 0, 0, 0, 0, }; [self bindAttribs:triangleData]; glDrawArrays(GL_TRIANGLES, 0, 12); }
我们来观察一下X轴0.5处的平面的顶点数据。
0.5, -0.5, 0.5, 1, 0, 0, 0, 0, 0.5, -0.5, -0.5, 1, 0, 0, 0, 1, 0.5, 0.5, -0.5, 1, 0, 0, 1, 1, 0.5, 0.5, -0.5, 1, 0, 0, 1, 1, 0.5, 0.5, 0.5, 1, 0, 0, 1, 0, 0.5, -0.5, 0.5, 1, 0, 0, 0, 0,
这是一个正方形,uv和顶点对应关系如下。 0.5, -0.5, 0.5点对应的
uv是
0, 0,
0.5, -0.5, -0.5点对应的
uv是
0, 1,
0.5, 0.5, -0.5点对应的
uv是
1, 1 , 0.5, 0.5, 0.5点对应的
uv是1
, 0。
然后增加绑定uv属性的代码。
GLuint positionAttribLocation = glGetAttribLocation(self.shaderProgram, "position"); glEnableVertexAttribArray(positionAttribLocation); GLuint colorAttribLocation = glGetAttribLocation(self.shaderProgram, "normal"); glEnableVertexAttribArray(colorAttribLocation); GLuint uvAttribLocation = glGetAttribLocation(self.shaderProgram, "uv"); glEnableVertexAttribArray(uvAttribLocation); // 为shader中的position和color赋值 // glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr) // indx: 上面Get到的Location // size: 有几个类型为type的数据,比如位置有x,y,z三个GLfloat元素,值就为3 // type: 一般就是数组里元素数据的类型 // normalized: 暂时用不上 // stride: 每一个点包含几个byte,本例中就是6个GLfloat,x,y,z,r,g,b // ptr: 数据开始的指针,位置就是从头开始,颜色则跳过3个GLFloat的大小 glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData); glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData + 3 * sizeof(GLfloat)); glVertexAttribPointer(uvAttribLocation, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (char *)triangleData + 6 * sizeof(GLfloat));
生成纹理
NSString *textureFile = [[NSBundle mainBundle] pathForResource:@"texture" ofType:@"jpg"]; NSError *error; self.diffuseTexture = [GLKTextureLoader textureWithContentsOfFile:textureFile options:nil error:&error];
绑定和使用纹理
有了纹理,接下来就要把它传递给Shader,前面我们已经把每个顶点的纹理坐标传递给了Vertex Shader。在Vertex Shader中新增了属性attribute vec2 uv;
,以及varying vec2 fragUV;
。Vertex Shader做的事情就是把uv
直接传递给Fragment Shader,让它去处理。
attribute vec4 position; attribute vec3 normal; attribute vec2 uv; uniform float elapsedTime; uniform mat4 projectionMatrix; uniform mat4 cameraMatrix; uniform mat4 modelMatrix; varying vec3 fragNormal; varying vec2 fragUV; void main(void) { mat4 mvp = projectionMatrix * cameraMatrix * modelMatrix; fragNormal = normal; fragUV = uv; gl_Position = mvp * position;
}
Fragment Shader中增加了uniform sampler2D diffuseMap;
,sampler2D
是纹理的参数类型。然后将diffuseMap
在纹理坐标fragUV
上的像素颜色作为基本色vec4 materialColor = texture2D(diffuseMap, fragUV);
。texture2D
函数用来采样纹理在某个uv
坐标下的颜色,返回值类型是vec4
。
precision highp float; varying vec3 fragNormal; varying vec2 fragUV; uniform float elapsedTime; uniform vec3 lightDirection; uniform mat4 normalMatrix; uniform sampler2D diffuseMap; void main(void) { vec3 normalizedLightDirection = normalize(-lightDirection); vec3 transformedNormal = normalize((normalMatrix * vec4(fragNormal, 1.0)).xyz); float diffuseStrength = dot(normalizedLightDirection, transformedNormal); diffuseStrength = clamp(diffuseStrength, 0.0, 1.0); vec3 diffuse = vec3(diffuseStrength); vec3 ambient = vec3(0.3); vec4 finalLightStrength = vec4(ambient + diffuse, 1.0); vec4 materialColor = texture2D(diffuseMap, fragUV); gl_FragColor = finalLightStrength * materialColor; }
绑定纹理的流程
- 激活纹理的某个通道
glActiveTexture(GL_TEXTURE0);
,OpenGL ES中最多可以激活8个通道。通道0是默认激活的,所以本例中这一句也可以不写。 - 绑定生成的纹理到
GL_TEXTURE_2D
,glBindTexture(GL_TEXTURE_2D, self.diffuseTexture.name);
,注意这里是绑定到GL_TEXTURE_2D
而不是GL_TEXTURE0
。 - 将0传递给
uniform
diffuseMap
,如果激活的是GL_TEXTURE1
就传递1,以此类推。