1 前言

纹理贴图 中介绍了将矩形图片贴到矩形模型上,本文将介绍:在不裁剪图片的情况下,将正方形的图片贴到圆形模型上。

​ 思考:实数区间 [0, 1] 与 [0, 2] 的元素可以建立一一映射关系么?答案是肯定的,如:y=2x、y=2x2、y=2*ex/e^2、y=2ln(x+1)/ln2、y=2sin(π/2*x) 等等,其中 x∈[0, 1],y∈[0, 2]。

​ 同理,在二维平面上,也可以构造一个函数,使得正方形上的点与圆形上的点一一对应。映射的构建如下图:

​ 原图如下:

​ 映射如下:

​ 读者如果对 OpenGL ES 不太熟悉,请回顾以下内容:

​ 本文完整代码资源见→【OpenGL ES】正方形图片贴到圆形上

​ 项目目录如下:

2 案例

​ MainActivity.java

package com.zhyan8.squaretocircle.activity;

import android.opengl.GLSurfaceView;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.zhyan8.squaretocircle.opengl.MyGLSurfaceView;
import com.zhyan8.squaretocircle.opengl.MyRender; public class MainActivity extends AppCompatActivity {
private GLSurfaceView mGlSurfaceView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGlSurfaceView = new MyGLSurfaceView(this);
setContentView(mGlSurfaceView);
mGlSurfaceView.setRenderer(new MyRender(this));
} @Override
protected void onResume() {
super.onResume();
mGlSurfaceView.onResume();
} @Override
protected void onPause() {
super.onPause();
mGlSurfaceView.onPause();
}
}

​ MyGLSurfaceView.java

package com.zhyan8.squaretocircle.opengl;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet; public class MyGLSurfaceView extends GLSurfaceView {
public MyGLSurfaceView(Context context) {
super(context);
setEGLContextClientVersion(3);
} public MyGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(3);
}
}

​ MyRender.java

package com.zhyan8.squaretocircle.opengl;

import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import com.zhyan8.squaretocircle.model.Model;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10; public class MyRender implements GLSurfaceView.Renderer {
private Context mContext;
private Model mModel;
private int mProgramId; public MyRender(Context context) {
mContext = context;
mModel = new Model();
} @Override
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
//设置背景颜色
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//创建程序id
mProgramId = mModel.onModelCreate(mContext.getResources());
GLES30.glUseProgram(mProgramId);
} @Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置视图窗口
GLES30.glViewport(0, 0, width, height);
mModel.onModelChange(width, height);
} @Override
public void onDrawFrame(GL10 gl) {
//将颜色缓冲区设置为预设的颜色
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//启用顶点的数组句柄
GLES30.glEnableVertexAttribArray(0);
GLES30.glEnableVertexAttribArray(1);
//绘制模型
mModel.onModelDraw();
//禁止顶点数组句柄
GLES30.glDisableVertexAttribArray(0);
GLES30.glDisableVertexAttribArray(1);
}
}

​ Model.java

package com.zhyan8.squaretocircle.model;

import android.content.res.Resources;
import android.graphics.Point;
import android.opengl.GLES30;
import com.zhyan8.squaretocircle.R;
import com.zhyan8.squaretocircle.utils.ArraysUtils;
import com.zhyan8.squaretocircle.utils.ShaderUtils;
import com.zhyan8.squaretocircle.utils.TextureUtils;
import java.nio.FloatBuffer; public class Model {
private static final int ROW_NUM = 60; // 纹理行数
private static final int COL_NUM = 60; // 纹理列数
private static final float ROW_WIDTH = 1.0f / ROW_NUM; // 每行宽度
private static final float COL_WIDTH = 1.0f / COL_NUM; // 每列宽度
private static final float SCALE = 1.0f; // 模型缩放系数
private static final int TEXTURE_DIMENSION = 2; // 纹理坐标维度
private static final int VERTEX_DIMENSION = 3; // 顶点坐标维度
private static final double EPSILON = 0.00000000001; // epsilon,比较小的浮点常量
private MyTransform mTransform = new MyTransform();
private float[][] mTextures;
private float[][] mVertices;
private FloatBuffer[] mTexturesBuffers;
private FloatBuffer[] mVerticesBuffers;
private int mTextureId;
private int mProgramId;
private int mPointNumPerRow; public Model() {
mPointNumPerRow = (COL_NUM + 1) * 2;
mTextures = new float[ROW_NUM][mPointNumPerRow * TEXTURE_DIMENSION];
mVertices = new float[ROW_NUM][mPointNumPerRow * VERTEX_DIMENSION];
mTexturesBuffers = new FloatBuffer[ROW_NUM];
mVerticesBuffers = new FloatBuffer[ROW_NUM];
} // 模型创建
public int onModelCreate(Resources resources) {
getTexture();
getVertex();
for (int i = 0; i < ROW_NUM; i++) {
mTexturesBuffers[i] = ArraysUtils.getFloatBuffer(mTextures[i]);
mVerticesBuffers[i] = ArraysUtils.getFloatBuffer(mVertices[i]);
}
mProgramId = ShaderUtils.createProgram(resources, R.raw.vertex_shader, R.raw.fragment_shader);
Point bitmapSize = new Point();
mTextureId = TextureUtils.loadTexture(resources, R.raw.zzz, bitmapSize);
mTransform.onTransformCreate(mProgramId, bitmapSize);
return mProgramId;
} // 模型参数变化
public void onModelChange(int width, int height) {
mTransform.onTransformChange(width, height);
} // 模型绘制
public void onModelDraw() {
mTransform.onTransformExecute();
for (int i = 0; i < ROW_NUM; i++) { // 一行一行绘制纹理
//准备顶点坐标和纹理坐标
GLES30.glVertexAttribPointer(0, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mVerticesBuffers[i]);
GLES30.glVertexAttribPointer(1, TEXTURE_DIMENSION, GLES30.GL_FLOAT, false, 0, mTexturesBuffers[i]);
//激活纹理
GLES30.glActiveTexture(GLES30.GL_TEXTURE);
//绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId);
//绘制贴图
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mPointNumPerRow);
// GLES30.glDrawArrays(GLES30.GL_LINE_STRIP, 0, mPointNumPerRow);
}
} // 计算纹理坐标
private void getTexture() {
for (int i = 0; i < ROW_NUM; i++) {
int index = 0;
float y1 = i * ROW_WIDTH;
float y2 = y1 + ROW_WIDTH;
float x = 0;
for (int j = 0; j <= COL_NUM; j++) {
mTextures[i][index++] = x;
mTextures[i][index++] = y1;
mTextures[i][index++] = x;
mTextures[i][index++] = y2;
x += COL_WIDTH;
}
}
} // 计算顶点坐标
private void getVertex() {
for (int i = 0; i < ROW_NUM; i++) {
int index = 0;
for (int j = 0; j < mTextures[i].length; j += TEXTURE_DIMENSION) {
float[] pos = getPosInWorldAxis(mTextures[i][j], mTextures[i][j + 1]);
textureVertexMapping(pos);
mVertices[i][index++] = pos[0] * SCALE;
mVertices[i][index++] = pos[1] * SCALE;
mVertices[i][index++] = pos[2] * SCALE;
}
}
} // 纹理坐标映射到顶点坐标
private void textureVertexMapping(float[] pos) {
float x = Math.abs(pos[0]);
float y = Math.abs(pos[1]);
double norm = Math.sqrt(x * x + y * y);
if (norm < EPSILON) {
return;
}
double normX = pos[0] / norm; // 归一化坐标
double normY = pos[1] / norm; // 归一化坐标
double rate = x > y ? y / x : x / y; // 短边与长边的比率
double dist = Math.sqrt(1 + rate * rate); // 圆心到正方形边上的距离
double factor = norm / dist; // 相对位置缩放因子
// double adjustNorm = adjustDist(norm);
// double adjustDist = adjustDist(dist);
// double factor = adjustNorm / adjustDist; // 相对位置缩放因子
pos[0] = (float) (normX * fun(factor));
pos[1] = (float) (normY * fun(factor));
} // 调整距离
private double adjustDist(double dist) {
if (dist < 1) return dist;
double rest = dist - 1;
double more = rest / 2;
return 1 + more;
} // 因子变换函数
private double fun(double x) {
return x;
// return Math.sqrt(x);
// return Math.pow(x, 0.3);
// return Math.log(x + 1) / Math.log(2);
// return Math.log10(x + 1) / Math.log10(2);
// return Math.sin(Math.PI / 2 * x);
} //纹理坐标转换为世界坐标
private float[] getPosInWorldAxis(float x, float y) {
float [] pos = new float[VERTEX_DIMENSION];
pos[0] = x * 2 - 1;
pos[1] = 1 - y * 2;
pos[2] = 0f;
return pos;
}
}

​ MyTransform.java

package com.zhyan8.squaretocircle.model;

import android.graphics.Point;
import android.opengl.GLES30;
import android.opengl.Matrix; public class MyTransform {
private int mProgramId;
private Point mBitmapSize;
private Point mViewSize;
private int mMvpMatrixHandle;
private float[] mMvpMatrix = new float[16]; public void onTransformCreate(int programId, Point bitmapSize) {
mProgramId = programId;
mBitmapSize = bitmapSize;
mMvpMatrixHandle = GLES30.glGetUniformLocation(mProgramId, "mvpMatrix");
} public void onTransformChange(int width, int height) {
mViewSize = new Point(width, height);
} //调整图片宽高适配屏幕尺寸
public void onTransformExecute() {
int mapWidth = mBitmapSize.x;
int mapHeight = mBitmapSize.y;
float mapRatio = mapWidth / (float) mapHeight;
float ratio = mViewSize.x / (float) mViewSize.y;
float w = 1.0f;
float h = 1.0f;
if (mapRatio > ratio) { //宽度最大,高度适配
h = mapRatio / ratio;
} else { //高度最大,宽度适配
w = ratio / mapRatio;
}
Matrix.orthoM(mMvpMatrix, 0, -w, w, -h, h,-1, 1);
GLES30.glUniformMatrix4fv(mMvpMatrixHandle, 1, false, mMvpMatrix, 0);
}
}

​ ShaderUtils.java

package com.zhyan8.squaretocircle.utils;

import android.content.res.Resources;
import android.opengl.GLES30;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader; public class ShaderUtils {
//创建程序id
public static int createProgram(Resources resources, int vertexShaderResId, int fragmentShaderResId) {
final int vertexShaderId = compileShader(resources, GLES30.GL_VERTEX_SHADER, vertexShaderResId);
final int fragmentShaderId = compileShader(resources, GLES30.GL_FRAGMENT_SHADER, fragmentShaderResId);
return linkProgram(vertexShaderId, fragmentShaderId);
} //通过外部资源编译着色器
private static int compileShader(Resources resources, int type, int shaderId){
String shaderCode = readShaderFromResource(resources, shaderId);
return compileShader(type, shaderCode);
} //通过代码片段编译着色器
private static int compileShader(int type, String shaderCode){
int shader = GLES30.glCreateShader(type);
GLES30.glShaderSource(shader, shaderCode);
GLES30.glCompileShader(shader);
return shader;
} //链接到着色器
private static int linkProgram(int vertexShaderId, int fragmentShaderId) {
final int programId = GLES30.glCreateProgram();
//将顶点着色器加入到程序
GLES30.glAttachShader(programId, vertexShaderId);
//将片元着色器加入到程序
GLES30.glAttachShader(programId, fragmentShaderId);
//链接着色器程序
GLES30.glLinkProgram(programId);
return programId;
} //从shader文件读出字符串
private static String readShaderFromResource(Resources resources, int shaderId) {
InputStream is = resources.openRawResource(shaderId);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
StringBuilder sb = new StringBuilder();
try {
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
}

​ TextureUtils.java

package com.zhyan8.squaretocircle.utils;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.opengl.GLES30;
import android.opengl.GLUtils; public class TextureUtils {
//加载纹理贴图
public static int loadTexture(Resources resources, int resourceId, Point bitmapSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId, options);
bitmapSize.set(bitmap.getWidth(), bitmap.getHeight());
final int[] textureIds = new int[1];
// 生成纹理id
GLES30.glGenTextures(1, textureIds, 0);
// 绑定纹理到OpenGL
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR_MIPMAP_LINEAR);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
// 加载bitmap到纹理中
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0);
// 生成MIP贴图
GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);
// 取消绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
return textureIds[0];
}
}

​ ArraysUtils.java

package com.zhyan8.squaretocircle.utils;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer; public class ArraysUtils {
public static FloatBuffer getFloatBuffer(float[] floatArr) {
FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
fb.put(floatArr);
fb.position(0);
return fb;
}
}

​ vertex_shader.glsl

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec2 aTextureCoord;
uniform mat4 mvpMatrix;
out vec2 vTexCoord;
void main() {
gl_Position = mvpMatrix * vPosition;
vTexCoord = aTextureCoord;
}

​ 顶点着色器的作用:进行矩阵变换位置、根据光照公式计算顶点颜⾊、⽣成 / 变换纹理坐标,并且把位置和纹理坐标发送到片元着色器。

​ 顶点着色器中,如果没有指定默认精度,则 int 和 float 的默认精度都为 highp。

​ fragment_shader.glsl

#version 300 es
precision mediump float;
uniform sampler2D uTextureUnit;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {
fragColor = texture(uTextureUnit,vTexCoord);
}

​ 片元着色器的作用:处理经光栅化阶段生成的每个片元,计算每个像素的颜色和透明度。

​ 在片元着色器中,浮点值没有默认的精度值,每个着色器必须声明一个默认的 float 精度。

3 运行效果

1)因子变换函数:y=x

2)因子变换函数:y=sqrt(x)

3)因子变换函数:y=log10(1+x)/log10(2)

4)因子变换函数:y=sin(πx/2)

5)因子变换函数:y=x,对距离进行调整

​ 鉴于以上图片头发都鼓起一个大包,对距离进行调整,如下:

double adjustNorm = adjustDist(norm);
double adjustDist = adjustDist(dist);
double factor = adjustNorm / adjustDist; // 相对位置缩放因子 // 调整距离
private double adjustDist(double dist) {
if (dist < 1) return dist;
double rest = dist - 1;
double more = rest / 2;
return 1 + more;
}

​ 效果如下:

​ 虽然头发也有个小包,但比前面4种方案小了很多。

​ 声明:本文转自【OpenGL ES】正方形图片贴到圆形上

【OpenGL ES】正方形图片贴到圆形上的更多相关文章

  1. OpenGL ES: (2) OpenGL ES 与 EGL、GLSL的关系

    OpenGL ES 是负责 GPU 工作的,目的是通过 GPU 计算,得到一张图片,这张图片在内存中其实就是一块 buffer,存储有每个点的颜色信息等.而这张图片最终是要显示到屏幕上,所以还需要具体 ...

  2. OpenGL ES: (5) OpenGL的基本概念、OpenGL ES 在屏幕产生图片的过程、OpenGL管线(pipeline)

    一. OpenGL的基本概念 OpenGL 的结构可以从逻辑上划分为下面 3 个部分: 图元(Primitives) 缓冲区(Buffers) 光栅化(Rasterize) 图元(Primitives ...

  3. Android OpenGL ES 开发(九): OpenGL ES 纹理贴图

    一.概念 一般说来,纹理是表示物体表面的一幅或几幅二维图形,也称纹理贴图(texture).当把纹理按照特定的方式映射到物体表面上的时候,能使物体看上去更加真实.当前流行的图形系统中,纹理绘制已经成为 ...

  4. 一篇通俗易懂的讲解OpenGL ES的文章

    电脑或者手机上做图像处理有很多方式,但是目前为止最高效的方法是有效地使用图形处理单元,或者叫 GPU.你的手机包含两个不同的处理单元,CPU 和 GPU.CPU 是个多面手,并且不得不处理所有的事情, ...

  5. OpenGL ES学习笔记(三)——纹理

    首先申明下,本文为笔者学习<OpenGL ES应用开发实践指南(Android卷)>的笔记,涉及的代码均出自原书,如有需要,请到原书指定源码地址下载. <OpenGL ES学习笔记( ...

  6. OpenGL ES 3.0 图元装配

    1. 前言 之前已经把纹理的渲染给弄出来了,但是又遇到一个新的问题,那就是图元装配,比如说我已经把图片给显示出来了,但是呢,并没有做到让它显示到具体的位置,而跟这个位置相关的则需要靠图元装配. 图元装 ...

  7. Opengl ES 1.x NDK实例开发之六:纹理贴图

    开发框架介绍请參见:Opengl ES NDK实例开发之中的一个:搭建开发框架 本章在第三章(Opengl ES 1.x NDK实例开发之三:多边形的旋转)的基础上演示怎样使用纹理贴图,分别实现了三角 ...

  8. Android OpenGL ES 画球体

    近期由于兴趣所向.開始学习OpenGL绘图. 本文以"画球体"为点,小结一下近期所学. > 初识OpenGL ES 接触OpenGL是从Android開始的.众所周知,And ...

  9. Android OpenGL ES(七)----理解纹理与纹理过滤

    1.理解纹理 OpenGL中的纹理能够用来表示图像.照片,甚至由一个数学算法生成的分形数据.每一个二维的纹理都由很多小的纹理元素组成.它们是小块的数据,类似于我们前面讨论过的片段和像素.要使用纹理,最 ...

  10. Opengl ES之纹理贴图

    纹理可以理解为一个二维数组,它可以存储大量的数据,这些数据可以发送到着色器上.一般情况下我们所说的纹理是表示一副2D图,此时纹理存储的数据就是这个图的像素数据. 所谓的纹理贴图,就是使用Opengl将 ...

随机推荐

  1. Laravel : 模糊查询 where orWhere

    Banner::where('title', 'like', "%{$keyword}%")->orWhere('introduce', 'like', "%{$k ...

  2. Redis在Liunx系统下使用

    Redis使用 前言 如何在Linux服务器上部署Redis,版本号如下: Redis版本 5.0.4 服务器版本 Linux CentOS 7.6 64位 下载Redis 进入官网找到下载地址 ht ...

  3. Linux-网络-子网-子网掩码-网关-DNS解析

  4. [转帖]CPU写入512bit要多久:从AVX到NEON

    https://zhuanlan.zhihu.com/p/677124882 写这篇文章的原因是有个项目需要降低延迟,希望能更快地把512bit的数据从内存搬进PCIe设备.原先的做法是软件写寄存器通 ...

  5. [转帖]LSM树详解

    https://zhuanlan.zhihu.com/p/181498475 LSM树(Log-Structured-Merge-Tree)的名字往往会给初识者一个错误的印象,事实上,LSM树并不像B ...

  6. 【技术剖析】7. 看看毕昇 JDK 团队是如何解决 JVM 中 CMS 的 Crash

    [技术剖析]7. 看看毕昇 JDK 团队是如何解决 JVM 中 CMS 的 Crashhttps://bbs.huaweicloud.com/forum/thread-168485-1-1.html ...

  7. add_argument()方法基本参数使用

    selenium做web自动化时我们想要通过get打开一个页面之前就设置好一些基本参数,需要 通过add_argument()方法来设置,下面以一个简单的不展示窗口为例. option = webdr ...

  8. 京东云开发者|提高IT运维效率,深度解读京东云AIOps落地实践

    基于深度学习对运维时序指标进行异常检测,快速发现线上业务问题 时间序列的异常检测是实际应用中的一个关键问题,尤其是在 IT 行业.我们没有采用传统的基于阈值的方法来实现异常检测,而是通过深度学习提出了 ...

  9. Dubbo架构设计与源码解析(一) 架构设计

    作者:黄金 一.架构演变 单应用架构 ----> 垂直架构 ----> 分布式架构 ----> 微服务架构 ----> 云原生架构 二.Dubbo总体架构 1.角色职能 • C ...

  10. 手写一个Promise完成resolve 和 reject状态的改变和修改属性

    1.手写 Promise 1 创建一个文件 Promise.js:内容 function Promise(){ } 2 引入 Promise.js 这个文件 <script src=" ...