您好,登錄后才能下訂單哦!
濾鏡介紹
目前市面上的濾鏡有很多,但整體歸類也就幾樣,都是在fragment shader中進行處理。目前濾鏡最常用的就是 lut濾鏡以及調整RGB曲線的濾鏡了。其他的類型變更大同小異。
動態濾鏡的構建
為了實現動態下載的濾鏡,我們接下來實現一套濾鏡的json參數,主要包括濾鏡類型、濾鏡名稱、vertex shader、fragment shader 文件、統一變量列表、與統一變量綁定的紋理圖片、默認濾鏡強度、是否帶紋理寬高偏移量、音樂路徑、音樂是否循環播放等參數。
json 以及各個字段的介紹如下:
{ "filterList": [{ "type": "filter", // 表明濾鏡類型,目前filter是只普通濾鏡,后續還會加入其它類型的濾鏡 "name": "amaro", // 濾鏡名稱 "vertexShader": "", // vertex shader 文件名 "fragmentShader": "fragment.glsl", // fragment shader 文件名 "uniformList":["blowoutTexture", "overlayTexture", "mapTexture"], // 統一變量 "uniformData": { // 與統一變量綁定的紋理圖片 "blowoutTexture": "blowout.png", "overlayTexture": "overlay.png", "mapTexture": "map.png" }, "strength": 1.0, // 默認濾鏡強度 0.0 ~ 1.0之間 "texelOffset": 0, // 是否需要支持寬高偏移值,即需要傳遞 1.0f/width, 1.0f/height到shader中 "audioPath": "", // 音樂路徑 "audioLooping": 1 // 是否循環播放音樂 }] }
有了json 之后,我們需要解碼得到濾鏡參數對象,解碼如下:
/** * 解碼濾鏡數據 * @param folderPath * @return */ public static DynamicColor decodeFilterData(String folderPath) throws IOException, JSONException { File file = new File(folderPath, "json"); String filterJson = FileUtils.convertToString(new FileInputStream(file)); JSONObject jsonObject = new JSONObject(filterJson); DynamicColor dynamicColor = new DynamicColor(); dynamicColor.unzipPath = folderPath; if (dynamicColor.filterList == null) { dynamicColor.filterList = new ArrayList<>(); } JSONArray filterList = jsonObject.getJSONArray("filterList"); for (int filterIndex = 0; filterIndex < filterList.length(); filterIndex++) { DynamicColorData filterData = new DynamicColorData(); JSONObject jsonData = filterList.getJSONObject(filterIndex); String type = jsonData.getString("type"); // TODO 目前濾鏡只做普通的filter,其他復雜的濾鏡類型后續在做處理 if ("filter".equals(type)) { filterData.name = jsonData.getString("name"); filterData.vertexShader = jsonData.getString("vertexShader"); filterData.fragmentShader = jsonData.getString("fragmentShader"); // 獲取統一變量字段 JSONArray uniformList = jsonData.getJSONArray("uniformList"); for (int uniformIndex = 0; uniformIndex < uniformList.length(); uniformIndex++) { String uniform = uniformList.getString(uniformIndex); filterData.uniformList.add(uniform); } // 獲取統一變量字段綁定的圖片資源 JSONObject uniformData = jsonData.getJSONObject("uniformData"); if (uniformData != null) { Iterator<String> dataIterator = uniformData.keys(); while (dataIterator.hasNext()) { String key = dataIterator.next(); String value = uniformData.getString(key); filterData.uniformDataList.add(new DynamicColorData.UniformData(key, value)); } } filterData.strength = (float) jsonData.getDouble("strength"); filterData.texelOffset = (jsonData.getInt("texelOffset") == 1); filterData.audioPath = jsonData.getString("audioPath"); filterData.audioLooping = (jsonData.getInt("audioLooping") == 1); } dynamicColor.filterList.add(filterData); } return dynamicColor; }
濾鏡的實現
在解碼得到濾鏡參數之后,我們接下來實現動態濾鏡渲染過程。為了方便構建濾鏡,我們創建一個濾鏡資源加載器,代碼如下:
/** * 濾鏡資源加載器 */ public class DynamicColorLoader { private static final String TAG = "DynamicColorLoader"; // 濾鏡所在的文件夾 private String mFolderPath; // 動態濾鏡數據 private DynamicColorData mColorData; // 資源索引加載器 private ResourceDataCodec mResourceCodec; // 動態濾鏡 private final WeakReference<DynamicColorBaseFilter> mWeakFilter; // 統一變量列表 private HashMap<String, Integer> mUniformHandleList = new HashMap<>(); // 紋理列表 private int[] mTextureList; // 句柄 private int mTexelWidthOffsetHandle = OpenGLUtils.GL_NOT_INIT; private int mTexelHeightOffsetHandle = OpenGLUtils.GL_NOT_INIT; private int mStrengthHandle = OpenGLUtils.GL_NOT_INIT; private float mStrength = 1.0f; private float mTexelWidthOffset = 1.0f; private float mTexelHeightOffset = 1.0f; public DynamicColorLoader(DynamicColorBaseFilter filter, DynamicColorData colorData, String folderPath) { mWeakFilter = new WeakReference<>(filter); mFolderPath = folderPath.startsWith("file://") ? folderPath.substring("file://".length()) : folderPath; mColorData = colorData; mStrength = (colorData == null) ? 1.0f : colorData.strength; Pair pair = ResourceCodec.getResourceFile(mFolderPath); if (pair != null) { mResourceCodec = new ResourceDataCodec(mFolderPath + "/" + (String) pair.first, mFolderPath + "/" + pair.second); } if (mResourceCodec != null) { try { mResourceCodec.init(); } catch (IOException e) { Log.e(TAG, "DynamicColorLoader: ", e); mResourceCodec = null; } } if (!TextUtils.isEmpty(mColorData.audioPath)) { if (mWeakFilter.get() != null) { mWeakFilter.get().setAudioPath(Uri.parse(mFolderPath + "/" + mColorData.audioPath)); mWeakFilter.get().setLooping(mColorData.audioLooping); } } loadColorTexture(); } /** * 加載紋理 */ private void loadColorTexture() { if (mColorData.uniformDataList == null || mColorData.uniformDataList.size() <= 0) { return; } mTextureList = new int[mColorData.uniformDataList.size()]; for (int dataIndex = 0; dataIndex < mColorData.uniformDataList.size(); dataIndex++) { Bitmap bitmap = null; if (mResourceCodec != null) { bitmap = mResourceCodec.loadBitmap(mColorData.uniformDataList.get(dataIndex).value); } if (bitmap == null) { bitmap = BitmapUtils.getBitmapFromFile(mFolderPath + "/" + String.format(mColorData.uniformDataList.get(dataIndex).value)); } if (bitmap != null) { mTextureList[dataIndex] = OpenGLUtils.createTexture(bitmap); bitmap.recycle(); } else { mTextureList[dataIndex] = OpenGLUtils.GL_NOT_TEXTURE; } } } /** * 綁定統一變量句柄 * @param programHandle */ public void onBindUniformHandle(int programHandle) { if (programHandle == OpenGLUtils.GL_NOT_INIT || mColorData == null) { return; } mStrengthHandle = GLES30.glGetUniformLocation(programHandle, "strength"); if (mColorData.texelOffset) { mTexelWidthOffsetHandle = GLES30.glGetUniformLocation(programHandle, "texelWidthOffset"); mTexelHeightOffsetHandle = GLES30.glGetUniformLocation(programHandle, "texelHeightOffset"); } else { mTexelWidthOffsetHandle = OpenGLUtils.GL_NOT_INIT; mTexelHeightOffsetHandle = OpenGLUtils.GL_NOT_INIT; } for (int uniformIndex = 0; uniformIndex < mColorData.uniformList.size(); uniformIndex++) { String uniformString = mColorData.uniformList.get(uniformIndex); int handle = GLES30.glGetUniformLocation(programHandle, uniformString); mUniformHandleList.put(uniformString, handle); } } /** * 輸入紋理大小 * @param width * @param height */ public void onInputSizeChange(int width, int height) { mTexelWidthOffset = 1.0f / width; mTexelHeightOffset = 1.0f / height; } /** * 綁定濾鏡紋理,只需要綁定一次就行,不用重復綁定,減少開銷 */ public void onDrawFrameBegin() { if (mStrengthHandle != OpenGLUtils.GL_NOT_INIT) { GLES30.glUniform1f(mStrengthHandle, mStrength); } if (mTexelWidthOffsetHandle != OpenGLUtils.GL_NOT_INIT) { GLES30.glUniform1f(mTexelWidthOffsetHandle, mTexelWidthOffset); } if (mTexelHeightOffsetHandle != OpenGLUtils.GL_NOT_INIT) { GLES30.glUniform1f(mTexelHeightOffsetHandle, mTexelHeightOffset); } if (mTextureList == null || mColorData == null) { return; } // 逐個綁定紋理 for (int dataIndex = 0; dataIndex < mColorData.uniformDataList.size(); dataIndex++) { for (int uniformIndex = 0; uniformIndex < mUniformHandleList.size(); uniformIndex++) { // 如果統一變量存在,則直接綁定紋理 Integer handle = mUniformHandleList.get(mColorData.uniformDataList.get(dataIndex).uniform); if (handle != null && mTextureList[dataIndex] != OpenGLUtils.GL_NOT_TEXTURE) { OpenGLUtils.bindTexture(handle, mTextureList[dataIndex], dataIndex + 1); } } } } /** * 釋放資源 */ public void release() { if (mTextureList != null && mTextureList.length > 0) { GLES30.glDeleteTextures(mTextureList.length, mTextureList, 0); mTextureList = null; } if (mWeakFilter.get() != null) { mWeakFilter.clear(); } } /** * 設置強度 * @param strength */ public void setStrength(float strength) { mStrength = strength; } }
然后我們構建一個DynamicColorFilter的基類,方便后續添加其他類型的濾鏡,代碼如下:
public class DynamicColorBaseFilter extends GLImageAudioFilter { // 顏色濾鏡參數 protected DynamicColorData mDynamicColorData; protected DynamicColorLoader mDynamicColorLoader; public DynamicColorBaseFilter(Context context, DynamicColorData dynamicColorData, String unzipPath) { super(context, (dynamicColorData == null || TextUtils.isEmpty(dynamicColorData.vertexShader)) ? VERTEX_SHADER : getShaderString(context, unzipPath, dynamicColorData.vertexShader), (dynamicColorData == null || TextUtils.isEmpty(dynamicColorData.fragmentShader)) ? FRAGMENT_SHADER_2D : getShaderString(context, unzipPath, dynamicColorData.fragmentShader)); mDynamicColorData = dynamicColorData; mDynamicColorLoader = new DynamicColorLoader(this, mDynamicColorData, unzipPath); mDynamicColorLoader.onBindUniformHandle(mProgramHandle); } @Override public void onInputSizeChanged(int width, int height) { super.onInputSizeChanged(width, height); if (mDynamicColorLoader != null) { mDynamicColorLoader.onInputSizeChange(width, height); } } @Override public void onDrawFrameBegin() { super.onDrawFrameBegin(); if (mDynamicColorLoader != null) { mDynamicColorLoader.onDrawFrameBegin(); } } @Override public void release() { super.release(); if (mDynamicColorLoader != null) { mDynamicColorLoader.release(); } } /** * 設置強度,調節濾鏡的輕重程度 * @param strength */ public void setStrength(float strength) { if (mDynamicColorLoader != null) { mDynamicColorLoader.setStrength(strength); } } /** * 根據解壓路徑和shader名稱讀取shader的字符串內容 * @param unzipPath * @param shaderName * @return */ protected static String getShaderString(Context context, String unzipPath, String shaderName) { if (TextUtils.isEmpty(unzipPath) || TextUtils.isEmpty(shaderName)) { throw new IllegalArgumentException("shader is empty!"); } String path = unzipPath + "/" + shaderName; if (path.startsWith("assets://")) { return OpenGLUtils.getShaderFromAssets(context, path.substring("assets://".length())); } else if (path.startsWith("file://")) { return OpenGLUtils.getShaderFromFile(path.substring("file://".length())); } return OpenGLUtils.getShaderFromFile(path); } }
接下來我們構建動態濾鏡組,因為動態濾鏡有可能有多個濾鏡組合而成。代碼如下:
public class GLImageDynamicColorFilter extends GLImageGroupFilter { public GLImageDynamicColorFilter(Context context, DynamicColor dynamicColor) { super(context); // 判斷數據是否存在 if (dynamicColor == null || dynamicColor.filterList == null || TextUtils.isEmpty(dynamicColor.unzipPath)) { return; } // 添加濾鏡 for (int i = 0; i < dynamicColor.filterList.size(); i++) { mFilters.add(new DynamicColorFilter(context, dynamicColor.filterList.get(i), dynamicColor.unzipPath)); } } /** * 設置濾鏡強度 * @param strength */ public void setStrength(float strength) { for (int i = 0; i < mFilters.size(); i++) { if (mFilters.get(i) != null && mFilters.get(i) instanceof DynamicColorBaseFilter) { ((DynamicColorBaseFilter) mFilters.get(i)).setStrength(strength); } } } }
總結
基本的動態濾鏡實現起來比較簡單,總的來說就是簡單的json參數、shader、統一變量和紋理綁定需要做成動態構建的過程而已。
效果如下:
動態濾鏡效果
該效果是通過解壓asset目錄下的壓縮包資源來實現的。你只需要提供包含shader 、紋理資源、以及json的壓縮包即可更改濾鏡。
詳細實現過程,可參考本人的開源項目:
CainCamera
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。