Hey there,
I need to shade a lot of different wheels with different track profiles. I'd like to differentiate between wheel tread and the sides. In Redshift I used a triplanar projection node, since I could map different textures to the three different dimensions. As I see it, in arnold triplanar you can have only one map. So I tried to map R, G and B of the utility shader with "Ns". But this is in world space, not in object space. So when I rotate the wheels in space those maps change.
So:
A: Is there a way to have 3 different maps in the triplanar projection node?
Or:
B: Can I get normal shading in object space?
Thank you very much!
It's possible, I can update my OSL triplanar map to accept 6 inputs, 3 inputs.
But I would need a good reason to do it.
You can also do these things with Core Arnold maps, you dont need the triplanar map, instead build up 3 paths with 3 UV Transforms and 3 Normal directions.
Po ( Point Object ) needed if you dont want drifting.
Dear Mads,
sorry. I'm not sure I can follow... I don't know your OSL shader. And I don't know what you mean by "a good reason". Anyway... The solution with 3 normal directions is what I tried this afternoon. My problem was "So when I rotate the wheels in space those maps change.". thats what you call "drifting", if I understand you correctly. I cannot see how I can use "Po" to counteract that drifting.
Thank you very much...
I could write an update to my OSL Triplanar here, if you wanted to cut a small gem and put it in my purse.
https://github.com/gkmotu/OSL-Shaders/blob/master/Triplanar.osl
If you want to do this with pure arnold nodes, I build a quick logic you need to follow.
Here I show the top down projection of an input, a texture.
It follows, and does not drift since I probe Po instead of worlds space P.
I intentionally dehooked the -N stream so its only top down here, not both topdown and down top, to get both directions covered on the sphere.
Here is the noodle I threw together and what you will need to build your own triplanar logic with core nodes.
Notice we negate the Normal to -N for the backface logic to patch up the other side.
You will need to generate the same for the 2 other axis.
Note you can also just multiply the normal mask with the RGB(A) data to mask each of the 3, dual sided normals.
Update:
Use this instead...this works 100% like you want.
shader Normal [[ string help = "Get the Normal in the specified coordinate space", string label = "Normal", string category = "Scene Attributes" ]] ( string Coordspace = "object" [[ string widget="popup", string help = "world, object, camera, shader, screen, NDC, raster, or an explicitly named coordinate system", string options="world|object|camera|shader|screen|NDC|raster", int editable=1 ]], output vector Out = 0.0, output float tt = 0, ) { Out = transform(Coordspace, N); tt = Out[2]; }
Dear @Mads Drøschler,
I hacked your triplanar.osl (*duck and run*)
I do not really know C++ (only Java, JS, Python and a little bit of C#) and OSL is completely new to me.
So I think this would be very unstable since there is no failsafe for missing textures:
// Triplanar mapping // Triplanar.osl, by Mads Drøschler // Modified: 02-28-2021 // License: MIT Copyright Mads Drøschler // ID and random generator float ID(int InstanceMode, int RandFlip) { float id; string ID = "theThing(tm)"; getattribute("nodeName", ID); int Hash = hash(ID); InstanceMode < 1 ? Hash = 0 : Hash = Hash; return id = noise("cell", vector(abs(Hash), abs(RandFlip), 11)); } // Texture color Texture (string Texture, float x, float y) { return texture(Texture,x,1-y,"wrap", "periodic"); } // Blend float Blend (float blur, color BlendMap) { return mix(1,BlendMap[0],0.5)/(blur/10); } shader Triplanar [[ string help = "<h3>Triplanar</h3>" "Map surfaces automatically<br>" ]] ( // Texture loader string Tex1 = "" [[ string widget = "filename", string widget = "null", int connectable = 0, string help = "Load a texture to the Triplanar map." ]], string Tex2 = "" [[ string widget = "filename", string widget = "null", int connectable = 0, string help = "Load a texture to the Triplanar map." ]], string Tex3 = "" [[ string widget = "filename", string widget = "null", int connectable = 0, string help = "Load a texture to the Triplanar map." ]], // Blend map color BlendMap = 1 [[ string widget = "null", string help ="Pick a map to control the blend area. ( Use an OSL noise for example. ). This can help breaking up " ]], //Debug string UVC = "uvwunwrap\uv_checker.png" [[ string widget = "filename", string widget = "null", int connectable = 0, ]], int RandVal = 0 [[ string widget = "null", int connectable = 0, string help ="Enable to randomize the transforms on the object." ]], int RandFlip = 12345 [[ string widget = "null", int connectable = 0, string help ="Random seed for the transform values." ]], string Override = "Non" [[ string widget = "null", int connectable = 0, string widget ="popup", string options = "Non|UVMap|RGB", string help ="Pick an override mode for debug purposes." ]], float blur = 1.0 [[ string widget = "null", int connectable = 0, float min = 0, float max = 2, string help ="Width of the blend area between the XYZ projections." ]], int InstanceMode = 0 [[ string widget = "null", int connectable = 0, string help ="Enable pr Object/Instance random transforms." ]], // Scale int scaleUniform =1 [[ string widget = "null", int connectable = 0, string help ="Scale the XYZ projections uniformly." ]], float scaleU = 100 [[ int connectable = 0, ]], float scaleV = 100 [[ int connectable = 0, ]], float scaleU_max = 0 [[ string widget = "null", int connectable = 0, ]], float scaleV_max = 0 [[ string widget = "null", int connectable = 0, ]], // Position int PosUniform =1 [[ string widget = "null", int connectable = 0, string help ="Offset the XYZ projections uniformly." ]], float PosU = 0 [[ string widget = "null", int connectable = 0 ]], float PosV = 0 [[ string widget = "null", int connectable = 0 ]], float PosU_max = 0 [[ string widget = "null", int connectable = 0 ]], float PosV_max = 0 [[ string widget = "null", int connectable = 0 ]], // Rotation int RotUniform =1 [[ string widget = "null", int connectable = 0, string help = "Rotate the XYZ projections uniformly." ]], float Rotx = 0 [[ string widget = "null", int connectable = 0 ]], float Roty = 0 [[ string widget = "null", int connectable = 0 ]], float Rotz = 0 [[ string widget = "null", int connectable = 0 ]], float Rotx_max = 0 [[ string widget = "null", int connectable = 0 ]], float Roty_max = 0 [[ string widget = "null", int connectable = 0 ]], float Rotz_max = 0 [[ string widget = "null", int connectable = 0 ]], // General inits float Gamma = 2.2 [[ string widget = "null", int connectable = 0, string help = "Set the texture gamma, default 2.2." ]], output color ColRGB = 0 [[ string label = "Col(RGB)", string help ="Output of the Triplanar map." ]], ) { // Skip empty files if (Tex1 == "") return; // Various inits point uv = transform("object",P); color BitX,BitY,BitZ; point uvx, uvy, uvz; vector x = vector(1,0,0); vector y = vector(0,1,0); vector z = vector(0,0,1); // Blend area float Blender = Blend(blur+0.07,BlendMap); // Normal vector Nor = pow(abs(transform("object",N)),Blender); Nor = Nor/(Nor[0]+Nor[1]+Nor[2]); // ID and Random generator float ratio = ID(InstanceMode,RandFlip); // Random Rotation float RandX = mix(Rotx, Rotx_max, ratio); RandVal < 1 ? RandX = 0 : RandX = RandX; float RandY = mix(Roty, Roty_max, ratio); RandVal < 1 ? RandY = 0 : RandY = RandY; float RandZ = mix(Rotz, Rotz_max, ratio); RandVal < 1 ? RandZ = 0 : RandZ = RandZ; // Rotation if ( RotUniform == 1) { uvx = rotate(uv, radians(Rotz-90+RandZ),0,x); uvy = rotate(uv, radians(90+-Rotz+RandZ),0,y); uvz = rotate(uv, radians(Rotz+RandZ),0,z); } else { uvx = rotate(uv, radians(Rotx-90+RandX),0,x); uvy = rotate(uv, radians(90+-Roty+RandY),0,y); uvz = rotate(uv, radians(Rotz+RandZ),0,z); } // Random Position float RandX_pos = mix(PosU, PosU_max, ratio); RandVal < 1 ? RandX_pos = 0 : RandX_pos = RandX_pos; float RandY_pos = mix(PosV, PosV_max, ratio); RandVal < 1 ? RandY_pos = 0 : RandY_pos = RandY_pos; // Position if (PosUniform == 0 ) { uvx[2] +=PosU+0.5+RandX_pos; uvx[1] -=PosV+0.5+RandY_pos; uvy[0] -=PosV+0.5+RandY_pos; uvy[2] +=PosU+0.5+RandX_pos; uvz[0] -=PosU+0.5+RandX_pos; uvz[1] -=PosV+0.5+RandY_pos; } else { uvx[2] +=PosU+0.5+RandX_pos; uvx[1] -=PosU+0.5+RandX_pos; uvy[0] -=PosU+0.5+RandX_pos; uvy[2] +=PosU+0.5+RandX_pos; uvz[0] -=PosU+0.5+RandX_pos; uvz[1] -=PosU+0.5+RandX_pos; } // Random scale float RandX_scale = mix(scaleU, scaleU_max, ratio); RandVal < 1 ? RandX_scale = 0 : RandX_scale = RandX_scale; float RandY_scale = mix(scaleV, scaleV_max, ratio); RandVal < 1 ? RandY_scale = 0 : RandY_scale = RandY_scale; float RandZ_scale = mix(scaleU, scaleU_max, ratio); RandVal < 1 ? RandZ_scale = 0 : RandZ_scale = RandZ_scale; // Scale if ( scaleUniform == 0 ) { uvx[2] /= scaleU; uvx[1] /= scaleV; uvy[2] /= scaleU; uvy[0] /= scaleV; uvz[0] /= scaleU; uvz[1] /= scaleV; uvx += RandX_scale; uvy += RandY_scale; uvz += RandZ_scale; } else { uvx /= scaleU+RandZ_scale; uvy /= scaleU+RandZ_scale; uvz /= scaleU+RandZ_scale; } Override == "UVMap" ? BitX = Texture(UVC,uvx[2],uvx[1]) : BitX = Texture(Tex1,uvx[2],uvx[1]); Override == "UVMap" ? BitY = Texture(UVC,uvy[2],uvy[0]) : BitY = Texture(Tex2,uvy[2],uvy[0]); Override == "UVMap" ? BitZ = Texture(UVC,uvz[0],uvz[1]) : BitZ = Texture(Tex3,uvz[0],uvz[1]); // Map to projections BitX *=Nor[0]; BitY *=Nor[1]; BitZ *=Nor[2]; // Collect ColRGB = pow(BitX+BitY+BitZ,Gamma); // RGB Debug Override == "RGB" ? ColRGB = Nor[0]*x + Nor[1]*y + Nor[2]*z :0; }
Seems to be working for me so far...
What would be the appropriate input type in OSL if I wanted to plug in an other arnold node (like ramp or noise) instead of loading a texture?
Thank you so much!
You cannot push in data and post evaluate the UVW.
So while you can open raw color channels, you wont be able to post align them, it has to occur at the source.
This means your ramp or whatever must come with the correct orientation pr call.
So you need to bake out the input. Render to texture.
Alternatively, forward both a color and a UVW to the triplanar map instead of just the color.
I guess you are referring to this:
"What would be the appropriate input type in OSL if I wanted to plug in an other arnold node (like ramp or noise) instead of loading a texture? "
I will read more into OSL writing in order to fully understand your comment.
Thx...
Correct.
It's impossible to reproject a noise for example.
But a noise is typically living in worldspace, so it would always map correctly regardless of being parsed through the 3 normal filters.
But, converting a 2D procedural is not possible, it must live as a field expression. ( and then it gives little meaning to parse through a triplanar if its living in world space )
I understand (I think). I was only asking, because all I needed was black and white for the different planes. I quickly created a black and a white png for that. As I said: thats working for me now. But OSL is something I will dive into some more when there is time...
You should, it's quite an awesome TD tool + things that generate eye candy.
You can join my Facebook group for OSL.
2,600 people.
Larry is there, a handful of Autodesk personel, lots of highend TDs.
https://www.facebook.com/groups/OSL.Shaders