Android Compose中簡(jiǎn)單使用AGSL

本文探討下如何使用AGSL (Android Graphics Shading Language)帝洪。

著色器(Shader)的概念

簡(jiǎn)單來(lái)說(shuō),著色器是可以插入到圖形管道(graphics pipeline)的不同階段的代碼片段脚猾,它們由GPU執(zhí)行葱峡。根據(jù)插入位置的不同,它們的名稱和函數(shù)可能略有不同龙助。有幾種類型砰奕,包括片段著色器(fragment shaders)、頂點(diǎn)著色器(vertex shaders)、幾何著色器(geometry shaders)和細(xì)分著色器(tessellation shaders)等军援。

本文僅探討第一種類型:片段著色器(也稱為像素著色器pixel shaders)仅淑。有一點(diǎn)非常重要,從Android 13(api>=33)才支持AGSL胸哥。

編程語(yǔ)言: AGSL

OpenGL ES開(kāi)發(fā)者來(lái)肯定對(duì)GLSL(OpenGL Shading Language)很熟悉涯竟。AGSL和GLSL很像,有很多共同點(diǎn)空厌。AGSL和GLSL的最大區(qū)別是兩者的坐標(biāo)系:在AGSL昆禽,原點(diǎn)在左上方;在GLSL蝇庭,原點(diǎn)在左下方醉鳖。這個(gè)說(shuō)法來(lái)自原文鏈接,原文這樣說(shuō)的:

the most significant difference between AGSL and GLSL lies in their coordinate systems. In GLSL, the origin is typically located in the bottom-left corner of the screen, whereas in AGSL, it is positioned in the top-left corner.

這跟我理解的GLSL坐標(biāo)系不太一樣哮内,我所理解的GLSL坐標(biāo)系盗棵,原點(diǎn)是在最中心,縱坐標(biāo)范圍自下而上是-1到1北发,橫坐標(biāo)范圍從左到右是-1到1. 哪位大神幫忙解釋下GLSL坐標(biāo)系到底是什么樣的纹因?作者的意思是不是是說(shuō)相對(duì)于x、y都為正的區(qū)間來(lái)說(shuō)琳拨,原點(diǎn)的位置處于左下角瞭恰?如果這樣解釋的話,我所理解的GLSL坐標(biāo)系和原作者的就應(yīng)該是一致的狱庇。

AGSL和C語(yǔ)言很像惊畏,可以看下面一段非常簡(jiǎn)單的fragment shader代碼片段:

half4 main(vec2 fragCoord) { 
    return half4(1.0, 0.0, 0.0, 1.0);
}

函數(shù)的輸入類型是vec2,二維向量密任,表示像素的坐標(biāo)颜启,可以用fragCoord.x和fragCoord.y訪問(wèn)橫縱坐標(biāo)值;參數(shù)名字叫做fragCoord浪讳,當(dāng)然可以隨便取別的任意名字缰盏。
fragment shader的輸出類型是half4,字面意思是4的一半淹遵,就是2字節(jié)口猜,即16-bit的浮點(diǎn)類型。這個(gè)返回值存儲(chǔ)了要繪制的像素的顏色值————上面的代碼是紅色(r=1,g=0,b=0,a=1)透揣。最終的繪制效果就是一張純紅色的圖片济炎。

純紅色有點(diǎn)單調(diào),我們來(lái)點(diǎn)新鮮的:顏色根據(jù)x坐標(biāo)進(jìn)行線性漸變的效果淌实,也就是我們要?jiǎng)討B(tài)調(diào)整像素的顏色值冻辩。我們需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的數(shù)據(jù)緩沖區(qū)猖腕,這個(gè)數(shù)據(jù)在Shader的所有的并行執(zhí)行中共享,這就要借助uniform數(shù)據(jù)類型恨闪,哦倘感,不對(duì),uniform不是數(shù)據(jù)類型咙咽,只是一個(gè)關(guān)鍵字:

uniform float2 resolution;
const float3 colour = float3(1, 0, 0);
half4 main(vec2 fragCoord) { 
    return half4(colour * fragCoord.x / resolution.x, 1.);
}

可以看到老玛,新代碼多了個(gè)uniform修飾的float2類型的變量,colour表示像素點(diǎn)的三原色值即三個(gè)float值(實(shí)例中是純紅色)钧敞,fragCoord.x表示像素點(diǎn)的x坐標(biāo)蜡豹,resolution.x表示屏幕寬度,這樣我們就實(shí)現(xiàn)了根據(jù)橫坐標(biāo)紅色進(jìn)行漸變的顏色效果溉苛。

漸變色比純色感覺(jué)好多了镜廉,不過(guò)我們可以再往前進(jìn)一步,不僅僅是紅色的漸變愚战,我們可以動(dòng)態(tài)設(shè)置color的三原色的值娇唯,代碼如下:

uniform float2 resolution;
uniform float4 colour;
half4 main(vec2 fragCoord) {
    return half4(colour.rgb * fragCoord.x / resolution.x, 1.);
}

可以看到,我們把color變量也用uniform修飾了寂玲,類型從float3改為了float4塔插。怎么修改color的值呢?kotlin偽代碼如下:

fun uniform(color: android.graphics.Color) {
    val colorArray = floatArrayOf(color.red(), color.green(), color.blue(), color.alpha())
    android.graphics.RuntimeShader.setFloatUniform("colour", colorArray)
}

需要注意的是setFloatUniform函數(shù)的第一個(gè)參數(shù)的字符常量一定要和AGSL代碼里的uniform float4 colour變量名字保持一致拓哟。

完整的代碼包括MainActivity.kt想许、LinearGradientScreen.kt、LinearGradientViewModel.kt断序、LinearGradientModifier.kt流纹、ShaderModifier.kt、ShaderModifier.android.kt逢倍,源碼如下:

// MainActivity.kt
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                LinearGradientScreen(android.graphics.Color.valueOf(1f, 0f, 0f, 1f)) // 紅色漸變效果
            }
        }
    }
}

// LinearGradientScreen.kt
@Composable
fun LinearGradientScreen(
    color: Color,
    modifier: Modifier = Modifier,
    viewModel: LinearGradientViewModel = androidx.lifecycle.viewmodel.compose.viewModel { LinearGradientViewModel(color) },
) {
    val colorState by viewModel.color.collectAsState()
    var green: MutableState<Float> = remember { mutableStateOf<Float>(0f) }
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .height(600.dp)
    ) {
        Text(
            text = "",
            modifier = Modifier
                .fillMaxWidth()
                .height(400.dp)
                .linearGradualShader(colorState)
        )
        Slider(
            value = green.value,
            onValueChange = {
                green.value = it
                viewModel.onColorChanged(Color.valueOf(1f - green.value, green.value, 0f, 0f))
            },
            valueRange = 0f..1f,
            modifier = Modifier.fillMaxWidth()
        )
    }
}

// LinearGradientViewModel.kt
class LinearGradientViewModel(color: Color) : ViewModel() {
    private val _color: MutableStateFlow<Color> = MutableStateFlow(color)
    val color: StateFlow<Color> = _color.asStateFlow()

    fun onColorChanged(config: Color) {
        _color.update { config }
    }
}

// LinearGradientModifier.kt
private val shader = """
    uniform float2 resolution;
    uniform float4 colour;
    half4 main(vec2 fragCoord) {
        return half4(colour.rgb * fragCoord.x / resolution.x, 1.);
    }
""".trimIndent()

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun Modifier.linearGradualShader(
    color: Color
): Modifier = this then shader(shader) {
    uniform("colour", color)
}

// ShaderModifier.kt
interface ShaderUniformProvider {
    fun uniform(name: String, value: Int)
    fun uniform(name: String, value: Float)
    fun uniform(name: String, value1: Float, value2: Float)
    fun uniform(name: String, color: Color)
}

// ShaderModifier.android.kt
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 fun Modifier.shader(
    shader: String,
    uniformsBlock: (ShaderUniformProvider.() -> Unit)?,
): Modifier = this then composed {
    val runtimeShader = remember { RuntimeShader(shader) }
    val shaderUniformProvider = remember { ShaderUniformProviderImpl(runtimeShader) }
    graphicsLayer {
        clip = true
        renderEffect = RenderEffect
            .createShaderEffect(
                runtimeShader.apply {
                    uniformsBlock?.invoke(shaderUniformProvider)
                    shaderUniformProvider.updateResolution(size)
                },
            ).asComposeRenderEffect()
    }
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 fun Modifier.runtimeShader(
    shader: String,
    uniformName: String = "content",
    uniformsBlock: (ShaderUniformProvider.() -> Unit)?,
): Modifier = this then composed {
    val runtimeShader = remember { RuntimeShader(shader) }
    val shaderUniformProvider = remember { ShaderUniformProviderImpl(runtimeShader) }
    graphicsLayer {
        clip = true
        renderEffect = RenderEffect
            .createRuntimeShaderEffect(
                runtimeShader.apply {
                    uniformsBlock?.invoke(shaderUniformProvider)
                    shaderUniformProvider.updateResolution(size)
                },
                uniformName,
            ).asComposeRenderEffect()
    }
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class ShaderUniformProviderImpl(
    private val runtimeShader: RuntimeShader,
) : ShaderUniformProvider {

    fun updateResolution(size: Size) {
        uniform("resolution", size.width, size.height)
    }

    override fun uniform(name: String, value: Int) {
        runtimeShader.setIntUniform(name, value)
    }

    override fun uniform(name: String, value: Float) {
        runtimeShader.setFloatUniform(name, value)
    }

    override fun uniform(name: String, value1: Float, value2: Float) {
        runtimeShader.setFloatUniform(name, value1, value2)
    }

    override fun uniform(name: String, color: Color) {
        val colorArray = floatArrayOf(color.red(), color.green(), color.blue(), color.alpha())
        val colorArray3 = floatArrayOf(color.red(), color.green(), color.blue())
        runtimeShader.setFloatUniform(name, colorArray)
        // runtimeShader.setColorUniform(name, color.toArgb())
        // runtimeShader.setFloatUniform(name, colorArray3)
    }
}

最終的漸變效果如圖所示(拖動(dòng)條最小時(shí)捧颅,是純紅色的漸變效果r=1,g=0,b=0,a=1;拖動(dòng)條最大時(shí)是純綠色的漸變效果r=0,g=1,b=0,a=1):

更多效果和算法

漸變效果涉及的算法比較簡(jiǎn)單较雕,中學(xué)生都能看懂,如果感興趣可以研究下其它的算法挚币,比如下面幾個(gè):

  1. Vignetting亮蒋。
  2. Smooth pixelation,跟pixelation類似妆毕,但是用正弦波來(lái)調(diào)制慎玖。
  3. Chromatic aberration

這三個(gè)效果的AGSL源碼分別如下:

// Vignetting
uniform float2 resolution;
uniform shader content; 
uniform float intensity;
uniform float decayFactor;
half4 main(vec2 fragCoord) {
    vec2 uv = fragCoord.xy / resolution.xy;
    half4 color = content.eval(fragCoord);
    uv *=  1.0 - uv.yx;
    float vig = clamp(uv.x*uv.y * intensity, 0., 1.);
    vig = pow(vig, decayFactor);
    return half4(vig * color.rgb, color.a);
}

// Smooth pixelation
uniform float2 resolution;
uniform shader content; 
uniform float pixelSize;

vec4 main(vec2 fragCoord) {
    vec2 uv = fragCoord.xy / resolution.xy;
    float factor = (abs(sin( resolution.y * (uv.y - 0.5) / pixelSize)) + abs(sin( resolution.x * (uv.x - 0.5) / pixelSize))) / 2.0;
    half4 color = content.eval(fragCoord);
    return half4(factor * color.rgb, color.a); 
}

// Chromatic aberration
uniform float2 resolution;
uniform float intensity;
uniform shader content; 

half4 main(vec2 fragCoord) {
    vec2 uv = fragCoord.xy / resolution.xy;
    half4 color = content.eval(fragCoord);
    vec2 offset = intensity / resolution.xy;
    color.r = content.eval(resolution.xy * ((uv - 0.5) * (1.0 + offset) + 0.5)).r;
    color.b = content.eval(resolution.xy * ((uv - 0.5) * (1.0 - offset) + 0.5)).b;
    return color; 
}






原文鏈接:

Pushing the Boundaries of Compose Multiplatform with AGSL Shaders

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市笛粘,隨后出現(xiàn)的幾起案子趁怔,更是在濱河造成了極大的恐慌湿硝,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件润努,死亡現(xiàn)場(chǎng)離奇詭異关斜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)铺浇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門痢畜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鳍侣,你說(shuō)我怎么就攤上這事丁稀。” “怎么了倚聚?”我有些...
    開(kāi)封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵线衫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我惑折,道長(zhǎng)桶雀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任唬复,我火速辦了婚禮矗积,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘敞咧。我一直安慰自己棘捣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布休建。 她就那樣靜靜地躺著乍恐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪测砂。 梳的紋絲不亂的頭發(fā)上茵烈,一...
    開(kāi)封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音砌些,去河邊找鬼呜投。 笑死,一個(gè)胖子當(dāng)著我的面吹牛存璃,可吹牛的內(nèi)容都是我干的仑荐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纵东,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼粘招!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起偎球,我...
    開(kāi)封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤洒扎,失蹤者是張志新(化名)和其女友劉穎辑甜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體袍冷,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磷醋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了难裆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片子檀。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖乃戈,靈堂內(nèi)的尸體忽然破棺而出褂痰,到底是詐尸還是另有隱情,我是刑警寧澤症虑,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布缩歪,位于F島的核電站,受9級(jí)特大地震影響谍憔,放射性物質(zhì)發(fā)生泄漏匪蝙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一习贫、第九天 我趴在偏房一處隱蔽的房頂上張望逛球。 院中可真熱鬧,春花似錦苫昌、人聲如沸颤绕。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奥务。三九已至,卻和暖如春袜硫,著一層夾襖步出監(jiān)牢的瞬間氯葬,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工婉陷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帚称,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓憨攒,卻偏偏與公主長(zhǎng)得像世杀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肝集,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容