ভূমিকা
প্রথমে Three.js-এর ভূমিকা পড়ুন, কারণ এটি এই ডকুমেন্টের ভিত্তি।
আমি শেডার নিয়ে আলোচনা করতে চাই। WebGL দারুণ এবং আমি আগেই বলেছি যে Three.js (এবং অন্যান্য লাইব্রেরি) আপনার জন্য জটিলতাগুলি দূর করার ক্ষেত্রে একটি দুর্দান্ত কাজ করে। কিন্তু এমন সময় আসবে যখন আপনি কোনও নির্দিষ্ট এফেক্ট তৈরি করতে চাইবেন অথবা আপনার স্ক্রিনে কীভাবে সেই অসাধারণ জিনিসটি দেখা গেল তা আরও গভীরভাবে জানতে চাইবেন এবং শেডার প্রায় নিশ্চিতভাবেই সেই সমীকরণের অংশ হবে। আপনি হয়ত শেষ টিউটোরিয়ালের প্রাথমিক বিষয় থেকে একটু জটিল বিষয়ে যেতে চাইবেন।
আমি ধরে নিচ্ছি যে আপনি Three.js ব্যবহার করছেন, কারণ এটি শেডার চালু করার জন্য আমাদের অনেক কাজ করে দেয়। আপনি প্রথমে শেডারের প্রসঙ্গ সম্পর্কে জানবেন, তারপরে এই ডকুমেন্টে পরবর্তী উন্নত ধাপগুলি সম্পর্কে জানবেন।
১. আমাদের দুটি শেডার
WebGL-এ ফিক্সড পাইপলাইন ব্যবহার করা যায় না, অর্থাৎ এটি আপনার কন্টেন্টকে সরাসরি রেন্ডার করার কোনও উপায় প্রদান করে না। এটি যা অফার করে, তা হল প্রোগ্রামেবল পাইপলাইন, যা আরও শক্তিশালী কিন্তু বোঝা এবং ব্যবহার করা আরও কঠিন। সংক্ষেপে, প্রোগ্রামেবল পাইপলাইন মানে হল, প্রোগ্রামার হিসেবে আপনি স্ক্রিনে ভার্টিসেস এবং অন্যান্য জিনিস রেন্ডার করার দায়িত্ব নেন। শেডার হল এই পাইপলাইনের অংশ এবং সেগুলি দুই ধরনের হয়:
- ভার্টেক্স শেডার
- ফ্র্যাগমেন্ট শেডার
আমি নিশ্চিত যে আপনিও একমত হবেন যে, এই দুটির কোনওটিরই নিজস্ব কোনও অর্থ নেই। এই দুটি মোড সম্পর্কে আপনার যা জানা উচিত তা হল, এগুলি সম্পূর্ণভাবে আপনার গ্রাফিক্স কার্ডের GPU-তে চলে। এর অর্থ হল, আমরা যতটা সম্ভব কাজ তাদের উপর ছেড়ে দিতে চাই, যাতে আমাদের CPU অন্যান্য কাজ করতে পারে। শেডারের জন্য প্রয়োজনীয় ফাংশনের জন্য আধুনিক GPU ব্যাপকভাবে অপ্টিমাইজ করা হয়, তাই এটি ব্যবহার করতে পারা খুবই ভালো।
২. ভার্টেক্স শেডার
একটি স্ট্যান্ডার্ড প্রিমিটিভ শেপ নিন, যেমন একটি গোলক। এটি উল্লম্ব আকৃতি দিয়ে তৈরি, তাই তো? ভার্টেক্স শেডার এই ভার্টেক্সগুলির প্রত্যেকটিকে এক এক করে নিয়ে সেগুলির সাথে যা খুশি করতে পারে। ভার্টেক্স শেডার প্রতিটি ভার্টেক্স নিয়ে কী করবে তা তার উপর নির্ভর করে, তবে তার একটি দায়িত্ব আছে: তাকে কোনও এক সময় gl_Position নামে কিছু সেট করতে হবে, যা হল একটি 4D ফ্লোট ভেক্টর এবং স্ক্রিনে ভার্টেক্সের চূড়ান্ত পজিশন। In and of itself that's quite an interesting process, because we're actually talking about getting a 3D position (a vertex with x,y,z) onto, or projected, to a 2D screen. আমাদের জন্য সৌভাগ্যবশত, আমরা যদি Three.js-এর মতো কিছু ব্যবহার করি, তাহলে আমাদের কাছে gl_Position সেট করার একটি শর্টহ্যান্ড উপায় আছে, যাতে জিনিসগুলি খুব বেশি ভারী না হয়।
৩. ফ্র্যাগমেন্ট শেডার
তাহলে আমাদের কাছে অবজেক্ট এবং তার ভার্টিসেস আছে, এবং আমরা সেগুলিকে 2D স্ক্রিনে প্রজেক্ট করেছি, কিন্তু আমরা যে রঙগুলি ব্যবহার করি সেগুলি সম্পর্কে কী? টেক্সচারিং ও লাইটিংয়ের ব্যাপারে কী বলা যায়? ফ্র্যাগমেন্ট শেডার ঠিক এই কাজটাই করে। ভার্টেক্স শেডারের মতোই ফ্র্যাগমেন্ট শেডারেরও একটি কাজ অবশ্যই করতে হয়: এটি gl_FragColor ভেরিয়েবল সেট বা বাতিল করে, আরেকটি 4D ফ্লোট ভেক্টর, যা আমাদের ফ্র্যাগমেন্টের চূড়ান্ত রঙ।
কিন্তু ফ্র্যাগমেন্ট কী? তিনটি শীর্ষবিন্দুর কথা ভাবো যা একটি ত্রিভুজ তৈরি করে। সেই ত্রিভুজের মধ্যে থাকা প্রতিটি পিক্সেল আঁকতে হবে। ফ্র্যাগমেন্ট হল সেই ডেটা যা ওই তিনটি ভার্টেক্স প্রদান করে যাতে ওই ত্রিভুজের প্রতিটি পিক্সেল আঁকা যায়। এর ফলে ফ্র্যাগমেন্টগুলি তাদের উপাদান ভার্টিসেস থেকে ইন্টারপোলেট করা মান পায়। একটি ভার্টেক্স লাল রঙের হলে এবং তার প্রতিবেশী নীল রঙের হলে আমরা দেখব যে রঙের ভ্যালু লাল থেকে বেগুনি হয়ে নীল রঙে ইন্টারপোলেট হচ্ছে।
৪. শেডার ভেরিয়েবেল
আপনি তিনটি ভেরিয়েবল ডিক্লেয়ারেশন করতে পারেন: ইউনিফর্ম, অ্যাট্রিবিউট এবং ভ্যারিইং। এগুলি বিভ্রান্তিকর হতে পারে, কারণ এগুলি অন্য কোনও সাধারণ ভাষার সাথে মিলে যায় না। আপনি কীভাবে সেগুলি সম্পর্কে ভাবতে পারেন তা এখানে দেওয়া হল:
ইউনিফর্ম ভার্টেক্স শেডার ও ফ্র্যাগমেন্ট শেডার উভয় জায়গাতেই পাঠানো হয় এবং এতে এমন মান থাকে যা রেন্ডার করা সম্পূর্ণ ফ্রেম জুড়ে একই থাকে। এর একটি ভালো উদাহরণ হতে পারে আলোর অবস্থান।
অ্যাট্রিবিউট হল এমন ভ্যালু যা আলাদা আলাদা ভার্টেক্সে প্রয়োগ করা হয়। অ্যাট্রিবিউট শুধুমাত্র ভার্টেক্স শেডারের জন্য উপলভ্য। এটি প্রতিটি শীর্ষবিন্দুর মতো কিছু হতে পারে যার একটি স্বতন্ত্র রঙ আছে। অ্যাট্রিবিউটের সাথে ভার্টিসেসের ওয়ান-টু-ওয়ান সম্পর্ক আছে।
Varying হল ভার্টেক্স শেডারে ঘোষিত ভেরিয়েবল যা আমরা ফ্র্যাগমেন্ট শেডারের সাথে শেয়ার করতে চাই। এটি করার জন্য আমরা নিশ্চিত করি যে আমরা ভার্টেক্স শেডার এবং ফ্র্যাগমেন্ট শেডার উভয় ক্ষেত্রেই একই ধরন এবং নামের একটি পরিবর্তনশীল ভেরিয়েবল ঘোষণা করেছি। এটির একটি ক্লাসিক ব্যবহার হবে ভার্টেক্সের নরমাল, কারণ এটি আলোর হিসেব করার জন্য ব্যবহার করা যেতে পারে।
পরে, আমরা তিনটি ধরনই ব্যবহার করি যাতে আপনি বুঝতে পারেন যে সেগুলি কীভাবে প্রয়োগ করা হয়।
না, আপনি তৈরি করতে পারেন এমন একটি সহজ শেডার দেখুন।
৫. Bonjourno World
তাহলে, এখানে ভার্টেক্স শেডারের হ্যালো ওয়ার্ল্ড দেওয়া হল:
/**
* Multiply each vertex by the model-view matrix
* and the projection matrix (both provided by
* Three.js) to get a final vertex position
*/
void main() {
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position,1.0);
}
এবং ফ্র্যাগমেন্ট শেডারের জন্য একই জিনিস এখানে দেওয়া হল:
/**
* Set the color to a lovely pink.
* Note that the color is a 4D Float
* Vector, R,G,B and A and each part
* runs from 0.0 to 1.0
*/
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}
তবে খুব একটা জটিল নয়, তাই না?
ভার্টেক্স শেডারে Three.js-এর মাধ্যমে আমাদের কাছে কয়েকটি ইউনিফর্ম পাঠানো হয়। এই দুটি ইউনিফর্ম হল 4D ম্যাট্রিক্স, যেগুলিকে মডেল-ভিউ ম্যাট্রিক্স এবং প্রোজেকশন ম্যাট্রিক্স বলা হয়। এগুলি কীভাবে কাজ করে তা আপনাকে সঠিকভাবে জানতে হবে না, যদিও আপনি যদি পারেন তবে জিনিসগুলি কীভাবে কাজ করে তা বোঝা সর্বদা সেরা। সংক্ষেপে বলতে গেলে, ভার্টেক্সের 3D পজিশন কীভাবে স্ক্রিনে চূড়ান্ত 2D পজিশনে প্রজেক্ট করা হয়, তা এগুলি থেকে বোঝা যায়।
আমি আসলে সেগুলিকে উদাহরণ স্নিপেট থেকে বাদ দিয়েছি কারণ Three.js সেগুলিকে আপনার শেডার কোডের একেবারে উপরে যোগ করে দেয়, তাই আপনাকে সেটি করার ব্যাপারে চিন্তা করতে হবে না। সত্যি বলতে, এটি আসলে তার চেয়ে অনেক বেশি যোগ করে, যেমন লাইট ডেটা, ভার্টেক্স কালার এবং ভার্টেক্স নরমাল। আপনি যদি Three.js ছাড়া এটি করতেন, তাহলে আপনাকে নিজে থেকেই এই সমস্ত ইউনিফর্ম এবং অ্যাট্রিবিউট তৈরি ও সেট করতে হতো। সত্যি ঘটনা।
৬. MeshShaderMaterial ব্যবহার করা
ঠিক আছে, তাহলে আমাদের কাছে একটি শেডার সেট-আপ আছে, কিন্তু আমরা কীভাবে এটি Three.js-এর সাথে ব্যবহার করব?
/**
* Assume we have jQuery to hand and pull out
* from the DOM the two snippets of text for
* each of our shaders
*/
var shaderMaterial = new THREE.MeshShaderMaterial({
vertexShader: $('vertexshader').text(),
fragmentShader: $('fragmentshader').text()
});
সেখান থেকে Three.js আপনার শেডার কম্পাইল ও রান করবে যা সেই মেশের সাথে অ্যাটাচ করা আছে যেটিতে আপনি সেই মেটেরিয়াল দিয়েছেন। সত্যিই, এর থেকে সহজ আর কিছু হয় না। হ্যাঁ, তা তো বটেই, তবে আমরা আপনার ব্রাউজারে 3D রান করার কথা বলছি, তাই আমি মনে করি আপনি কিছুটা জটিলতা আশা করেন।
আমরা আসলে আমাদের MeshShaderMaterial-এ আরও দুটি প্রপার্টি যোগ করতে পারি: ইউনিফর্ম এবং অ্যাট্রিবিউট। এরা দুজনেই ভেক্টর, ইন্টিজার বা ফ্লোট নিতে পারে, তবে আমি আগেই বলেছি, ইউনিফর্ম পুরো ফ্রেম বা সব ভার্টিসেসের জন্য একই থাকে, তাই এগুলি সাধারণত একটিই ভ্যালু হয়। তবে, অ্যাট্রিবিউট হল ভার্টেক্স-প্রতি ভেরিয়েবল, তাই সেগুলি অ্যারে হিসেবে প্রত্যাশিত। অ্যাট্রিবিউট অ্যারেতে থাকা ভ্যালুর সংখ্যা এবং মেশে থাকা ভার্টিসেসের সংখ্যার মধ্যে এক-থেকে-এক সম্পর্ক থাকতে হবে।
৭. পরবর্তী ধাপ
এখন আমরা অ্যানিমেশন লুপ, ভার্টেক্স অ্যাট্রিবিউট ও ইউনিফর্ম যোগ করার জন্য কিছুটা সময় ব্যয় করব। আমরা একটি পরিবর্তনশীল ভেরিয়েবলও যোগ করব যাতে ভার্টেক্স শেডার ফ্র্যাগমেন্ট শেডারে কিছু ডেটা পাঠাতে পারে। চূড়ান্ত ফলাফল হল, আমাদের গোলাপি রঙের স্ফিয়ারটি উপর থেকে এবং পাশে আলোয় আলোকিত হয়ে স্পন্দিত হতে থাকবে। এটা একটু জটিল, তবে আশা করি এর মাধ্যমে আপনি তিনটি ভেরিয়েবেলের ধরন এবং সেগুলি কীভাবে একে অপরের সাথে এবং অন্তর্নিহিত জ্যামিতির সাথে সম্পর্কিত তা ভালোভাবে বুঝতে পারবেন।
৮. হালকা রঙের নকল
এখন, রঙ আপডেট করুন যাতে এটি ফ্ল্যাট রঙের অবজেক্ট না হয়। Three.js কীভাবে লাইটিং হ্যান্ডেল করে তা আমরা দেখতে পারি, তবে আমি নিশ্চিত যে আপনি বুঝতে পারছেন এটি আমাদের প্রয়োজনের চেয়ে বেশি জটিল, তাই আমরা এটি নকল করতে যাচ্ছি। Three.js-এর অংশ হিসেবে দুর্দান্ত শেডার এবং ক্রিস মিল্ক ও Google-এর তৈরি অসাধারণ WebGL প্রোজেক্ট Rome-এর শেডার আপনার অবশ্যই দেখা উচিত। আমাদের শেডারে ফিরে আসা যাক। ফ্র্যাগমেন্ট শেডারে প্রতিটি ভার্টেক্স নরমাল প্রদান করার জন্য আমরা ভার্টেক্স শেডার আপডেট করব। আমরা এটি বিভিন্নভাবে করি:
// create a shared variable for the
// VS and FS containing the normal
varying vec3 vNormal;
void main() {
// set the vNormal value with
// the attribute value passed
// in by Three.js
vNormal = normal;
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position,1.0);
}
এবং ফ্র্যাগমেন্ট শেডারে আমরা একই ভেরিয়েবলের নাম সেট আপ করব এবং তারপর ভার্টেক্স নরমালের ডট প্রোডাক্ট এমন একটি ভেক্টরের সাথে ব্যবহার করব যা গোলকের উপরে এবং ডানদিকে জ্বলজ্বল করা আলো দেখায়। এর ফলে আমরা এমন একটি এফেক্ট পাই যা 3D প্যাকেজে ডিরেকশনাল লাইটের মতো।
// same name and type as VS
varying vec3 vNormal;
void main() {
// calc the dot product and clamp
// 0 -> 1 rather than -1 -> 1
vec3 light = vec3(0.5,0.2,1.0);
// ensure it's normalized
light = normalize(light);
// calculate the dot product of
// the light to the vertex normal
float dProd = max(0.0, dot(vNormal, light));
// feed into our frag color
gl_FragColor = vec4(dProd, dProd, dProd, 1.0);
}
ডট প্রোডাক্ট কাজ করার কারণ হল, দুটি ভেক্টর দেওয়া হলে এটি এমন একটি সংখ্যা প্রদান করে যা আপনাকে বলে যে দুটি ভেক্টর কতটা 'একই রকম'। নর্মালাইজড ভেক্টরের ক্ষেত্রে, সেগুলি যদি ঠিক একই দিকে নির্দেশ করে, তাহলে আপনি ১ ভ্যালু পাবেন। যদি তারা বিপরীত দিকে নির্দেশ করে তবে আপনি -1 পাবেন। আমরা সেই সংখ্যাটি নিয়ে আমাদের লাইটিংয়ে প্রয়োগ করি। তাই, উপরের ডানদিকের ভার্টেক্সের ভ্যালু ১ (সম্পূর্ণ আলোকিত) বা তার কাছাকাছি হবে, অন্যদিকে পাশের ভার্টেক্সের ভ্যালু ০-এর কাছাকাছি এবং পিছনের ভার্টেক্সের ভ্যালু -১ হবে। আমরা নেগেটিভ ভ্যালুর জন্য ভ্যালু ০-তে সেট করি, কিন্তু আপনি যখন সংখ্যাগুলি যোগ করেন তখন আপনি আমাদের দেখানো বেসিক লাইটিং পান।
এরপরে কী? তাহলে, কিছু ভার্টেক্স পজিশন নিয়ে ঘাটাঘাটি করে দেখা যাক।
৯. অ্যাট্রিবিউট
আমি এখন যা করতে চাই তা হল একটি অ্যাট্রিবিউট ব্যবহার করে প্রতিটি ভার্টেক্সে একটি র্যান্ডম নম্বর অ্যাটাচ করা। আমরা এই নম্বরটি ব্যবহার করে ভার্টেক্সকে তার স্বাভাবিক অবস্থান থেকে সরিয়ে দেব। এর ফলে, আপনি এক ধরনের অদ্ভুত স্পাইক বল দেখতে পাবেন যা আপনি যতবার পৃষ্ঠা রিফ্রেশ করবেন ততবার পরিবর্তিত হবে। এটি এখনই অ্যানিমেটেড হবে না (সেটি পরে হবে) তবে পৃষ্ঠাটি কয়েকবার রিফ্রেশ করলে দেখতে পাবেন যে এটি র্যান্ডমাইজ করা হয়েছে।
ভার্টেক্স শেডারে অ্যাট্রিবিউট যোগ করে শুরু করুন:
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// push the displacement into the three
// slots of a 3D vector so it can be
// used in operations with other 3D
// vectors like positions and normals
vec3 newPosition = position +
normal *
vec3(displacement);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition,1.0);
}
এটি দেখতে কেমন?
আসলে খুব একটা আলাদা নয়! এর কারণ হল MeshShaderMaterial-এ অ্যাট্রিবিউট সেট-আপ করা হয়নি, তাই কার্যকরভাবে শেডার পরিবর্তে শূন্য ভ্যালু ব্যবহার করে। এটি এখন এক ধরনের প্লেসহোল্ডার। এক সেকেন্ডের মধ্যে আমরা JavaScript-এ MeshShaderMaterial-এ অ্যাট্রিবিউট যোগ করব এবং Three.js অটোমেটিক আমাদের জন্য দুটিকে একসাথে বেঁধে দেবে।
এছাড়াও, মনে রাখবেন যে আমাকে আপডেট করা পজিশনটি একটি নতুন vec3 ভেরিয়েবলে অ্যাসাইন করতে হয়েছে, কারণ আসল অ্যাট্রিবিউটটি, সব অ্যাট্রিবিউটের মতোই, শুধুমাত্র পড়ার জন্য।
১০. MeshShaderMaterial আপডেট করা হচ্ছে
আমাদের ডিসপ্লেসমেন্টকে শক্তিশালী করার জন্য প্রয়োজনীয় অ্যাট্রিবিউট সহ আমাদের MeshShaderMaterial আপডেট করার জন্য সরাসরি ঝাঁপিয়ে পড়ুন। মনে রাখবেন: অ্যাট্রিবিউট হল প্রতি-ভার্টেক্স ভ্যালু, তাই আমাদের স্ফিয়ারে প্রতি ভার্টেক্সে একটি ভ্যালু প্রয়োজন। এইভাবে সেটি করুন:
var attributes = {
displacement: {
type: 'f', // a float
value: [] // an empty array
}
};
// create the material and now
// include the attributes property
var shaderMaterial = new THREE.MeshShaderMaterial({
attributes: attributes,
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});
// now populate the array of attributes
var vertices = sphere.geometry.vertices;
var values = attributes.displacement.value
for(var v = 0; v < vertices.length; v++) {
values.push(Math.random() * 30);
}
এখন আমরা একটি বিকৃত গোলক দেখতে পাচ্ছি, তবে মজার ব্যাপার হল যে সমস্ত ডিসপ্লেসমেন্ট GPU-তে ঘটছে।
১১. এটি অ্যানিমেট করুন
আমাদের অবশ্যই এটি অ্যানিমেট করা উচিত। আমরা কীভাবে এটি করি? তাহলে, দুটি জিনিস আমাদের ঠিক করতে হবে:
- প্রতিটি ফ্রেমে কতটা ডিসপ্লেসমেন্ট প্রয়োগ করা উচিত তা অ্যানিমেট করার জন্য একটি ইউনিফর্ম। আমরা এর জন্য সাইন বা কোসাইন ব্যবহার করতে পারি কারণ সেগুলি -১ থেকে ১ পর্যন্ত চলে
- JS-এ অ্যানিমেশন লুপ
আমরা MeshShaderMaterial এবং Vertex Shader, উভয় ক্ষেত্রেই ইউনিফর্ম যোগ করতে চলেছি। প্রথমে ভার্টেক্স শেডার:
uniform float amplitude;
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// multiply our displacement by the
// amplitude. The amp will get animated
// so we'll have animated displacement
vec3 newPosition = position +
normal *
vec3(displacement *
amplitude);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition,1.0);
}
এরপর আমরা MeshShaderMaterial আপডেট করি:
// add a uniform for the amplitude
var uniforms = {
amplitude: {
type: 'f', // a float
value: 0
}
};
// create the final material
var shaderMaterial = new THREE.MeshShaderMaterial({
uniforms: uniforms,
attributes: attributes,
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});
আমাদের শেডার এখনকার মতো হয়ে গেছে। কিন্তু এখন মনে হচ্ছে আমরা এক ধাপ পিছিয়ে গেছি। এর মূল কারণ হল আমাদের অ্যামপ্লিটিউড ভ্যালু হল ০ এবং যেহেতু আমরা এটিকে ডিসপ্লেসমেন্টের সাথে গুণ করি, তাই আমরা কোনও পরিবর্তন দেখতে পাচ্ছি না। আমরা অ্যানিমেশন লুপও সেট-আপ করিনি, তাই ০-এর কোনও পরিবর্তন আমরা দেখতে পাই না।
আমাদের জাভাস্ক্রিপ্টে এখন আমাদের রেন্ডার কলকে একটি ফাংশনে মোড়ানো দরকার এবং তারপর এটি কল করতে requestAnimationFrame ব্যবহার করুন। সেখানে আমাদের ইউনিফর্মের মূল্যও আপডেট করতে হবে।
var frame = 0;
function update() {
// update the amplitude based on
// the frame value
uniforms.amplitude.value = Math.sin(frame);
frame += 0.1;
renderer.render(scene, camera);
// set up the next call
requestAnimFrame(update);
}
requestAnimFrame(update);
উপসংহার
আর কিছু করতে হবে না! আপনি এখন দেখতে পাবেন যে এটি একটি অদ্ভুত (এবং কিছুটা ট্রিপি) স্পন্দিত পদ্ধতিতে অ্যানিমেট করছে।
শেডার্স সম্পর্কে আরও অনেক কিছু বলা যায়, তবে আশা করি এই ভূমিকা থেকে আপনি কিছুটা ধারণা পেয়েছেন। এখন আপনি শেডার দেখলে বুঝতে পারবেন এবং নিজের থেকে কিছু অসাধারণ শেডার তৈরি করার আত্মবিশ্বাস পাবেন।