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 中纹理坐标一般归一化了。
本文完整资源见 → RuntimeShader应用。
2 对 View 进行二次渲染
MainActivity.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 {@Overrideprotected 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 version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns: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"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World"android:textSize="60sp"android:textColor="#669933"/></LinearLayout>
dazzling.agsl
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
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;@Overrideprotected 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
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.removeCallbacksAndMessages(null);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.removeCallbacksAndMessages(null);mHandler.postDelayed(() -> {mChoreographer.removeFrameCallback(mFrameCallback);}, delay);}@Overridepublic 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);}@Overridepublic 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() {@Overridepublic 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
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
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;}@Overridepublic RuntimeShader onSurfaceCreated() {String shaderCode = StringUtils.loadString(mContext, "shaders/jelly.agsl");mShader = new RuntimeShader(shaderCode);mBitmapTexture = BitmapTexture.create(mContext, "textures/photo.png");return mShader;}@Overridepublic void onSurfaceChanged(int width, int height) {mResolution = new float[] { width, height };}@Overridepublic void onDrawFrame(long runTime) {mBitmapTexture.bind(mShader, "u_texture", "u_textureSize");mShader.setFloatUniform("u_resolution", mResolution);mShader.setFloatUniform("u_time", runTime / 1000f);}
}
jelly.agsl
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
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;}@Overridepublic 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;}@Overridepublic void onSurfaceChanged(int width, int height) {mResolution = new float[] { width, height };}@Overridepublic 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
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 色散效果。