在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
相关产品推荐
相关产品推荐

