绘制地形
地形模型一般是由NxN的网格构成,网格的点在y轴上的坐标由灰度地形图上相应的颜色决定。颜色越亮,高度越高。颜色每个通道的取值范围可以是0~ 255,通过公式转换,可以很容易的控制生成模型的高度。
生成网格顶点数据
用多个三角带来生成地形。根据单个三角带的的顶点数据的生成规则,计算每个顶点的位置,法线和UV。
计算顶点位置
计算顶点位置之前,我们先要获取到灰度地形图的像素数据。因为我们需要知道指定点的像素颜色。
- (GLubyte *)dataFromImage:(UIImage *)img {
CGImageRef imageRef = [img CGImage];
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
GLubyte *textureData = (GLubyte *)malloc(width * height * 4);
//textureData的内存布局是R,G,B,A,R,G,B,A,R,G,B,A,...不停重复。位置(x,y)的像素数据在偏移量y * 图片宽度 * 4 + x * 4处
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(textureData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
return textureData;
}
获取顶点位置
- (GLKVector3)vertexPosition:(int)col row:(int)row buffer:(unsigned char *)buffer bytesPerRow:(size_t)bytesPerRow bytesPerPixel:(size_t)bytesPerPixel {
long long offset = (int)(row / self.terrainSize.height * self.heightMap.size.height) * bytesPerRow + (int)(col / self.terrainSize.width * self.heightMap.size.width) * bytesPerPixel;
unsigned char r = buffer[offset];
GLfloat x = col;
GLfloat y = r / 255.0 * self.terrainHeight;
GLfloat z = row;
return GLKVector3Make(x, y, z);
}
计算法线
因为我想给每个顶点指定唯一的法线,所以必须计算出顶点在每个面上的法线之和。在网格上每个顶点最多被4个面共享,也就是顶点的前后左右各有一个顶点。假设这四个顶点是Va,Vb,Vc,Vd
,中间的点为Vce
,那么第一个面的法线就是(Vb - Vce) 叉乘 (Va - Vce)
,以此类推,算出四个法线,相加后归一化,就可以得到最终的法线了。因为边缘的顶点可能只被2或3个面共享,所以需要处理一下这种特殊情况。
GLKVector3CrossProduct
构建地形几何体
获取了位置和法线,就可以很方便的构建几何体了。
- -(void)buildGeometry {
CGImageRef image = self.heightMap.CGImage;
size_t bytesPerRow = CGImageGetBytesPerRow(image);
size_t bitsPerComponent = CGImageGetBitsPerComponent(image);
size_t bitesPerPixel = CGImageGetBitsPerPixel(image);
size_t bytesPerPixel = bitesPerPixel / bitsPerComponent;
UInt8 * buffer = [self dataFromImage:self.heightMap];
for (int row = 0;row < self.terrainSize.height; ++row) {
GLGeometry * terrainMeshStrip = [[GLGeometry alloc] initWithGeometryType:GLGeometryTypeTriangleStrip];
for (int col = 0;col <= self.terrainSize.width; ++col) {
GLKVector3 position1 = [self vertexPosition:col row:row buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
GLKVector3 normal1 = [self vertexNormal:position1 col:col row:row buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
GLVertex vertex1 = GLVertexMake(position1.x, position1.y, position1.z, normal1.x, normal1.y, normal1.z, col / (GLfloat)self.terrainSize.width * 2, row / (GLfloat)self.terrainSize.height * 2);
[terrainMeshStrip appendVertex:vertex1];
GLKVector3 position2 = [self vertexPosition:col row:row + 1 buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
GLKVector3 normal2 = [self vertexNormal:position2 col:col row:row + 1 buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
GLVertex vertex2 = GLVertexMake(position2.x, position2.y, position2.z, normal2.x, normal2.y, normal2.z, col / (GLfloat)self.terrainSize.width * 2, (row + 1) / (GLfloat)self.terrainSize.height * 2) ;
[terrainMeshStrip appendVertex:vertex2];
}
[self.terrainMeshStrips addObject:terrainMeshStrip];
}
free(buffer);
}
GLKTextureInfo *grass = [GLKTextureLoader textureWithCGImage:[UIImage imageNamed:@"dirt_01.jpg"].CGImage options:nil error:nil];
NSError *error;
GLKTextureInfo *dirt = [GLKTextureLoader textureWithCGImage:[UIImage imageNamed:@"dirt_01.jpg"].CGImage options:nil error:&error];
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, grass.name);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, dirt.name);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
在GLKTextureInfo
创建后,使用glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
配置它们支持重复贴图。
dirt_01.jpg
最终效果