Skip to content

Commit

Permalink
Support "vertex" UV coordinates on curves in the render delegate (#1578)
Browse files Browse the repository at this point in the history
* Support "vertex" UV coordinates on curves in the render delegate #1435

* Improvements in the curves UVs #1435

* Ensure curves without width defined render with a default radius

* Add changelog
  • Loading branch information
sebastienblor committed Jul 3, 2023
1 parent 53fad8e commit 2d0479b
Show file tree
Hide file tree
Showing 8 changed files with 481 additions and 55 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Feature
- [usd#1435](https://github.com/Autodesk/arnold-usd/issues/1435) - Support "vertex" UV coordinates on Curves in the render delegate
- [usd#1579](https://github.com/Autodesk/arnold-usd/issues/1579) - Curves without any width should render with a default value

### Bug fixes
- [usd#1567](https://github.com/Autodesk/arnold-usd/issues/1567) - Fix metallic attribute in UsdPreviewSurface in the render delegate
- [usd#1550](https://github.com/Autodesk/arnold-usd/issues/1550) - UsdPrimvarReader_float2 returning "st" not working in the usd procedural
Expand Down
57 changes: 28 additions & 29 deletions libs/render_delegate/basis_curves.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,33 @@ void HdArnoldBasisCurves::Sync(
} else {
ArnoldUsdCurvesData::SetRadiusFromValue(GetArnoldNode(), desc.value);
}
// For constant and
} else if (desc.interpolation == HdInterpolationConstant) {
continue;
}
if (primvar.first == str::t_uv || primvar.first == str::t_st) {
// This is either a VtVec2fArray or VtVec3fArray (in Solaris).
if (desc.value.IsHolding<VtVec2fArray>()) {
const auto& v = desc.value.UncheckedGet<VtVec2fArray>();
AiNodeSetArray(
GetArnoldNode(), str::uvs, AiArrayConvert(v.size(), 1, AI_TYPE_VECTOR2, v.data()));
continue;
}
if (desc.value.IsHolding<VtVec3fArray>()) {
const auto& v = desc.value.UncheckedGet<VtVec3fArray>();
auto* arr = AiArrayAllocate(v.size(), 1, AI_TYPE_VECTOR2);
if (!v.empty()) {
std::transform(
v.begin(), v.end(), static_cast<GfVec2f*>(AiArrayMap(arr)),
[](const GfVec3f& in) -> GfVec2f {
return {in[0], in[1]};
});
AiArrayUnmap(arr);
}
AiNodeSetArray(GetArnoldNode(), str::uvs, arr);
continue;
}
}

if (desc.interpolation == HdInterpolationConstant) {
// We skip reading the basis for now as it would require remapping the vertices, widths and
// all the primvars.
if (primvar.first != _tokens->basis) {
Expand All @@ -191,31 +216,7 @@ void HdArnoldBasisCurves::Sync(
nullptr);
}
} else if (desc.interpolation == HdInterpolationUniform) {
if (primvar.first == str::t_uv || primvar.first == str::t_st) {
// This is either a VtVec2fArray or VtVec3fArray (in Solaris).
if (desc.value.IsHolding<VtVec2fArray>()) {
const auto& v = desc.value.UncheckedGet<VtVec2fArray>();
AiNodeSetArray(
GetArnoldNode(), str::uvs, AiArrayConvert(v.size(), 1, AI_TYPE_VECTOR2, v.data()));
} else if (desc.value.IsHolding<VtVec3fArray>()) {
const auto& v = desc.value.UncheckedGet<VtVec3fArray>();
auto* arr = AiArrayAllocate(v.size(), 1, AI_TYPE_VECTOR2);
if (!v.empty()) {
std::transform(
v.begin(), v.end(), static_cast<GfVec2f*>(AiArrayMap(arr)),
[](const GfVec3f& in) -> GfVec2f {
return {in[0], in[1]};
});
AiArrayUnmap(arr);
}
AiNodeSetArray(GetArnoldNode(), str::uvs, arr);
} else {
// If it's an unsupported type, just set it as user data.
HdArnoldSetUniformPrimvar(GetArnoldNode(), primvar.first, desc.role, desc.value);
}
} else {
HdArnoldSetUniformPrimvar(GetArnoldNode(), primvar.first, desc.role, desc.value);
}
HdArnoldSetUniformPrimvar(GetArnoldNode(), primvar.first, desc.role, desc.value);
} else if (desc.interpolation == HdInterpolationVertex || desc.interpolation == HdInterpolationVarying) {
if (primvar.first == HdTokens->points) {
HdArnoldSetPositionFromValue(GetArnoldNode(), str::curves, desc.value);
Expand All @@ -233,8 +234,6 @@ void HdArnoldBasisCurves::Sync(
}
HdArnoldSetVertexPrimvar(GetArnoldNode(), primvar.first, desc.role, value);
}
} else if (desc.interpolation == HdInterpolationVarying) {
HdArnoldSetVertexPrimvar(GetArnoldNode(), primvar.first, desc.role, desc.value);
}
}
UpdateVisibilityAndSidedness();
Expand Down
15 changes: 7 additions & 8 deletions libs/render_delegate/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1729,17 +1729,16 @@ AtArray* HdArnoldGetShidxs(const HdGeomSubsets& subsets, int numFaces, HdArnoldS

bool HdArnoldDeclare(AtNode* node, const TfToken& name, const TfToken& scope, const TfToken& type)
{
if (AiNodeEntryLookUpParameter(AiNodeGetNodeEntry(node), AtString(name.GetText())) != nullptr) {
TF_DEBUG(HDARNOLD_PRIMVARS)
.Msg(
"Unable to translate %s primvar for %s due to a name collision with a built-in parameter",
name.GetText(), AiNodeGetName(node));
return false;
}
const AtString nameStr{name.GetText()};
if (AiNodeLookUpUserParameter(node, nameStr) != nullptr) {
// If the attribute already exists (either as a node entry parameter
// or as a user data in the node), then we should not call AiNodeDeclare
// as it would fail.
if (AiNodeLookUpUserParameter(node, nameStr) ||
AiNodeEntryLookUpParameter(AiNodeGetNodeEntry(node), nameStr)) {
AiNodeResetParameter(node, nameStr);
return true;
}

return AiNodeDeclare(node, nameStr, AtString(TfStringPrintf("%s %s", scope.GetText(), type.GetText()).c_str()));
}

Expand Down
34 changes: 16 additions & 18 deletions libs/translator/reader/read_geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,13 +480,7 @@ void UsdArnoldReadCurves::Read(const UsdPrim &prim, UsdArnoldReaderContext &cont
staticTime.motionBlur = false;

UsdGeomCurves curves(prim);

VtValue widthValues;
if (!curves.GetWidthsAttr().Get(&widthValues, frame)) {
AiMsgWarning("[usd] Skipping curves with empty width %s", prim.GetPath().GetText());
return;
}


AtNode *node = context.CreateArnoldNode("curves", prim.GetPath().GetText());

AtString basis = str::linear;
Expand Down Expand Up @@ -516,8 +510,6 @@ void UsdArnoldReadCurves::Read(const UsdPrim &prim, UsdArnoldReaderContext &cont
}
}



AiNodeSetStr(node, str::basis, basis);

// CV counts per curve
Expand All @@ -533,16 +525,22 @@ void UsdArnoldReadCurves::Read(const UsdPrim &prim, UsdArnoldReaderContext &cont
const auto vstep = basis == str::bezier ? 3 : 1;
const auto vmin = basis == str::linear ? 2 : 4;
ArnoldUsdCurvesData curvesData(vmin, vstep, vertexCounts);

TfToken widthInterpolation = curves.GetWidthsInterpolation();
if ((widthInterpolation == UsdGeomTokens->vertex || widthInterpolation == UsdGeomTokens->varying) &&
basis != str::linear) {
// if radius data is per-vertex and the curve is pinned, then don't remap
if (!(widthInterpolation == UsdGeomTokens->vertex && isValidPinnedCurve))
curvesData.RemapCurvesVertexPrimvar<float, double>(widthValues);
curvesData.SetRadiusFromValue(node, widthValues);

VtValue widthValues;
if (curves.GetWidthsAttr().Get(&widthValues, frame)) {
TfToken widthInterpolation = curves.GetWidthsInterpolation();
if ((widthInterpolation == UsdGeomTokens->vertex || widthInterpolation == UsdGeomTokens->varying) &&
basis != str::linear) {
// if radius data is per-vertex and the curve is pinned, then don't remap
if (!(widthInterpolation == UsdGeomTokens->vertex && isValidPinnedCurve))
curvesData.RemapCurvesVertexPrimvar<float, double>(widthValues);
curvesData.SetRadiusFromValue(node, widthValues);
} else {
curvesData.SetRadiusFromValue(node, widthValues);
}
} else {
curvesData.SetRadiusFromValue(node, widthValues);
// Width isn't defined, we assume a constant width equal to 1
AiNodeSetFlt(node, str::radius, 0.5);
}

ReadMatrix(prim, node, time, context);
Expand Down
7 changes: 7 additions & 0 deletions testsuite/test_1435/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Curves with per-vertex st coordinates

See #1435

author: sebastien.ortega

PARAMS: {'scene':'test.usda'}
211 changes: 211 additions & 0 deletions testsuite/test_1435/data/test.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#usda 1.0
(
doc = """Generated from Composed Stage of root layer
"""
endTimeCode = 1
framesPerSecond = 24
metersPerUnit = 1
startTimeCode = 1
timeCodesPerSecond = 24
upAxis = "Y"
)

def Xform "test" (
kind = "component"
)
{
def BasisCurves "test" (
apiSchemas = ["MaterialBindingAPI"]
)
{
uniform token basis = "bezier"
int[] curveVertexCounts = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
float3[] extent = [(-5, 0, -5), (5, 1, 3.5779235)]
rel material:binding = </materials/shader>
point3f[] points = [(-0.86734253, 0, -0.9439319), (-0.86734253, 1, -0.9439319), (5, 0, 0.7396351), (5, 1, 0.7396351), (-2.928457, 0, -2.2366061), (-2.928457, 1, -2.2366061), (-0.9475007, 0, -3.191457), (-0.9475007, 1, -3.191457), (2.1079116, 0, 1.7289146), (2.1079116, 1, 1.7289146), (-3.0919335, 0, 0.07936842), (-3.0919335, 1, 0.07936842), (-5, 0, 1.499233), (-5, 1, 1.499233), (1.4457333, 0, -0.7533004), (1.4457333, 1, -0.7533004), (0.2606192, 0, 0.9491923), (0.2606192, 1, 0.9491923), (1.1564449, 0, -2.8530495), (1.1564449, 1, -2.8530495), (5, 0, -2.212023), (5, 1, -2.212023), (-1.3227377, 0, 2.1443152), (-1.3227377, 1, 2.1443152), (-4.9845285, 0, -1.2268404), (-4.9845285, 1, -1.2268404), (-3.2350502, 0, 3.0137196), (-3.2350502, 1, 3.0137196), (3.179194, 0, -1.9110526), (3.179194, 1, -1.9110526), (3.4112368, 0, 0.16663373), (3.4112368, 1, 0.16663373), (-4.0955606, 0, -4.076156), (-4.0955606, 1, -4.076156), (3.3390605, 0, -4.132369), (3.3390605, 1, -4.132369), (3.6978378, 0, 3.1946297), (3.6978378, 1, 3.1946297), (0.96328783, 0, -5), (0.96328783, 1, -5), (-1.6550982, 0, -5), (-1.6550982, 1, -5), (0.8823204, 0, 3.5779235), (0.8823204, 1, 3.5779235)] (
interpolation = "vertex"
)
string primvars:arnold:mode = "thick" (
interpolation = "constant"
)
texCoord2f[] primvars:st = [(0.41326576, 0.4056068), (0.41326576, 0.4056068), (1, 0.5739635), (1, 0.5739635), (0.2071543, 0.27633938), (0.2071543, 0.27633938), (0.40524995, 0.18085429), (0.40524995, 0.18085429), (0.7107911, 0.67289144), (0.7107911, 0.67289144), (0.19080666, 0.50793684), (0.19080666, 0.50793684), (0, 0.6499233), (0, 0.6499233), (0.6445733, 0.42466995), (0.6445733, 0.42466995), (0.5260619, 0.5949192), (0.5260619, 0.5949192), (0.61564445, 0.21469504), (0.61564445, 0.21469504), (1, 0.2787977), (1, 0.2787977), (0.36772624, 0.7144315), (0.36772624, 0.7144315), (0.0015471459, 0.37731594), (0.0015471459, 0.37731594), (0.17649497, 0.801372), (0.17649497, 0.801372), (0.81791943, 0.30889475), (0.81791943, 0.30889475), (0.84112364, 0.5166634), (0.84112364, 0.5166634), (0.09044395, 0.09238439), (0.09044395, 0.09238439), (0.83390605, 0.0867631), (0.83390605, 0.0867631), (0.8697838, 0.819463), (0.8697838, 0.819463), (0.5963288, 0), (0.5963288, 0), (0.33449015, 0), (0.33449015, 0), (0.58823204, 0.8577924), (0.58823204, 0.8577924)] (
interpolation = "vertex"
)
int[] primvars:st:indices = None
uniform token type = "linear"
uniform token wrap = "nonperiodic"
}
}

def Scope "materials"
{
def Material "shader"
{
token outputs:arnold:surface.connect = </materials/shader/standard_surface1.outputs:shader>

def Shader "standard_surface1"
{
uniform token info:id = "arnold:standard_surface"
string inputs:aov_id1 = ""
string inputs:aov_id2 = ""
string inputs:aov_id3 = ""
string inputs:aov_id4 = ""
string inputs:aov_id5 = ""
string inputs:aov_id6 = ""
string inputs:aov_id7 = ""
string inputs:aov_id8 = ""
float inputs:base = 1
color3f inputs:base_color = (0.8, 0.8, 0.8)
color3f inputs:base_color.connect = </materials/shader/checkerboard_good.outputs:rgb>
bool inputs:caustics = 0
float inputs:coat = 0
float inputs:coat_affect_color = 0
float inputs:coat_affect_roughness = 0
float inputs:coat_anisotropy = 0
color3f inputs:coat_color = (1, 1, 1)
float inputs:coat_IOR = 1.5
vector3f inputs:coat_normal = (0, 0, 0)
float inputs:coat_rotation = 0
float inputs:coat_roughness = 0.1
int inputs:dielectric_priority = 0
float inputs:diffuse_roughness = 0
float inputs:emission = 0
color3f inputs:emission_color = (1, 1, 1)
bool inputs:exit_to_background = 0
color3f inputs:id1 = (0, 0, 0)
color3f inputs:id2 = (0, 0, 0)
color3f inputs:id3 = (0, 0, 0)
color3f inputs:id4 = (0, 0, 0)
color3f inputs:id5 = (0, 0, 0)
color3f inputs:id6 = (0, 0, 0)
color3f inputs:id7 = (0, 0, 0)
color3f inputs:id8 = (0, 0, 0)
float inputs:indirect_diffuse = 1
float inputs:indirect_specular = 1
bool inputs:internal_reflections = 1
float inputs:metalness = 0
vector3f inputs:normal = (0, 0, 0)
color3f inputs:opacity = (1, 1, 1)
float inputs:sheen = 0
color3f inputs:sheen_color = (1, 1, 1)
float inputs:sheen_roughness = 0.3
float inputs:specular = 1
float inputs:specular_anisotropy = 0
color3f inputs:specular_color = (1, 1, 1)
float inputs:specular_IOR = 1.5
float inputs:specular_rotation = 0
float inputs:specular_roughness = 0.2
float inputs:subsurface = 0
float inputs:subsurface_anisotropy = 0
color3f inputs:subsurface_color = (1, 1, 1)
color3f inputs:subsurface_radius = (1, 1, 1)
float inputs:subsurface_scale = 1
token inputs:subsurface_type = "randomwalk"
vector3f inputs:tangent = (0, 0, 0)
float inputs:thin_film_IOR = 1.5
float inputs:thin_film_thickness = 0
bool inputs:thin_walled = 0
float inputs:transmission = 0
color3f inputs:transmission_color = (1, 1, 1)
float inputs:transmission_depth = 0
float inputs:transmission_dispersion = 0
float inputs:transmission_extra_roughness = 0
color3f inputs:transmission_scatter = (0, 0, 0)
float inputs:transmission_scatter_anisotropy = 0
bool inputs:transmit_aovs = 0
token outputs:shader
}

def Shader "checkerboard_good"
{
uniform token info:id = "arnold:checkerboard"
color3f inputs:color1 = (1, 1, 1)
color3f inputs:color2 = (0, 0, 0)
float inputs:contrast = 1
float inputs:filter_offset = 0
float inputs:filter_strength = 1
float inputs:u_frequency = 1
float inputs:u_offset = 0
string inputs:uvset = ""
float inputs:v_frequency = 1.27
float inputs:v_offset = 0
color3f outputs:rgb
}
}
}

def Xform "lights"
{
def DomeLight "domelight1" (
apiSchemas = ["HoudiniViewportGuideAPI"]
)
{
custom rel filters
float houdini:guidescale = 1
uniform bool houdini:inviewermenu = 0
color3f inputs:color = (1, 1, 1)
float inputs:diffuse = 1
bool inputs:enableColorTemperature = 0
float inputs:exposure = 0
float inputs:intensity = 1
bool inputs:normalize = 0
float inputs:specular = 1
asset inputs:texture:file = @@
token inputs:texture:format = "automatic"
rel light:filters
rel portals
float primvars:arnold:camera = 0 (
interpolation = "constant"
)
matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
uniform token[] xformOpOrder = ["xformOp:transform"]
}
}

def Xform "cameras"
{
def Camera "camera1" (
apiSchemas = ["HoudiniCameraPlateAPI", "HoudiniViewportGuideAPI"]
)
{
float2 clippingRange = (1, 1000000)
float exposure = 0
float focalLength = 0.5
float focusDistance = 5
float fStop = 0
float horizontalAperture = 0.20955
float horizontalApertureOffset = 0
asset houdini:backgroundimage = @@
asset houdini:foregroundimage = @@
float houdini:guidescale = 1
uniform bool houdini:inviewermenu = 1
token projection = "perspective"
double shutter:close = 0.25
double shutter:open = -0.25
float verticalAperture = 0.11787187
float verticalApertureOffset = 0
matrix4d xformOp:transform = ( (0.9995288042127394, 0, -0.03069478048547104, 0), (-0.004382454461563712, 0.9897551376987948, -0.14270795875399597, 0), (0.030380316686031666, 0.1427752338427078, 0.9892887692474915, 0), (-1.5657610058776417, 2.6599768452283414, 15.047198989242691, 1) )
uniform token[] xformOpOrder = ["xformOp:transform"]
}
}

def Scope "Render"
{
def RenderSettings "rendersettings"
{
custom int arnold:global:AA_seed
int arnold:global:AA_seed.timeSamples = {
1: 1,
}
uniform token aspectRatioConformPolicy = "expandAperture"
rel camera = </cameras/camera1>
uniform float4 dataWindowNDC = (0, 0, 1, 1)
uniform token[] includedPurposes = ["default"]
uniform bool instantaneousShutter = 0
uniform token[] materialBindingPurposes = ["full", "allPurpose"]
uniform float pixelAspectRatio = 1
rel products
uniform int2 resolution = (2048, 1152)
}
}

Loading

0 comments on commit 2d0479b

Please sign in to comment.