所以我想在我的基于像素的渲染系统中实现光照,谷歌搜索并发现显示R / G / B值更亮或更暗我必须将每个红绿色和蓝色值乘以数字<如图1所示,将其显示得更暗并且通过数字> 1显示它更轻。
所以我像这样实现了它,但它真的拖累了我的性能,因为我必须为每个像素执行此操作:
void PixelRenderer::applyLight(Uint32& color){
Uint32 alpha = color >> 24;
alpha << 24;
alpha >> 24;
Uint32 red = color >> 16;
red = red << 24;
red = red >> 24;
Uint32 green = color >> 8;
green = green << 24;
green = green >> 24;
Uint32 blue = color;
blue = blue << 24;
blue = blue >> 24;
red = red * 0.5;
green = green * 0.5;
blue = blue * 0.5;
color = alpha << 24 | red << 16 | green << 8 | blue;
}
关于如何提高速度的任何想法或例子?
答案 0 :(得分:3)
试试这个:(编辑:事实证明,这只是一个可读性改进,但请继续阅读以获得更多见解。)
void PixelRenderer::applyLight(Uint32& color)
{
Uint32 alpha = color >> 24;
Uint32 red = (color >> 16) & 0xff;
Uint32 green = (color >> 8) & 0xff;
Uint32 blue = color & 0xff;
red = red * 0.5;
green = green * 0.5;
blue = blue * 0.5;
color = alpha << 24 | red << 16 | green << 8 | blue;
}
话说回来,你应该明白,使用通用处理器(如计算机的CPU)执行那种操作必然会非常慢。这就是发明硬件加速显卡的原因。
修改
如果你坚持以这种方式操作,那么你可能不得不诉诸黑客以提高效率。在处理8位通道值时经常使用的一种类型的hack是查找表。使用查找表,不是将每个单独的通道值乘以浮点数,而是预先计算256个值的数组,其中数组的索引是通道值,并且该索引中的值是将通道值乘以的预计算结果漂浮。然后,在转换图像时,只需使用通道值查找数组的条目,而不是执行实际的浮点乘法。这要快得多。 (但仍然没有编程专用的那么快,大规模并行硬件为你做那些事情。)
修改
正如其他人已经指出的那样,如果你不打算在alpha通道上运行,那么你不需要提取它然后再应用它,你可以保持不变。所以,你可以color = (color & 0xff000000) | red << 16 | green << 8 | blue;
答案 1 :(得分:3)
这样的移位和遮罩在现代处理器上通常非常快。我可能会看一些其他的事情:
Uint32 PixelRenderer::applyLight(Uint32 color)
并返回修改后的值,这可能有助于避免一些解除引用并为编译器提供一些额外的优化机会。 最后,查看汇编程序以查看编译器生成的内容(具有优化功能)。有分支或转换吗?你的方法实际上已经内联了吗?
答案 2 :(得分:2)
要保留前端使用中的alpha值:
(color>>1)&0x7F7F7F | (color&0xFF000000)
(关于Wimmel在评论中提供的内容的调整)。
我认为这里的'学习曲线'是你使用shift并转回来掩盖比特。您应该将&
与屏蔽值一起使用。
对于更通用的解决方案(0.0<=factor<=1.0
):
void PixelRenderer::applyLight(Uint32& color, double factor){
Uint32 alpha=color&0xFF000000;
Uint32 red= (color&0x00FF0000)*factor;
Uint32 green= (color&0x0000FF00)*factor;
Uint32 blue=(color&0x000000FF)*factor;
color=alpha|(red&0x00FF0000)|(green&0x0000FF00)|(blue&0x000000FF);
}
请注意,在执行乘法运算之前,无需将组件向下移位到低位。
最终,您可能会发现瓶颈是浮点转换和算术。
要减少这一点,您应该考虑:
将其缩小为比例因子,例如在0-256范围内。
预先计算factor*component
为256个元素的数组并“挑选”这些组件。
我建议的范围是257,因为你可以达到以下因素:
对于更通用的解决方案(0<=factor<=256
):
void PixelRenderer::applyLight(Uint32& color, Uint32 factor){
Uint32 alpha=color&0xFF000000;
Uint32 red= ((color&0x00FF0000)*factor)>>8;
Uint32 green= ((color&0x0000FF00)*factor)>>8;
Uint32 blue=((color&0x000000FF)*factor)>>8;
color=alpha|(red&0x00FF0000)|(green&0x0000FF00)|(blue&0x000000FF);
}
这是一个可运行的程序,说明了第一个例子:
#include <stdio.h>
#include <inttypes.h>
typedef uint32_t Uint32;
Uint32 make(Uint32 alpha,Uint32 red,Uint32 green,Uint32 blue){
return (alpha<<24)|(red<<16)|(green<<8)|blue;
}
void output(Uint32 color){
printf("alpha=%"PRIu32" red=%"PRIu32" green=%"PRIu32" blue=%"PRIu32"\n",(color>>24),(color&0xFF0000)>>16,(color&0xFF00)>>8,color&0xFF);
}
Uint32 applyLight(Uint32 color, double factor){
Uint32 alpha=color&0xFF000000;
Uint32 red= (color&0x00FF0000)*factor;
Uint32 green= (color&0x0000FF00)*factor;
Uint32 blue=(color&0x000000FF)*factor;
return alpha|(red&0x00FF0000)|(green&0x0000FF00)|(blue&0x000000FF);
}
int main(void) {
Uint32 color1=make(156,100,50,20);
Uint32 result1=applyLight(color1,0.9);
output(result1);
Uint32 color2=make(255,255,255,255);
Uint32 result2=applyLight(color2,0.1);
output(result2);
Uint32 color3=make(78,220,200,100);
Uint32 result3=applyLight(color3,0.05);
output(result3);
return 0;
}
预期输出为:
alpha=156 red=90 green=45 blue=18
alpha=255 red=25 green=25 blue=25
alpha=78 red=11 green=10 blue=5
答案 3 :(得分:2)
我没有看到其他人提到的一件事是并行化您的代码。至少有两种方法:SIMD指令和多线程。
SIMD instructions(如SSE,AVX等)同时对多个数据执行相同的数学运算。因此,您可以将像素的红色,绿色,蓝色和alpha乘以1条指令中的相同值,如下所示:
vec4 lightValue = vec4(0.5, 0.5, 0.5, 1.0);
vec4 result = vec_Mult(inputPixel, lightValue);
这相当于:
lightValue.red = 0.5;
lightValue.green = 0.5;
lightValue.blue = 0.5;
lightValue.alpha = 1.0;
result.red = inputPixel.red * lightValue.red;
result.green = inputPixel.green * lightValue.green;
result.blue = inputPixel.blue * lightValue.blue;
result.alpha = inputPixel.alpha * lightValue.alpha;
您还可以使用在多个核心上运行的线程将图像切割为切片并同时在多个切片上执行闪电操作。如果您使用的是C ++ 11,则可以使用std::thread
来启动多个线程。否则,您的操作系统可能具有线程功能,例如WinThreads,Grand Central Dispatch,pthreads,boost threads,Threading Building Blocks等。
您可以将上述两者结合使用,并且具有一次对整个像素进行操作的多线程代码。
如果您想更进一步,可以使用OpenGL,OpenCL,DirectX,Metal,{{{ 3}},Mantle或其他CUDA技术之一。 GPU通常是数百个核心,可以非常快速地并行处理多个磁贴,每个磁贴一次处理整个像素(而不仅仅是通道)。
但更好的选择可能就是根本不写任何代码。很可能有人已经完成了这项工作,你可以利用它。例如,在MacOS上有GPGPU和CoreImage框架。在iOS上你也有CoreImage,还有Accelerate。我确信在Windows,Linux和其他可能正在使用的操作系统上都有类似的库。
答案 4 :(得分:1)
32 bits uint
转换为struct
。.h
包含文件中,以便可以内联applyLight
方法以接受像素数组。对于如此小的方法,方法调用开销可能很重要实现:
class brightness {
private:
struct pixel { uint8_t b, g, r, a; };
float factor;
static inline void apply(uint8_t& p, float f) {
p = max(min(int(p * f), 255),0);
}
public:
brightness(float factor) : factor(factor) { }
void apply(uint32_t& color){
pixel& p = (pixel&)color;
apply(p.b, factor);
apply(p.g, factor);
apply(p.r, factor);
}
};
使用查找表实现(使用&#34时循环较慢;循环展开&#34;):
class brightness {
struct pixel { uint8_t b, g, r, a; };
uint8_t table[256];
public:
brightness(float factor) {
for(int i = 0; i < 256; i++)
table[i] = max(min(int(i * factor), 255), 0);
}
void apply(uint32_t& color){
pixel& p = (pixel&)color;
p.b = table[p.b];
p.g = table[p.g];
p.r = table[p.r];
}
};
// usage
brightness half_bright(0.5);
uint32_t pixel = 0xffffffff;
half_bright.apply(pixel);