You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在OpenGL绘图应用中实现逼真铅笔笔刷纹理的技术求助

实现专业级OpenGL铅笔笔刷方案

核心原理拆解

专业绘图软件的铅笔笔刷核心在于模拟真实铅笔的颗粒感、笔触渐变、压力响应,而非简单叠加纹理。关键要做到:

  • 用铅笔纹理作为遮罩,控制颜色的显示区域
  • 叠加噪声纹理模拟铅笔与纸张摩擦的颗粒
  • 根据压力、速度动态调整纹理密度、透明度和缩放
  • 保证笔触的连续性,避免方形拼接的生硬感

片段着色器优化方案

替换原着色器,实现遮罩+噪声叠加+压力响应的完整效果:

precision mediump float;
uniform sampler2D uPencilTexture; // 带Alpha的灰度铅笔纹理(黑为笔触区域,透明为空白)
uniform sampler2D uNoiseTexture;  // 无缝循环噪声纹理(模拟纸纹/铅笔颗粒)
varying vec2 vTexCoord;           // 当前片段的笔刷纹理坐标
uniform vec4 uColor;              // 笔刷颜色
uniform float uPressure;          // 触点压力(0-1)
uniform float uRotation;          // 笔触方向旋转角度

// 旋转纹理坐标的辅助函数
vec2 rotate(vec2 uv, float angle) {
    float s = sin(angle);
    float c = cos(angle);
    mat2 rot = mat2(c, -s, s, c);
    return rot * (uv - 0.5) + 0.5;
}

void main() {
    // 1. 旋转纹理坐标,模拟笔尖随笔触方向变化
    vec2 rotatedTexCoord = rotate(vTexCoord, uRotation);
    
    // 2. 采样铅笔纹理作为主遮罩,根据压力调整笔触虚实
    float pencilAlpha = 1.0 - texture2D(uPencilTexture, rotatedTexCoord).r;
    pencilAlpha = pow(pencilAlpha, 1.0 / (uPressure * 2.0 + 0.5));
    
    // 3. 采样噪声纹理,模拟颗粒感,压力越大颗粒越明显
    vec2 noiseUV = rotatedTexCoord * 12.0;
    float noise = texture2D(uNoiseTexture, noiseUV).r;
    noise = mix(0.8, 1.0, noise) * (uPressure * 0.4 + 0.6);
    
    // 4. 组合颜色、遮罩与噪声,加入边缘衰减避免方形生硬感
    vec3 finalColor = uColor.rgb * noise;
    float edgeFalloff = smoothstep(0.45, 0.5, length(vTexCoord - 0.5));
    float finalAlpha = uColor.a * pencilAlpha * uPressure * (1.0 - edgeFalloff);
    
    gl_FragColor = vec4(finalColor, finalAlpha);
}

渲染流程调整

1. 解决笔触断层问题

原代码每个触点绘制独立方形,易出现断层。修改brush.updateBrush(td),通过插值生成连续笔触的采样点:

// Brush类中添加连续笔触逻辑
fun updateBrush(currentTouch: TouchData) {
    val lastTouch = touchDataManager.getLast() ?: return
    // 根据触点距离生成插值采样点,保证笔触连续
    val distance = currentTouch.position.distanceTo(lastTouch.position)
    val stepCount = max(2, (distance / 8f).toInt()) // 每8像素一个采样点
    
    for (i in 0..stepCount) {
        val t = i / stepCount.toFloat()
        val interpolatedPos = lastTouch.position.lerp(currentTouch.position, t)
        val interpolatedPressure = lerp(lastTouch.pressure, currentTouch.pressure, t)
        // 将插值后的触点加入绘制队列
        drawQueue.add(InterpolatedTouch(interpolatedPos, interpolatedPressure))
    }
}

2. 模板缓冲区实现笔触累积

若需实现多次叠加加深颜色的效果,启用模板缓冲区记录笔触叠加次数:

// 绘制前启用模板测试
GLES30.glEnable(GLES30.GL_STENCIL_TEST)
GLES30.glStencilFunc(GLES30.GL_ALWAYS, 1, 0xFF)
GLES30.glStencilOp(GLES30.GL_KEEP, GLES30.GL_KEEP, GLES30.GL_INCR)

// 绘制笔触
square.draw(...)

// 绘制后关闭模板测试
GLES30.glDisable(GLES30.GL_STENCIL_TEST)

后续可在着色器中读取模板值,根据叠加次数调整颜色深度。

3. 纹理采样参数优化

加载纹理时设置无缝循环与线性采样,避免纹理拼接痕迹:

// 铅笔纹理与噪声纹理通用设置
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT)
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_REPEAT)
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR)
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)

额外增强技巧

  • 方向响应:根据笔触移动方向计算旋转角度,传递给着色器旋转铅笔纹理,模拟笔尖角度变化
  • 速度影响:笔触速度越快,降低透明度,模拟快速画过的淡痕
  • 多层次噪声:叠加2-3层不同缩放的噪声纹理,增强颗粒感的层次感
  • 真实纹理素材:使用扫描的真实铅笔笔触图作为笔刷纹理,比合成灰度图更逼真

内容的提问来源于stack exchange,提问作者Avinash kumawat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 15:47:05