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

如何修正JS着色器函数以实现像素画风格的最近邻放大?

问题:3D场景像素化渲染的最近邻放大着色器错误分析

需求说明

为测试自研游戏美术风格,我希望将3D世界渲染为像素画形式。例如,将如下场景(需先调整色彩/风格以适配像素化效果):
原图
转换为类似这样的效果:
像素化图
通过调整3D源素材的风格,像素化输出效果会更理想。常规做法是先将图像缩小至约80p,再用最近邻重采样放大到1080p,但直接渲染到80p画布再放大效率更高。
虽然着色器通常不用于最近邻格式的位图缩放,但这种方式的实时转换性能优于我尝试过的其他方法。

我的代码

位图缓冲区以行优先(row major)格式存储,即r1, g1, b1, a1, r2, g2, b2, a2...,我使用gpu.js将JS函数转换为着色器。目标是将位图按最近邻方式放大,使每个像素变为2x2或3x3等方块。假设inputBuffer是输出尺寸按比例缩小后的缓冲区。

var pixelateMatrix = gpu.createKernel(function(inputBuffer, width, height, scale) { 
  var y = Math.floor((this.thread.x / (width[0] * 4)) / scale[0]); 
  var x = Math.floor((this.thread.x % (width[0] * 4)) / scale[0]); 
  var remainder = this.thread.x % 4; 
  return inputBuffer[(x * y) + remainder]; 
}).setOutput([width * height * 4]); 

注意:代码遍历的是全尺寸输出的新缓冲区,需根据outputBuffer的当前索引(库中通过this.thread.x暴露)找到sourceBuffer中的对应坐标。

问题现象

代码未实现最近邻放大,反而生成了彩虹效果(上方是正常小尺寸渲染图,下方是着色器输出结果,右侧为输入输出缓冲区的调试日志):
错误结果
请问我的代码哪里出错了?


问题分析与解决方案

你代码里的核心问题是输入缓冲区的索引计算逻辑完全错误,导致颜色通道和像素位置混乱,才出现了彩虹色的异常效果。咱们一步步拆解问题并修正:

1. 错误点拆解

首先要明确几个关键概念:

  • this.thread.x是输出缓冲区的全局索引(每个像素占4个通道,所以范围是0到outputWidth*outputHeight*4 - 1)
  • 行优先缓冲区的像素索引公式是:(y * 宽度 + x) * 4 + 通道索引,你的代码完全违背了这个规则:
    • 用输入宽度计算输出像素的坐标,混淆了输入输出尺寸的对应关系
    • 最后计算输入索引时用(x * y) + remainder,完全错误地拼接了坐标和通道

2. 修正后的代码

我们重新梳理正确的映射逻辑:

  1. 从全局线程索引分离出当前处理的颜色通道
  2. 将全局索引转换为输出像素的坐标
  3. 把输出坐标按放大比例映射回输入像素的坐标(最近邻就是直接取整)
  4. 用行优先规则计算输入缓冲区的正确索引

修正后的代码如下:

var pixelateMatrix = gpu.createKernel(function(inputBuffer, inputWidth, inputHeight, scale) { 
  // 1. 获取当前处理的颜色通道(0=r,1=g,2=b,3=a)
  const channel = this.thread.x % 4;
  // 2. 计算当前输出像素的全局索引(剔除通道维度)
  const outputPixelIdx = Math.floor(this.thread.x / 4);
  // 3. 计算输出图像的宽高
  const outputWidth = inputWidth * scale;
  // 4. 计算输出像素的坐标
  const outputX = outputPixelIdx % outputWidth;
  const outputY = Math.floor(outputPixelIdx / outputWidth);
  // 5. 映射到输入图像的对应像素坐标(最近邻采样)
  const inputX = Math.floor(outputX / scale);
  const inputY = Math.floor(outputY / scale);
  // 6. 计算输入缓冲区的正确索引(行优先规则)
  const inputIndex = (inputY * inputWidth + inputX) * 4 + channel;
  
  return inputBuffer[inputIndex]; 
})
.setOutput([inputWidth * inputHeight * scale * scale * 4]); // 输出尺寸为输入的scale²倍,每个像素4通道

3. 额外注意事项

  • 调用kernel时,确保传入的inputWidth/inputHeight是输入缓冲区的实际宽高,scale是放大倍数(如2、3)
  • 输出缓冲区的尺寸计算必须准确:输入像素数 × scale² × 4,对应放大后的所有像素通道
  • 如果你的参数习惯用数组形式(比如原代码的width[0]),只需把变量名对应替换即可

这样修改后,就能正确实现最近邻放大,每个输入像素会被放大成scale×scale的纯色方块,不会再出现颜色混乱的彩虹效果了。

内容的提问来源于stack exchange,提问作者john doe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:08:45