إنشاء خرائط ضجيج سلس

صورة

يعد إنشاء صورة سلسة في Photoshop أمرًا سهلاً: قم بقص الصورة ، والتقاط الأجزاء اليمنى والأسفل المشذبة ، ثم لصقها إلى اليسار والجزء العلوي باستخدام أداة Fade. ولكن من أجل التنفيذ السليم لخرائط الضجيج السلس ، عليك التفكير بعناية.

إذا كان لديك فهم أساسي لضوضاء بيرلين ، فأنت تعلم أنها تتكون من أرقام عشوائية محرف. يتم استخدامه بشكل رئيسي في بعدين. ولكنها مفيدة أيضًا في بُعد واحد (على سبيل المثال ، عند التحرك) ، في ثلاثة أبعاد (التحول الأسطواني والكروي للكائنات ثلاثية الأبعاد) ، وحتى في أربعة أو خمسة أبعاد.

يمكن استخدام الضجيج رباعي الأبعاد لإنشاء صورة ثنائية الأبعاد سلسة. ليس من الشائع لنا أن نفكر في أربعة أبعاد ، لذلك سنتخذ بعدًا واحدًا في كل مرة.

في أمثلتي ، استخدمت ضجيجًا مزدوجًا ثنائي الأوكتاف. تكون الضوضاء البسيطة أسرع في الأبعاد الكبيرة ، وبسبب طبيعتها المثلثة ، تبدو أفضل.

لقد كتبت وظيفة صغيرة drawNoiseلإنشاء لوحة قماشية ومعالجة مصفوفة بكسل في حلقة.

ضجيج سلس أحادي البعد


في أحد الأبعاد ، تكون الضوضاء خطًا سلسًا لا نهائيًا (يبدأ تنفيذي للضوضاء مع اثنين ، لذلك أستخدم ثابتًا كمعلمة ثانية). هنا نرى أن هذه مجرد أرقام عشوائية محرف.

// one dimensional line
fNoiseScale = .02;
drawNoise(function(i,x,y){
    var v = Simplex.noise(
         123+x*fNoiseScale
        ,137 // we just need one dimension so this parameter is a constant
    );
    return v*iSize>y?255:0;
}).img();


ضجيج أحادي البعد

يمكنك استخدام هذا في الرسوم المتحركة ، مع سرد قيمة الضجيج كل مللي ثانية ، ولكن يمكنك أيضًا إنشاء حلقة وحساب جميع القيم مقدمًا. القيم الموجودة في الصورة أعلاه لا تدور حول الحواف. لكن تطبيق التكرار بسيط للغاية ، فقط بعد واحد وحلقة أخرى ... أو دائرة ، لذلك.

حلقة أحادية البعد


بالنسبة لمعظمكم ، تبدو ضوضاء بيرلين تشبه الصورة أدناه.


إذا رسمنا دائرة هنا وحسبنا قيم الضوضاء في هذه الدائرة ، فسوف نحصل على حلقة أحادية البعد.


الضوضاء مع دائرة لإنشاء حلقة أحادية البعد.

في التعليمات البرمجية ، يبدو كما يلي:

// one dimensional loop
drawNoise(function(i,x,y){
    var fNX = x/iSize // we let the x-offset define the circle
        ,fRdx = fNX*2*Math.PI // a full circle is two pi radians
        ,a = fRdsSin*Math.sin(fRdx)
        ,b = fRdsSin*Math.cos(fRdx)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
        )
    ;
    return v*iSize>y?255:0;
}).img().div(2);


ربما فهمت بالفعل ما سنقوم به. لتكرار صورة ثنائية الأبعاد ، نحتاج إلى خريطة ضوضاء ثلاثية الأبعاد (على الأقل).

بطاقة أسطوانية


تم إنشاء Noise Perlin في الأصل من أجل التركيب ثلاثي الأبعاد المستمر (فيلم "Tron"). خريطة الصورة ليست ورقة ملفوفة حول كائن ، ولكن يتم حسابها من خلال موقعها في مجال ضوضاء ثلاثي الأبعاد. لذلك ، عند قطع الكائن ، لا يزال بإمكاننا حساب الخريطة للسطح الذي تم إنشاؤه حديثًا.

قبل أن نصل إلى هدفنا النهائي المتمثل في الحصول على صورة سلسة ، نقوم أولاً بإنشاء صورة تنضم بسلاسة إلى اليسار واليمين. هذا مشابه لدائرة ثنائية الأبعاد لحلقة أحادية البعد ، ولكن مع بعد إضافي واحد: أسطوانة.

// three dimensional cylindrical map
drawNoise(function(i,x,y){
    var fNX = x/iSize
        ,fRdx = fNX*2*Math.PI
        ,a = fRdsSin*Math.sin(fRdx)
        ,b = fRdsSin*Math.cos(fRdx)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
            ,312+y*fNoiseScale // similar to the one dimensional loop but we add a third dimension defined by the image y-offset
        )
    ;
    return v*255<<0;
}).img().div(2);


خريطة ضوضاء أسطوانية

صورة خريطة كروية


قد تعتقد أنه سيكون من المناسب استخدام المجال لإنشاء صورة سلسة ، ولكنك مخطئ.

سوف أقوم بعمل استطرادا صغيرا وأظهر كيف يتم حساب خريطة الصورة الكروية وكيف تبدو.

// three dimensional spherical map
document.body.addChild('h2').innerText = 'three dimensional spherical map';
fNoiseScale = .1;
var oSpherical = drawNoise(function(i,x,y){
    var  fNX = (x+.5)/iSize // added half a pixel to get the center of the pixel instead of the top-left
        ,fNY = (y+.5)/iSize
        ,fRdx = fNX*2*Math.PI
        ,fRdy = fNY*Math.PI // the vertical offset of a 3D sphere spans only half a circle, so that is one Pi radians
        ,fYSin = Math.sin(fRdy+Math.PI) // a 3D sphere can be seen as a bunch of cicles stacked onto each other, the radius of each of these is defined by the vertical position (again one Pi radians)
        ,a = fRdsSin*Math.sin(fRdx)*fYSin
        ,b = fRdsSin*Math.cos(fRdx)*fYSin
        ,c = fRdsSin*Math.cos(fRdy)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
            ,312+c*fNoiseScale
        )
    ;
    return v*255<<0;
}).img();


خريطة الضوضاء الكروية


المجال بالضوضاء

خريطة بانورامية مكعبة


يمكن أيضًا استخدام المجال الذي أنشأناه كبانوراما إذا وضعت كاميرا في وسط الكرة. لكن أفضل طريقة هي استخدام صورة بانورامية مكعبة ، لأن وجوهها أقل بكثير. يتم إسقاط الكرة على الجوانب الستة للمكعب ، كما هو موضح في هذا الرسم التخطيطي.


تراكب كرة على المكعب

لكل بكسل على سطح المكعب ، نحتاج إلى حساب التقاطع بين وجهة النظر C في المركز والكرة. قد يبدو الأمر معقدًا ، ولكنه في الواقع بسيط جدًا.

يمكننا اعتبار خط CA كمتجه. ويمكن تطبيع المتجهات بحيث لا يتغير اتجاهها ، ولكن ينخفض ​​الطول إلى 1. ونتيجة لذلك ، ستبدو جميع المتجهات معًا مثل كرة.

التطبيع أيضًا بسيط للغاية ، نحتاج فقط إلى تقسيم قيم المتجه بواسطة xyz على الطول الكلي للمتجه. يمكن حساب طول المتجه باستخدام نظرية فيثاغورس.

في الكود أدناه ، يتم إجراء حساب التطبيع لأول مرة على وجه واحد. ثم يتم احتساب الضوضاء في وقت واحد لجميع الحواف الستة ، لأنه للحصول على موضع الوجه التالي ، تحتاج فقط لقلب القيم على طول xyz.

// 3D panoramical cube map
document.body.addChild('h2').innerText = '3D panoramical cube map';
// we're not using the drawNoise function because our canvas is rectangular
var mCubemap = document.createElement('canvas')
    ,iW = 6*iSize;
mCubemap.width = iW;
mCubemap.height = iSize;
var  iHSize = iSize/2 // half the size of the cube
    ,oCtx = mCubemap.getContext('2d')
    ,oImgData = oCtx.getImageData(0,0,iW,iSize)
    ,aPixels = oImgData.data
    ,aa = 123
    ,bb = 231
    ,cc = 321
;
for (var i=0,l=iSize*iSize;i<l;i++) {
    var  x = i%iSize        // x position in image
        ,y = (i/iSize)<<0    // y position in image
        ,a = -iHSize + x+.5    // x position on the cube plane, the added .5 is to get the center of the pixel
        ,b = -iHSize + y+.5 // y position on the cube plane
        ,c = -iHSize        // z position of the cube plane
        ,fDistanceAB = Math.sqrt(a*a+b*b) // to calculate the vectors length we use Pythagoras twice
        ,fDistanceABC = Math.sqrt(fDistanceAB*fDistanceAB+c*c)
        ,fDrds = .5*fDistanceABC // adjust the distance a bit to get a better radius in the noise field
        ,v = 1
    ;
    a /= fDrds; // normalize the vector
    b /= fDrds; // normalize the vector
    c /= fDrds; // normalize the vector
    //
    // since we now know the spherical position for one plane we can derive the positions for the other five planes simply by switching the x, y and z values (the a, b and c variables)
    var aNoisePositions = [
         [a,b,c]    // back
        ,[-c,b,a]    // right
        ,[-a,b,-c]    // front
        ,[c,b,-a]    // left
        ,[a,c,-b]    // top
        ,[a,-c,b]    // bottom
    ];
    for (var j=0;j<6;j++) {
        v = Simplex.noise(
             aa + aNoisePositions[j][0]
            ,bb + aNoisePositions[j][1]
            ,cc + aNoisePositions[j][2]
        );
        var pos = 4*(y*iW+j*iSize+x); // the final position of the rgba pixel
        aPixels[pos] = aPixels[pos+1] = aPixels[pos+2] = v*255<<0;
        aPixels[pos+3] = 255;
    }
}
oCtx.putImageData(oImgData,0,0);
document.body.addChild('img',{src:mCubemap.toDataURL("image/jpeg")});

فيما يلي ستة جوانب في صورة واحدة ، بالإضافة إلى لقطة شاشة لكيفية ظهورها عند عرضها من مكعب. يحتوي رمز المصدر على مثال ثلاثي الأبعاد مكتوب في threejs .


خريطة بانورامية مكعبة


صورة سلسة ثنائية الأبعاد


قد يبدو أن الصورة السلسة ثنائية الأبعاد سهلة التنفيذ ، ولكن يبدو لي أن هذا هو أصعب ما تم وصفه في المقالة ، لأنه لفهمها عليك التفكير في أربعة أبعاد. كان أقرب شيء لذلك هو خريطة أسطوانية (مع التكرار الأفقي) ، لذلك سنأخذها كأساس. في الخريطة الأسطوانية ، استخدمنا الوضع الأفقي للصورة للدائرة ؛ أي أن الموضع الأفقي للصورة يعطينا إحداثيين x و y في مجال الضجيج xyz. يتوافق الوضع الرأسي للصورة مع z في مجال الضجيج.

نريد أن تكون الصورة سلسة ورأسية ، لذلك إذا أضفنا بُعدًا آخر ، فيمكننا استخدامها لإنشاء دائرة ثانية واستبدال القيمة الخطية للحقل z. هذا مشابه لإنشاء أسطوانتين في مجال رباعي الأبعاد. حاولت أن أتخيل هذا على رسم ، إنه غير دقيق ، لكنني حاولت نقل المبدأ العام ، وليس رسم أسطوانة رباعية الأبعاد.


رسم تخطيطي لأسطوانتين بأربعة أبعاد

الرمز بسيط للغاية: هذه دائرتان فقط في مساحة ضوضاء رباعية الأبعاد.

// four dimensional tile
fNoiseScale = .003;
drawNoise(function(i,x,y){
    var  fNX = x/iSize
        ,fNY = y/iSize
        ,fRdx = fNX*2*Math.PI
        ,fRdy = fNY*2*Math.PI
        ,a = fRds*Math.sin(fRdx)
        ,b = fRds*Math.cos(fRdx)
        ,c = fRds*Math.sin(fRdy)
        ,d = fRds*Math.cos(fRdy)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,231+b*fNoiseScale
            ,312+c*fNoiseScale
            ,273+d*fNoiseScale
        )
    ;
    return (Math.min(Math.max(2*(v -.5)+.5,0),1)*255)<<0;
}).img().div(2,2);

وها هي النتيجة:

صورة

All Articles