【Android】RuntimeShader 应用

1 简介

RuntimeShader 是 Android 13(T)中新增的特性,用于逐像素渲染界面,它使用 AGSL(Android Graphics Shading Language)编写着色器代码,底层基于 Skia 图形渲染引擎。官方介绍详见 → RuntimeShader

相较于 OpenGL ES,RuntimeShader 具有以下特点。

  • RuntimeShader 中只有片元着色器,没有顶点着色器。
  • RuntimeShader 中用户不用输入顶点数据,简化了输入操作。
  • RuntimeShader 基于 AGSL 语言,OpenGL ES 基于 GLSL 语言。
  • AGSL 中纹理坐标值域与 View 的宽高对应,GLSL 中纹理坐标一般归一化了。

2 对 View 进行二次渲染

MainActivity.java

java 复制代码
package com.zhyan8.shaderdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.RenderEffect;
import android.graphics.RuntimeShader;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;

import com.zhyan8.shaderdemo.utils.ScreenUtils;
import com.zhyan8.shaderdemo.utils.StringUtils;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout parentView = findViewById(R.id.parent);
        float[] resolution = ScreenUtils.getScreenSizeF(this);
        applyRuntimeShader(parentView, resolution);
    }

    private void applyRuntimeShader(View view, float[] resolution) {
        String shaderCode = StringUtils.loadString(this, "shaders/dazzling.agsl");
        RuntimeShader shader = new RuntimeShader(shaderCode);
        shader.setFloatUniform("u_resolution", resolution);
        RenderEffect effect = RenderEffect.createRuntimeShaderEffect(shader, "u_texture");
        view.setRenderEffect(effect);
    }
}

activity_main.xml

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:gravity="center"
    android:background="#FFFFFF"
    android:id="@+id/parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World"
        android:textSize="60sp"
        android:textColor="#669933"/>

</LinearLayout>

dazzling.agsl

cpp 复制代码
uniform shader u_texture;
uniform vec2 u_resolution;

vec4 main(vec2 coords) {
    vec4 tex = u_texture.eval(coords);
    vec2 normUV = coords / u_resolution;
    vec3 color = tex.rgb * vec3(normUV.x, normUV.y, 0.5);
    return vec4(color, 1.0);
}

说明:coords 的值域与 View 的宽高对应,并不是归一化的坐标。

运行效果如下。

3 通过 Canvas 进行渲染

3.1 简单应用

MainActivity.java

java 复制代码
package com.zhyan8.shaderdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.WindowManager;
import android.widget.LinearLayout;

import com.zhyan8.shaderdemo.graphics.ShaderView;

public class MainActivity extends AppCompatActivity {
    private ShaderView mShaderView;
    private LinearLayout mParentView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mParentView = findViewById(R.id.parent);
        addView();
        MyRenderer renderer = new MyRenderer(this);
        mShaderView.setRenderer(renderer);
        mShaderView.requestRender(true);
    }

    private void addView() {
        mShaderView = new ShaderView(this);
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
        lp.width = WindowManager.LayoutParams.MATCH_PARENT;
        lp.height = WindowManager.LayoutParams.MATCH_PARENT;
        mParentView.addView(mShaderView, lp);
    }
}

ShaderView.java

java 复制代码
package com.zhyan8.shaderdemo.graphics;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RuntimeShader;
import android.os.Handler;
import android.os.Looper;
import android.view.Choreographer;
import android.view.View;

/**
 * 自定义view, 承载渲染环境作用, 类比GLSurfaceView
 * @author little fat sheep
 */
public class ShaderView extends View {
    private Paint mPaint = new Paint();
    private Renderer mRenderer;
    private float[] mResolution;
    private long mStartTime = 0L;
    private long mRunTime = 0L;
    private Choreographer mChoreographer;
    private Handler mHandler;

    public ShaderView(Context context) {
        super(context);
        mChoreographer = Choreographer.getInstance();
        mHandler = new Handler(Looper.getMainLooper());
    }

    public void setRenderer(Renderer renderer) {
        this.mRenderer = renderer;
        RuntimeShader shader = renderer.onSurfaceCreated();
        mPaint.setShader(shader);
        mStartTime = System.currentTimeMillis();
    }

    public void requestRender() {
        invalidate();
    }

    public void requestRender(long duration) {
        mHandler.post(() -> {
            mChoreographer.postFrameCallback(mFrameCallback);
        });
        mHandler.postDelayed(() -> {
            mChoreographer.removeFrameCallback(mFrameCallback);
        }, duration);
    }

    public void requestRender(boolean continuous) {
        if (continuous) {
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {
            invalidate();
        }
    }

    public void stopRenderer() {
        mChoreographer.removeFrameCallback(mFrameCallback);
    }

    public void stopRenderer(long delay) {
        mHandler.postDelayed(() -> {
            mChoreographer.removeFrameCallback(mFrameCallback);
        }, delay);
    }

    @Override
    public void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mResolution = new float[] { w, h };
        mRenderer.onSurfaceChanged(w, h);
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mRunTime = System.currentTimeMillis() - mStartTime;
        mRenderer.onDrawFrame(mRunTime);
        canvas.drawRect(0f, 0f, mResolution[0], mResolution[1], mPaint);
    }

    private Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            mChoreographer.postFrameCallback(this);
            ShaderView.this.invalidate();
        }
    };

    /**
     * 渲染器接口, 类比GLSurfaceView.Renderer
     * @author little fat sheep
     */
    public interface Renderer {
        RuntimeShader onSurfaceCreated();
        void onSurfaceChanged(int width, int height);
        void onDrawFrame(long runTime);
    }
}

BitmapTexture.java

java 复制代码
package com.zhyan8.shaderdemo.graphics;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.RuntimeShader;
import android.graphics.Shader;

import com.zhyan8.shaderdemo.utils.BitmapUtils;

/**
 * Bitmap纹理
 * @author little fat sheep
 */
public class BitmapTexture {
    private BitmapShader mBitmapShader;
    private float[] mSize;

    private BitmapTexture(BitmapShader bitmapShader, float[] size) {
        this.mBitmapShader = bitmapShader;
        this.mSize = size;
    }

    public static BitmapTexture create(Context context, String assetPath) {
        Bitmap bitmap = BitmapUtils.loadBitmapFromAsset(context, assetPath);
        return create(bitmap);
    }

    public static BitmapTexture create(Context context, int rawId) {
        Bitmap bitmap = BitmapUtils.loadBitmapFromRaw(context, rawId);
        return create(bitmap);
    }

    public static BitmapTexture create(Bitmap bitmap) {
        BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        float[] size = new float[] { bitmap.getWidth(), bitmap.getHeight() };
        return new BitmapTexture(bitmapShader, size);
    }

    public void bind(RuntimeShader shader, String textureName, String sizeName) {
        shader.setInputShader(textureName, mBitmapShader);
        shader.setFloatUniform(sizeName, mSize);
    }
}

MyRenderer.java

java 复制代码
package com.zhyan8.shaderdemo;

import android.content.Context;
import android.graphics.RuntimeShader;

import com.zhyan8.shaderdemo.graphics.BitmapTexture;
import com.zhyan8.shaderdemo.graphics.ShaderView;
import com.zhyan8.shaderdemo.utils.StringUtils;

public class MyRenderer implements ShaderView.Renderer {
    private Context mContext;
    private RuntimeShader mShader;
    private float[] mResolution;
    private BitmapTexture mBitmapTexture;

    public MyRenderer(Context context) {
        this.mContext = context;
    }

    @Override
    public RuntimeShader onSurfaceCreated() {
        String shaderCode = StringUtils.loadString(mContext, "shaders/jelly.agsl");
        mShader = new RuntimeShader(shaderCode);
        mBitmapTexture = BitmapTexture.create(mContext, "textures/photo.png");
        return mShader;
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        mResolution = new float[] { width, height };
    }

    @Override
    public void onDrawFrame(long runTime) {
        mBitmapTexture.bind(mShader, "u_texture", "u_textureSize");
        mShader.setFloatUniform("u_resolution", mResolution);
        mShader.setFloatUniform("u_time", runTime / 1000f);
    }
}

jelly.agsl

cpp 复制代码
uniform shader u_texture;
uniform vec2 u_textureSize;
uniform vec2 u_resolution;
uniform float u_time;

vec4 texture(vec2 normUV) { // 纹理采样
    vec2 uv = normUV * u_textureSize;
    return u_texture.eval(uv);
}

vec2 fun(vec2 uv, float aspect) { // 畸变函数
    vec2 center = vec2(0.5, 0.5 / aspect);
    vec2 dire = normalize(uv - center);
    float dist = distance(uv, center);
    vec2 uv1 = uv + sin(dist * 2.2 + u_time * 3.5) * 0.025;
    return uv1;
}

vec4 main(vec2 coords) {
    vec2 normUV = coords / u_resolution;
    float aspect = u_resolution.x / u_resolution.y;
    normUV.y /= aspect;
    vec2 uv = fun(normUV, aspect);
    uv.y *= aspect;
    return texture(uv);
}

运行效果如下。

3.2 二次渲染

本节将对 3.1 节中 MyRenderer 进行修改,使用两个 RuntimeShader 实现二次渲染。

MyRenderer.java

java 复制代码
package com.zhyan8.shaderdemo;

import android.content.Context;
import android.graphics.RuntimeShader;

import com.zhyan8.shaderdemo.graphics.BitmapTexture;
import com.zhyan8.shaderdemo.graphics.ShaderView;
import com.zhyan8.shaderdemo.utils.StringUtils;

public class MyRenderer implements ShaderView.Renderer {
    private Context mContext;
    private RuntimeShader mShader1;
    private RuntimeShader mShader2;
    private float[] mResolution;
    private BitmapTexture mBitmapTexture;

    public MyRenderer(Context context) {
        this.mContext = context;
    }

    @Override
    public RuntimeShader onSurfaceCreated() {
        String shaderCode1 = StringUtils.loadString(mContext, "shaders/dispersion.agsl");
        mShader1 = new RuntimeShader(shaderCode1);
        String shaderCode2 = StringUtils.loadString(mContext, "shaders/jelly.agsl");
        mShader2 = new RuntimeShader(shaderCode2);
        mBitmapTexture = BitmapTexture.create(mContext, "textures/photo.jpg");
        return mShader2;
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        mResolution = new float[] { width, height };
    }

    @Override
    public void onDrawFrame(long runTime) {
        mBitmapTexture.bind(mShader1, "u_texture", "u_textureSize");
        mShader1.setFloatUniform("u_resolution", mResolution);
        mShader1.setFloatUniform("u_time", runTime / 1000f);

        mShader2.setInputShader("u_texture", mShader1);
        mShader2.setFloatUniform("u_textureSize", mResolution);
        mShader2.setFloatUniform("u_resolution", mResolution);
        mShader2.setFloatUniform("u_time", runTime / 1000f);
    }
}

dispersion.agsl

cpp 复制代码
uniform shader u_texture;
uniform vec2 u_textureSize;
uniform vec2 u_resolution;
uniform float u_time;

vec4 texture(vec2 normUV) { // 纹理采样
    vec2 uv = normUV * u_textureSize;
    return u_texture.eval(uv);
}

vec2 getOffset() { // 偏移函数
    float time = u_time * 1.5;
    vec2 dire = vec2(sin(time), cos(time));
    float strength = sin(u_time * 2.0) * 0.01;
    return dire * strength;
}

vec4 main(vec2 coords) {
    vec2 normUV = coords / u_resolution;
    vec4 color = texture(normUV);
    vec2 offset = getOffset();
    color.r = texture(normUV + offset).r;
    color.b = texture(normUV - offset).b;
    return color;
}

运行效果如下,可以看到叠加了果冻畸变和 RGB 色散效果。

相关推荐
yan123682 小时前
Linux 驱动之设备树
android·linux·驱动开发·linux驱动
aningxiaoxixi4 小时前
android stdio 的布局属性
android
CYRUS STUDIO5 小时前
FART 自动化脱壳框架一些 bug 修复记录
android·bug·逆向·fart·脱壳
寻找优秀的自己6 小时前
Cocos 打包 APK 兼容环境表(Android API Level 10~15)
android·cocos2d
大胃粥6 小时前
WMS& SF& IMS: 焦点窗口更新框架
android
QING6187 小时前
Gradle 核心配置属性详解 - 新手指南(二)
android·前端·gradle
QING6187 小时前
Gradle 核心配置属性详解 - 新手指南(一)
android·前端·gradle
_一条咸鱼_10 小时前
Android Runtime内存管理子系统启动流程原理(13)
android·面试·android jetpack
法迪10 小时前
Android的uid~package~pid的关系
android