• 学习OpenGL:笔记九


    绘制地形

    地形模型一般是由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个面共享,所以需要处理一下这种特殊情况。

    - (GLKVector3)vertexNormal:(GLKVector3)position col:(int)col row:(int)row buffer:(unsigned char *)buffer bytesPerRow:(size_t)bytesPerRow bytesPerPixel:(size_t)bytesPerPixel {
        GLKVector3 sides[4]; // 最多四条共享边
        int sideCount = 0;
        // 统计顶点有几条共享边,从而计算法线,GLKVector3CrossProduct就是用作叉乘的方法,两个向量叉乘得出的向量将垂直于它们两个,也就是法向量
        if (col >= 1) {
            //左边有共享边
            GLKVector3 leftPosition = [self vertexPosition:col - 1 row:row buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
            GLKVector3 vectorLeft = GLKVector3Subtract(leftPosition, position);
            sides[sideCount] = vectorLeft;
            sideCount++;
        }
        if (row >= 1) {
            //前面有共享边
            GLKVector3 frontPosition = [self vertexPosition:col row:row - 1 buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
            GLKVector3 vectorFront = GLKVector3Subtract(frontPosition, position);
            sides[sideCount] = vectorFront;
            sideCount++;
        }
        if (col <= self.terrainSize.width - 1) {
            //右边有共享边
            GLKVector3 rightPosition = [self vertexPosition:col + 1 row:row buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
            GLKVector3 vectorRight = GLKVector3Subtract(rightPosition, position);
            sides[sideCount] = vectorRight;
            sideCount++;
        }
        if (row <= self.terrainSize.width - 1) {
            //后面有共享边
            GLKVector3 backPosition = [self vertexPosition:col row:row + 1 buffer:buffer bytesPerRow:bytesPerRow bytesPerPixel:bytesPerPixel];
            GLKVector3 vectorBack = GLKVector3Subtract(backPosition, position);
            sides[sideCount] = vectorBack;
            sideCount++;
        }
        
        GLKVector3 normal = GLKVector3Make(0, 0, 0);
        for (int i = 0; i < sideCount; ++i) {
            GLKVector3 vec = sides[i];
            if (i == sideCount - 1 && i != 3) {
                continue;
            }
            GLKVector3 vec2 = i == sideCount - 1 ? sides[0] : sides[i + 1];
            normal = GLKVector3Add(normal, GLKVector3CrossProduct(vec2, vec));
        }
        return GLKVector3Normalize(normal);
    }
    

      

    构建地形几何体

    获取了位置和法线,就可以很方便的构建几何体了。

    - -(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_2DGL_TEXTURE_WRAP_SGL_REPEAT);配置它们支持重复贴图。

    dirt_01.jpg

    最终效果

     

     
     
  • 相关阅读:
    uni-app 轮播图
    uni-app 头部及底部导航
    Eapp 创建项目及简单应用
    nodeJS学习笔记 express获得GET和POST请求参数
    Promise 之基础详细介绍
    动态代理个人理解
    springboot实现日志记录
    调节 alert confirm prompt 的位置
    打印日志文件
    Java正则速成秘籍(三)之见招拆招篇
  • 原文地址:https://www.cnblogs.com/neverMore-face/p/10164880.html
Copyright © 2020-2023  润新知