【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 色散效果。

相关推荐
用户20187928316727 分钟前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子27 分钟前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822736 分钟前
安卓接入Max广告源
android
齊家治國平天下36 分钟前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO37 分钟前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel39 分钟前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢44 分钟前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖1 小时前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉2 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql
风起云涌~3 小时前
【Android】浅谈androidx.startup.InitializationProvider
android